1%% @author Couchbase <info@couchbase.com>
2%% @copyright 2018 Couchbase, Inc.
3%%
4%% Licensed under the Apache License, Version 2.0 (the "License");
5%% you may not use this file except in compliance with the License.
6%% You may obtain a copy of the License at
7%%
8%%      http://www.apache.org/licenses/LICENSE-2.0
9%%
10%% Unless required by applicable law or agreed to in writing, software
11%% distributed under the License is distributed on an "AS IS" BASIS,
12%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13%% See the License for the specific language governing permissions and
14%% limitations under the License.
15%%
16%% @doc Query volume logger
17%%
18
19-module(couch_query_logger).
20-behaviour(gen_server).
21
22-include("couch_db.hrl").
23
24-define(INTERVAL, 60000).
25-define(ENABLED, true).
26
27%% gen_server callbacks
28-export([init/1, handle_call/3, handle_cast/2,
29         handle_info/2, terminate/2, code_change/3]).
30
31-export([start_link/0, log/3]).
32-export([set_interval/1, reset_interval/0]).
33-export([enable/0, disable/0]).
34
35-record(state, {interval, enabled}).
36
37%% ===================================================================
38%% gen_server callbacks
39%% ===================================================================
40
41init([]) ->
42    ets:new(?MODULE, [named_table, private]),
43    erlang:send_after(?INTERVAL, self(), dump),
44    {ok, #state{interval = ?INTERVAL, enabled = ?ENABLED}}.
45
46handle_call({enable, Enabled}, _From, State) ->
47    {reply, ok, State#state{enabled = Enabled}};
48
49handle_call({interval, Interval}, _From, State) ->
50    {reply, ok, State#state{interval = Interval}};
51
52handle_call({Path, Origin, Staleness}, _From, #state{enabled = true} = State) ->
53    Pos = pos(Origin, Staleness),
54    try
55        ets:update_counter(?MODULE, Path, {Pos, 1})
56    catch
57        error:badarg ->
58            ets:insert(?MODULE, default(Path, Pos))
59    end,
60    {reply, ok, State};
61
62handle_call(_Request, _From, State) ->
63    {reply, ignored, State}.
64
65handle_cast(_Request, State) ->
66    {noreply, State}.
67
68handle_info(dump, #state{interval = Interval} = State) ->
69    dump(),
70    ets:delete_all_objects(?MODULE),
71    erlang:send_after(Interval, self(), dump),
72    {noreply, State};
73
74handle_info(_Info, State) ->
75    {noreply, State}.
76
77terminate(Reason, _State) ->
78    dump(),
79    Len = process_info(self(), message_queue_len),
80    Msg = ?LOG_USERDATA(Reason),
81    ?LOG_ERROR("couch_query_logger terminating because of ~s : ~p", [Msg, Len]),
82    ok.
83
84code_change(_OldVsn, State, _Extra) ->
85  {ok, State}.
86
87%% ===================================================================
88%% module callbacks
89%% ===================================================================
90
91start_link() ->
92    gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
93
94log(Path, Origin, Staleness) ->
95    gen_server:call(?MODULE, {Path, Origin, Staleness}).
96
97set_interval(Interval) when is_integer(Interval)->
98    gen_server:call(?MODULE, {interval, Interval}).
99
100reset_interval() ->
101    gen_server:call(?MODULE, {interval, ?INTERVAL}).
102
103enable() ->
104    gen_server:call(?MODULE, {enable, true}).
105
106disable() ->
107    gen_server:call(?MODULE, {enable, false}).
108
109%% ===================================================================
110%% helper functions
111%% ===================================================================
112
113pos(internal, ok) -> 2;
114pos(internal, update_after) -> 3;
115pos(internal, false) -> 4;
116pos(external, ok) -> 5;
117pos(external, update_after) -> 6;
118pos(external, false) -> 7.
119
120default(Path, Pos) ->
121    erlang:setelement(Pos, {Path, 0, 0, 0, 0, 0, 0}, 1).
122
123tostring({Path, IO, IU, IF, EO, EU, EF}, Acc) ->
124    [io_lib:format("~s | internal.stale={ok: ~B, update_after: ~B, false: ~B}"
125                   " | external.stale={ok: ~B, update_after: ~B, false: ~B}~n",
126                   [?LOG_USERDATA(Path), IO, IU, IF, EO, EU, EF]) | Acc].
127
128dump() ->
129    QVol = ets:foldl(fun tostring/2, [], ?MODULE),
130    Msg = ["Query-Volume", $\n, QVol, "---"],
131    ?LOG_INFO("~s", [?l2b(Msg)]).