Skip to content

Commit b6ea325

Browse files
authored
Add new HTTP endpoint /_node/_local/_smoosh/status. (apache#4766)
Introduce a new HTTP endpoint `/_node/_local/_smoosh/status` to get status information from the CouchDB auto-compaction daemon. Previously, this was only possible by starting a `remsh` session and manually calling the `smoosh:status/1` function. The internal data structures of `smoosh:status/1` are migrated into Erlang maps to send them directly as json to the client. To add more status information to smoosh in the future, the available information will be stored under the json key `channels`. Example: { "channels": { "ratio_dbs": { ... }, "slack_dbs": { ... }, ... }
1 parent 435db51 commit b6ea325

File tree

7 files changed

+174
-56
lines changed

7 files changed

+174
-56
lines changed

src/chttpd/src/chttpd_node.erl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@ handle_node_req(#httpd{path_parts = [_, <<"_local">>]} = Req) ->
3939
send_json(Req, 200, {[{name, node()}]});
4040
handle_node_req(#httpd{path_parts = [A, <<"_local">> | Rest]} = Req) ->
4141
handle_node_req(Req#httpd{path_parts = [A, node()] ++ Rest});
42+
% GET /_node/$node/_smoosh/status
43+
handle_node_req(#httpd{method = 'GET', path_parts = [_, _Node, <<"_smoosh">>, <<"status">>]} = Req) ->
44+
{ok, Status} = smoosh:status(),
45+
send_json(Req, 200, Status);
46+
handle_node_req(#httpd{path_parts = [_, _Node, <<"_smoosh">>, <<"status">>]} = Req) ->
47+
send_method_not_allowed(Req, "GET");
4248
% GET /_node/$node/_versions
4349
handle_node_req(#httpd{method = 'GET', path_parts = [_, _Node, <<"_versions">>]} = Req) ->
4450
IcuVer = couch_ejson_compare:get_icu_version(),

src/docs/src/api/server/common.rst

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1902,6 +1902,116 @@ See :ref:`Configuration of Prometheus Endpoint <config/prometheus>` for details.
19021902
Accept: text/plain
19031903
Host: localhost:17986
19041904
1905+
.. _api/server/smoosh/status:
1906+
1907+
=====================================
1908+
``/_node/{node-name}/_smoosh/status``
1909+
=====================================
1910+
1911+
.. versionadded:: 3.4
1912+
1913+
.. http:get:: /_node/{node-name}/_smoosh/status
1914+
:synopsis: Returns metrics of the CouchDB's auto-compaction daemon
1915+
1916+
This prints the state of each channel, how many jobs they are
1917+
currently running and how many jobs are enqueued (as well as the
1918+
lowest and highest priority of those enqueued items). The idea is to
1919+
provide, at a glance, sufficient insight into ``smoosh`` that an operator
1920+
can assess whether ``smoosh`` is adequately targeting the reclaimable
1921+
space in the cluster.
1922+
1923+
In general, a healthy status output will have
1924+
items in the ``ratio_dbs`` and ``ratio_views`` channels. Owing to the default
1925+
settings, the ``slack_dbs`` and ``slack_views`` will almost certainly have
1926+
items in them. Historically, we've not found that the slack channels,
1927+
on their own, are particularly adept at keeping things well compacted.
1928+
1929+
:code 200: Request completed successfully
1930+
:code 401: CouchDB Server Administrator privileges required
1931+
1932+
**Request**:
1933+
1934+
.. code-block:: http
1935+
1936+
GET /_node/_local/_smoosh/status HTTP/1.1
1937+
Host: 127.0.0.1:5984
1938+
Accept: */*
1939+
1940+
**Response**:
1941+
1942+
.. code-block:: http
1943+
1944+
HTTP/1.1 200 OK
1945+
Content-Type: application/json
1946+
1947+
{
1948+
"channels": {
1949+
"slack_dbs": {
1950+
"starting": 0,
1951+
"waiting": {
1952+
"size": 0,
1953+
"min": 0,
1954+
"max": 0
1955+
},
1956+
"active": 0
1957+
},
1958+
"ratio_dbs": {
1959+
"starting": 0,
1960+
"waiting": {
1961+
"size": 56,
1962+
"min": 1.125,
1963+
"max": 11.0625
1964+
},
1965+
"active": 0
1966+
},
1967+
"ratio_views": {
1968+
"starting": 0,
1969+
"waiting": {
1970+
"size": 0,
1971+
"min": 0,
1972+
"max": 0
1973+
},
1974+
"active": 0
1975+
},
1976+
"upgrade_dbs": {
1977+
"starting": 0,
1978+
"waiting": {
1979+
"size": 0,
1980+
"min": 0,
1981+
"max": 0
1982+
},
1983+
"active": 0
1984+
},
1985+
"slack_views": {
1986+
"starting": 0,
1987+
"waiting": {
1988+
"size": 0,
1989+
"min": 0,
1990+
"max": 0
1991+
},
1992+
"active": 0
1993+
},
1994+
"upgrade_views": {
1995+
"starting": 0,
1996+
"waiting": {
1997+
"size": 0,
1998+
"min": 0,
1999+
"max": 0
2000+
},
2001+
"active": 0
2002+
},
2003+
"index_cleanup": {
2004+
"starting": 0,
2005+
"waiting": {
2006+
"size": 0,
2007+
"min": 0,
2008+
"max": 0
2009+
},
2010+
"active": 0
2011+
}
2012+
}
2013+
}
2014+
19052015
.. _api/server/system:
19062016

19072017
==============================

src/smoosh/src/smoosh_channel.erl

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,10 @@ enqueue(ServerRef, Object, Priority) ->
7777
get_status(StatusTab) when is_reference(StatusTab) ->
7878
try ets:lookup(StatusTab, status) of
7979
[{status, Status}] -> Status;
80-
[] -> []
80+
[] -> #{}
8181
catch
8282
error:badarg ->
83-
[]
83+
#{}
8484
end.
8585

8686
close(ServerRef) ->
@@ -235,11 +235,11 @@ unpersist(Name) ->
235235
%
236236
set_status(#state{} = State) ->
237237
#state{active = Active, starting = Starting, waiting = Waiting} = State,
238-
Status = [
239-
{active, map_size(Active)},
240-
{starting, map_size(Starting)},
241-
{waiting, smoosh_priority_queue:info(Waiting)}
242-
],
238+
Status = #{
239+
active => map_size(Active),
240+
starting => map_size(Starting),
241+
waiting => smoosh_priority_queue:info(Waiting)
242+
},
243243
true = ets:insert(State#state.stab, {status, Status}),
244244
State.
245245

src/smoosh/src/smoosh_persist.erl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ t_persist_unpersist_disabled(_) ->
225225

226226
Q2 = unpersist(Name),
227227
?assertEqual(Name, smoosh_priority_queue:name(Q2)),
228-
?assertEqual([{size, 0}], smoosh_priority_queue:info(Q2)).
228+
?assertEqual(#{max => 0, min => 0, size => 0}, smoosh_priority_queue:info(Q2)).
229229

230230
t_persist_unpersist_enabled(_) ->
231231
Name = "chan2",
@@ -241,15 +241,15 @@ t_persist_unpersist_enabled(_) ->
241241
Q2 = unpersist(Name),
242242
?assertEqual(Name, smoosh_priority_queue:name(Q2)),
243243
Info2 = smoosh_priority_queue:info(Q2),
244-
?assertEqual([{size, 3}, {min, 1.0}, {max, infinity}], Info2),
244+
?assertEqual(#{max => infinity, min => 1.0, size => 3}, Info2),
245245
?assertEqual(Keys, drain_q(Q2)),
246246

247247
% Try to persist the already unpersisted queue
248248
?assertEqual(ok, persist(Q2, #{}, #{})),
249249
Q3 = unpersist(Name),
250250
?assertEqual(Name, smoosh_priority_queue:name(Q3)),
251251
Info3 = smoosh_priority_queue:info(Q2),
252-
?assertEqual([{size, 3}, {min, 1.0}, {max, infinity}], Info3),
252+
?assertEqual(#{max => infinity, min => 1.0, size => 3}, Info3),
253253
?assertEqual(Keys, drain_q(Q3)).
254254

255255
t_persist_unpersist_errors(_) ->
@@ -267,7 +267,7 @@ t_persist_unpersist_errors(_) ->
267267

268268
Q2 = unpersist(Name),
269269
?assertEqual(Name, smoosh_priority_queue:name(Q2)),
270-
?assertEqual([{size, 0}], smoosh_priority_queue:info(Q2)),
270+
?assertEqual(#{max => 0, min => 0, size => 0}, smoosh_priority_queue:info(Q2)),
271271

272272
Dir = state_dir(),
273273
ok = file:make_dir(Dir),
@@ -278,7 +278,7 @@ t_persist_unpersist_errors(_) ->
278278

279279
Q3 = unpersist(Name),
280280
?assertEqual(Name, smoosh_priority_queue:name(Q3)),
281-
?assertEqual([{size, 0}], smoosh_priority_queue:info(Q3)),
281+
?assertEqual(#{max => 0, min => 0, size => 0}, smoosh_priority_queue:info(Q3)),
282282

283283
ok = file:del_dir_r(Dir).
284284

src/smoosh/src/smoosh_priority_queue.erl

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -64,17 +64,14 @@ qsize(#priority_queue{tree = Tree}) ->
6464
gb_trees:size(Tree).
6565

6666
info(#priority_queue{tree = Tree} = Q) ->
67-
[
68-
{size, qsize(Q)}
69-
| case gb_trees:is_empty(Tree) of
70-
true ->
71-
[];
72-
false ->
73-
{{Min, _}, _} = gb_trees:smallest(Tree),
74-
{{Max, _}, _} = gb_trees:largest(Tree),
75-
[{min, Min}, {max, Max}]
76-
end
77-
].
67+
case gb_trees:is_empty(Tree) of
68+
true ->
69+
#{size => qsize(Q), min => 0, max => 0};
70+
false ->
71+
{{Min, _}, _} = gb_trees:smallest(Tree),
72+
{{Max, _}, _} = gb_trees:largest(Tree),
73+
#{size => qsize(Q), min => Min, max => Max}
74+
end.
7875

7976
insert(Key, Priority, Capacity, #priority_queue{tree = Tree, map = Map} = Q) ->
8077
TreeKey = {Priority, make_ref()},
@@ -122,7 +119,7 @@ basics_test() ->
122119
Q = new("foo"),
123120
?assertMatch(#priority_queue{}, Q),
124121
?assertEqual("foo", name(Q)),
125-
?assertEqual([{size, 0}], info(Q)).
122+
?assertEqual(0, maps:get(size, info(Q))).
126123

127124
empty_test() ->
128125
Q = new("foo"),
@@ -136,15 +133,15 @@ one_element_test() ->
136133
Q0 = new("foo"),
137134
Q = in(?K1, ?P1, 1, Q0),
138135
?assertMatch(#priority_queue{}, Q),
139-
?assertEqual([{size, 1}, {min, 1}, {max, 1}], info(Q)),
136+
?assertEqual(#{max => 1, min => 1, size => 1}, info(Q)),
140137
?assertEqual(Q, truncate(1, Q)),
141138
?assertMatch({?K1, #priority_queue{}}, out(Q)),
142139
{?K1, Q2} = out(Q),
143140
?assertEqual(Q2, Q0),
144141
?assertEqual(#{?K1 => ?P1}, to_map(Q)),
145142
Q3 = from_map("foo", 1, to_map(Q)),
146143
?assertEqual("foo", name(Q3)),
147-
?assertEqual([{size, 1}, {min, ?P1}, {max, ?P1}], info(Q3)),
144+
?assertEqual(#{max => ?P1, min => ?P1, size => 1}, info(Q3)),
148145
?assertEqual(to_map(Q), to_map(Q3)),
149146
?assertEqual(Q0, flush(Q)).
150147

@@ -153,7 +150,7 @@ multiple_elements_basics_test() ->
153150
Q1 = in(?K1, ?P1, 10, Q0),
154151
Q2 = in(?K2, ?P2, 10, Q1),
155152
Q3 = in(?K3, ?P3, 10, Q2),
156-
?assertEqual([{size, 3}, {min, ?P1}, {max, ?P3}], info(Q3)),
153+
?assertEqual(#{max => ?P3, min => ?P1, size => 3}, info(Q3)),
157154
?assertEqual([?K3, ?K2, ?K1], drain(Q3)).
158155

159156
update_element_same_priority_test() ->
@@ -166,7 +163,7 @@ update_element_new_priority_test() ->
166163
Q1 = in(?K1, ?P1, 10, Q0),
167164
Q2 = in(?K2, ?P2, 10, Q1),
168165
Q3 = in(?K1, ?P3, 10, Q2),
169-
?assertEqual([{size, 2}, {min, ?P2}, {max, ?P3}], info(Q3)),
166+
?assertEqual(#{max => ?P3, min => ?P2, size => 2}, info(Q3)),
170167
?assertEqual([?K1, ?K2], drain(Q3)).
171168

172169
capacity_test() ->
@@ -189,7 +186,7 @@ a_lot_of_elements_test() ->
189186
lists:seq(1, N)
190187
),
191188
Q = from_map("foo", N, maps:from_list(KVs)),
192-
?assertMatch([{size, N} | _], info(Q)),
189+
?assertMatch(N, maps:get(size, info(Q))),
193190
{_, Priorities} = lists:unzip(drain(Q)),
194191
?assertEqual(lists:reverse(lists:sort(Priorities)), Priorities).
195192

src/smoosh/src/smoosh_server.erl

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -96,12 +96,14 @@ flush() ->
9696
gen_server:call(?MODULE, flush, infinity).
9797

9898
status() ->
99-
try ets:foldl(fun get_channel_status/2, [], ?MODULE) of
100-
Res -> {ok, Res}
101-
catch
102-
error:badarg ->
103-
{ok, []}
104-
end.
99+
ChannelsStatus =
100+
try ets:foldl(fun get_channel_status/2, #{}, ?MODULE) of
101+
Res -> Res
102+
catch
103+
error:badarg ->
104+
#{}
105+
end,
106+
{ok, #{channels => ChannelsStatus}}.
105107

106108
enqueue(Object0) ->
107109
Object = smoosh_utils:validate_arg(Object0),
@@ -286,7 +288,7 @@ remove_enqueue_ref(Ref, #state{} = State) when is_reference(Ref) ->
286288

287289
get_channel_status(#channel{name = Name, stab = Tab}, Acc) ->
288290
Status = smoosh_channel:get_status(Tab),
289-
[{Name, Status} | Acc];
291+
Acc#{list_to_atom(Name) => Status};
290292
get_channel_status(_, Acc) ->
291293
Acc.
292294

src/smoosh/test/smoosh_tests.erl

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -82,29 +82,31 @@ teardown(DbName) ->
8282
config:delete("smoosh", "cleanup_index_files", false).
8383

8484
t_default_channels(_) ->
85+
ChannelStatus = maps:get(channels, status()),
8586
?assertMatch(
8687
[
87-
{"index_cleanup", _},
88-
{"ratio_dbs", _},
89-
{"ratio_views", _},
90-
{"slack_dbs", _},
91-
{"slack_views", _},
92-
{"upgrade_dbs", _},
93-
{"upgrade_views", _}
88+
index_cleanup,
89+
ratio_dbs,
90+
ratio_views,
91+
slack_dbs,
92+
slack_views,
93+
upgrade_dbs,
94+
upgrade_views
9495
],
95-
status()
96+
lists:sort(maps:keys(ChannelStatus))
9697
),
9798
% If app hasn't started status won't crash
9899
application:stop(smoosh),
99-
?assertEqual([], status()).
100+
?assertEqual(#{channels => #{}}, status()).
100101

101102
t_channels_recreated_on_crash(_) ->
102103
RatioDbsPid = get_channel_pid("ratio_dbs"),
103104
meck:reset(smoosh_channel),
104105
exit(RatioDbsPid, kill),
105106
meck:wait(1, smoosh_channel, start_link, 1, 3000),
106107
wait_for_channels(7),
107-
?assertMatch([_, {"ratio_dbs", _} | _], status()),
108+
ChannelStatus = maps:get(channels, status()),
109+
?assertMatch(true, maps:is_key(ratio_dbs, ChannelStatus)),
108110
?assertNotEqual(RatioDbsPid, get_channel_pid("ratio_dbs")).
109111

110112
t_can_create_and_delete_channels(_) ->
@@ -402,17 +404,17 @@ delete_doc(DbName, DDocId) ->
402404

403405
status() ->
404406
{ok, Props} = smoosh:status(),
405-
lists:keysort(1, Props).
407+
Props.
406408

407409
status(Channel) ->
408-
case lists:keyfind(Channel, 1, status()) of
409-
{_, Val} ->
410-
Val,
411-
Active = proplists:get_value(active, Val),
412-
Starting = proplists:get_value(starting, Val),
413-
WaitingInfo = proplists:get_value(waiting, Val),
414-
Waiting = proplists:get_value(size, WaitingInfo),
415-
{Active, Starting, Waiting};
410+
ChannelStatus = maps:get(channels, status()),
411+
ChannelAtom = list_to_atom(Channel),
412+
case maps:is_key(ChannelAtom, ChannelStatus) of
413+
true ->
414+
#{active := Active, starting := Starting, waiting := Waiting} = maps:get(
415+
ChannelAtom, ChannelStatus
416+
),
417+
{Active, Starting, maps:get(size, Waiting)};
416418
false ->
417419
false
418420
end.
@@ -443,7 +445,8 @@ wait_for_channels() ->
443445

444446
wait_for_channels(N) when is_integer(N), N >= 0 ->
445447
WaitFun = fun() ->
446-
case length(status()) of
448+
ChannelStatus = maps:get(channels, status()),
449+
case length(maps:keys(ChannelStatus)) of
447450
N -> ok;
448451
_ -> wait
449452
end

0 commit comments

Comments
 (0)