Instrument Mango execution stats
Adds metrics for mango execution statistics:
* total docs examined (counter). Note that for non-quorum doc
reads this needs to be counted at the shard level and not be the
mango query coordinator
* total results returned (counter)
* query time (histogram)
* mango:selector evaluations (may be queries or _changes feeds)
For mrview-based queries which don't use quorum reads (the default),
total docs examined should equal mango:selector evaluations. However,
there are cases where mango selectors are evaluated outside of query
time e.g. partial indexing, selector-based _changes which make it
useful to maintain a distinct counter.
diff --git a/src/couch/priv/stats_descriptions.cfg b/src/couch/priv/stats_descriptions.cfg
index 557ac36..7c8fd94 100644
--- a/src/couch/priv/stats_descriptions.cfg
+++ b/src/couch/priv/stats_descriptions.cfg
@@ -310,3 +310,23 @@
{type, counter},
{desc, <<"number of mango queries that generated an index scan warning">>}
]}.
+{[mango, docs_examined], [
+ {type, counter},
+ {desc, <<"number of documents examined by mango queries coordinated by this node">>}
+]}.
+{[mango, quorum_docs_examined], [
+ {type, counter},
+ {desc, <<"number of documents examined by mango queries, using cluster quorum">>}
+]}.
+{[mango, results_returned], [
+ {type, counter},
+ {desc, <<"number of rows returned by mango queries">>}
+]}.
+{[mango, query_time], [
+ {type, histogram},
+ {desc, <<"length of time processing a mango query">>}
+]}.
+{[mango, evaluate_selector], [
+ {type, counter},
+ {desc, <<"number of mango selector evaluations">>}
+]}.
diff --git a/src/mango/src/mango_cursor_text.erl b/src/mango/src/mango_cursor_text.erl
index 6b202da..2b42c39 100644
--- a/src/mango/src/mango_cursor_text.erl
+++ b/src/mango/src/mango_cursor_text.erl
@@ -184,6 +184,7 @@
} = CAcc0,
CAcc1 = update_bookmark(CAcc0, Sort),
Stats1 = mango_execution_stats:incr_docs_examined(Stats),
+ couch_stats:increment_counter([mango, docs_examined]),
CAcc2 = CAcc1#cacc{execution_stats = Stats1},
case mango_selector:match(CAcc2#cacc.selector, Doc) of
true when Skip > 0 ->
diff --git a/src/mango/src/mango_cursor_view.erl b/src/mango/src/mango_cursor_view.erl
index 2569469..240ef50 100644
--- a/src/mango/src/mango_cursor_view.erl
+++ b/src/mango/src/mango_cursor_view.erl
@@ -249,6 +249,7 @@
Doc ->
put(mango_docs_examined, get(mango_docs_examined) + 1),
Selector = couch_util:get_value(selector, Options),
+ couch_stats:increment_counter([mango, docs_examined]),
case mango_selector:match(Selector, Doc) of
true ->
ok = rexi:stream2(ViewRow),
@@ -433,6 +434,7 @@
% an undefined doc was returned, indicating we should
% perform a quorum fetch
ExecutionStats1 = mango_execution_stats:incr_quorum_docs_examined(ExecutionStats),
+ couch_stats:increment_counter([mango, quorum_docs_examined]),
Id = couch_util:get_value(id, RowProps),
case mango_util:defer(fabric, open_doc, [Db, Id, Opts]) of
{ok, #doc{}=DocProps} ->
diff --git a/src/mango/src/mango_execution_stats.erl b/src/mango/src/mango_execution_stats.erl
index 7e8afd7..5878a31 100644
--- a/src/mango/src/mango_execution_stats.erl
+++ b/src/mango/src/mango_execution_stats.erl
@@ -62,6 +62,7 @@
incr_results_returned(Stats) ->
+ couch_stats:increment_counter([mango, results_returned]),
Stats#execution_stats {
resultsReturned = Stats#execution_stats.resultsReturned + 1
}.
@@ -81,11 +82,13 @@
}.
-maybe_add_stats(Opts, UserFun, Stats, UserAcc) ->
+maybe_add_stats(Opts, UserFun, Stats0, UserAcc) ->
+ Stats1 = log_end(Stats0),
+ couch_stats:update_histogram([mango, query_time], Stats1#execution_stats.executionTimeMs),
+
case couch_util:get_value(execution_stats, Opts) of
true ->
- Stats0 = log_end(Stats),
- JSONValue = to_json(Stats0),
+ JSONValue = to_json(Stats1),
Arg = {add_key, execution_stats, JSONValue},
{_Go, FinalUserAcc} = UserFun(Arg, UserAcc),
FinalUserAcc;
diff --git a/src/mango/src/mango_selector.erl b/src/mango/src/mango_selector.erl
index 005a6af..3ea83c2 100644
--- a/src/mango/src/mango_selector.erl
+++ b/src/mango/src/mango_selector.erl
@@ -52,15 +52,19 @@
% Match a selector against a #doc{} or EJSON value.
% This assumes that the Selector has been normalized.
% Returns true or false.
+match(Selector, D) ->
+ couch_stats:increment_counter([mango, evaluate_selector]),
+ match_int(Selector, D).
+
% An empty selector matches any value.
-match({[]}, _) ->
+match_int({[]}, _) ->
true;
-match(Selector, #doc{body=Body}) ->
+match_int(Selector, #doc{body=Body}) ->
match(Selector, Body, fun mango_json:cmp/2);
-match(Selector, {Props}) ->
+match_int(Selector, {Props}) ->
match(Selector, {Props}, fun mango_json:cmp/2).
% Convert each operator into a normalized version as well
@@ -582,7 +586,7 @@
erlang:error({unnormalized_selector, Sel}).
-% Returns true if Selector requires all
+% Returns true if Selector requires all
% fields in RequiredFields to exist in any matching documents.
% For each condition in the selector, check
@@ -612,13 +616,13 @@
% We can "see" through $and operator. Iterate
% through the list of child operators.
-has_required_fields_int([{[{<<"$and">>, Args}]}], RequiredFields)
+has_required_fields_int([{[{<<"$and">>, Args}]}], RequiredFields)
when is_list(Args) ->
has_required_fields_int(Args, RequiredFields);
% We can "see" through $or operator. Required fields
% must be covered by all children.
-has_required_fields_int([{[{<<"$or">>, Args}]} | Rest], RequiredFields)
+has_required_fields_int([{[{<<"$or">>, Args}]} | Rest], RequiredFields)
when is_list(Args) ->
Remainder0 = lists:foldl(fun(Arg, Acc) ->
% for each child test coverage against the full
@@ -635,7 +639,7 @@
% Handle $and operator where it has peers. Required fields
% can be covered by any child.
-has_required_fields_int([{[{<<"$and">>, Args}]} | Rest], RequiredFields)
+has_required_fields_int([{[{<<"$and">>, Args}]} | Rest], RequiredFields)
when is_list(Args) ->
Remainder = has_required_fields_int(Args, RequiredFields),
has_required_fields_int(Rest, Remainder);