Merge branch 'master' into 1278-add-clustered-db-info
diff --git a/src/chttpd/src/chttpd_db.erl b/src/chttpd/src/chttpd_db.erl
index 7761007..5610779 100644
--- a/src/chttpd/src/chttpd_db.erl
+++ b/src/chttpd/src/chttpd_db.erl
@@ -320,11 +320,15 @@
db_req(#httpd{method='GET',path_parts=[DbName]}=Req, _Db) ->
% measure the time required to generate the etag, see if it's worth it
T0 = os:timestamp(),
- {ok, DbInfo} = fabric:get_db_info(DbName),
+ {ok, DbInfo} = fabric:get_db_info(DbName, [{format, aggregate}]),
DeltaT = timer:now_diff(os:timestamp(), T0) / 1000,
couch_stats:update_histogram([couchdb, dbinfo], DeltaT),
send_json(Req, {DbInfo});
+db_req(#httpd{method='GET',path_parts=[DbName, <<"_info">>]}=Req, _Db) ->
+ {ok, DbInfo} = fabric:get_db_info(DbName, [{format, set}]),
+ send_json(Req, {DbInfo});
+
db_req(#httpd{method='POST', path_parts=[DbName], user_ctx=Ctx}=Req, Db) ->
chttpd:validate_ctype(Req, "application/json"),
diff --git a/src/fabric/src/fabric.erl b/src/fabric/src/fabric.erl
index f5c7937..185ffeb 100644
--- a/src/fabric/src/fabric.erl
+++ b/src/fabric/src/fabric.erl
@@ -18,9 +18,9 @@
% DBs
-export([all_dbs/0, all_dbs/1, create_db/1, create_db/2, delete_db/1,
- delete_db/2, get_db_info/1, get_doc_count/1, set_revs_limit/3,
- set_security/2, set_security/3, get_revs_limit/1, get_security/1,
- get_security/2, get_all_security/1, get_all_security/2,
+ delete_db/2, get_db_info/1, get_db_info/2, get_doc_count/1,
+ set_revs_limit/3, set_security/2, set_security/3, get_revs_limit/1,
+ get_security/1, get_security/2, get_all_security/1, get_all_security/2,
compact/1, compact/2]).
% Documents
@@ -44,6 +44,7 @@
-type callback() :: fun((any(), any()) -> {ok | stop, any()}).
-type json_obj() :: {[{binary() | atom(), any()}]}.
-type option() :: atom() | {atom(), any()}.
+-type db_info_type() :: set | aggregate.
%% db operations
%% @equiv all_dbs(<<>>)
@@ -84,6 +85,23 @@
get_db_info(DbName) ->
fabric_db_info:go(dbname(DbName)).
+%% @doc returns a property list of interesting properties
+%% about the database such as `doc_count', `disk_size',
+%% etc.
+%% TODO: fix return type def
+-spec get_db_info(dbname(), db_info_type()) ->
+ {ok, [
+ {instance_start_time, binary()} |
+ {doc_count, non_neg_integer()} |
+ {doc_del_count, non_neg_integer()} |
+ {purge_seq, non_neg_integer()} |
+ {compact_running, boolean()} |
+ {disk_size, non_neg_integer()} |
+ {disk_format_version, pos_integer()}
+ ]}.
+get_db_info(DbName, Options) ->
+ fabric_db_info:go(dbname(DbName), Options).
+
%% @doc the number of docs in a database
-spec get_doc_count(dbname()) ->
{ok, non_neg_integer()} |
diff --git a/src/fabric/src/fabric_db_info.erl b/src/fabric/src/fabric_db_info.erl
index 98e8e52..1518034 100644
--- a/src/fabric/src/fabric_db_info.erl
+++ b/src/fabric/src/fabric_db_info.erl
@@ -12,21 +12,28 @@
-module(fabric_db_info).
--export([go/1]).
+-export([go/1, go/2]).
-include_lib("fabric/include/fabric.hrl").
-include_lib("mem3/include/mem3.hrl").
go(DbName) ->
+ go(DbName, [{format, aggregate}]).
+
+go(DbName, Options) ->
Shards = mem3:shards(DbName),
Workers = fabric_util:submit_jobs(Shards, get_db_info, []),
RexiMon = fabric_util:create_monitors(Shards),
- Fun = fun handle_message/3,
+ Fun = case couch_util:get_value(format, Options, aggregate) of
+ set -> fun handle_set_message/3;
+ _ -> fun handle_aggr_message/3
+ end,
{ok, ClusterInfo} = get_cluster_info(Shards),
- Acc0 = {fabric_dict:init(Workers, nil), [{cluster, ClusterInfo}]},
+ Acc0 = fabric_dict:init(Workers, nil),
try
case fabric_util:recv(Workers, #shard.ref, Fun, Acc0) of
- {ok, Acc} -> {ok, Acc};
+ {ok, Acc} ->
+ {ok, [{cluster, {ClusterInfo}} | Acc]};
{timeout, {WorkersDict, _}} ->
DefunctWorkers = fabric_util:remove_done_workers(
WorkersDict,
@@ -37,51 +44,84 @@
"get_db_info"
),
{error, timeout};
- {error, Error} -> throw(Error)
+ {error, Error} ->
+ throw(Error)
end
after
rexi_monitor:stop(RexiMon)
end.
-handle_message({rexi_DOWN, _, {_,NodeRef},_}, _Shard, {Counters, Acc}) ->
- case fabric_util:remove_down_workers(Counters, NodeRef) of
- {ok, NewCounters} ->
- {ok, {NewCounters, Acc}};
- error ->
- {error, {nodedown, <<"progress not possible">>}}
+
+handle_aggr_message({ok, Info}, Shard, Counters) ->
+ case fabric_dict:lookup_element(Shard, Counters) of
+ undefined ->
+ % already heard from someone else in this range
+ {ok, Counters};
+ nil ->
+ C1 = fabric_dict:store(Shard, Info, Counters),
+ C2 = fabric_view:remove_overlapping_shards(Shard, C1),
+ case fabric_dict:any(nil, C2) of
+ true ->
+ {ok, C2};
+ false ->
+ {stop, merge_results(C2)}
+ end
end;
-handle_message({rexi_EXIT, Reason}, Shard, {Counters, Acc}) ->
+handle_aggr_message({rexi_DOWN, _, {_,NodeRef},_}, _Shard, Counters) ->
+ case fabric_util:remove_down_workers(Counters, NodeRef) of
+ {ok, NewCounters} ->
+ {ok, NewCounters};
+ error ->
+ {error, {nodedown, <<"progress not possible">>}}
+ end;
+
+handle_aggr_message({rexi_EXIT, Reason}, Shard, Counters) ->
NewCounters = fabric_dict:erase(Shard, Counters),
case fabric_view:is_progress_possible(NewCounters) of
- true ->
- {ok, {NewCounters, Acc}};
- false ->
- {error, Reason}
+ true ->
+ {ok, NewCounters};
+ false ->
+ {error, Reason}
end;
-handle_message({ok, Info}, #shard{dbname=Name} = Shard, {Counters, Acc}) ->
- case fabric_dict:lookup_element(Shard, Counters) of
- undefined ->
- % already heard from someone else in this range
- {ok, {Counters, Acc}};
- nil ->
- Seq = couch_util:get_value(update_seq, Info),
- C1 = fabric_dict:store(Shard, Seq, Counters),
- C2 = fabric_view:remove_overlapping_shards(Shard, C1),
- case fabric_dict:any(nil, C2) of
+handle_aggr_message(Else, _Shard, _Acc) ->
+ %% TODO: do we want this behavior change?
+ {error, Else}.
+
+
+handle_set_message({ok, Info}, Shard, Counters) ->
+ C1 = fabric_dict:store(Shard, Info, Counters),
+ case fabric_dict:any(nil, C1) of
true ->
- {ok, {C2, [Info|Acc]}};
+ {ok, C1};
false ->
- {stop, [
- {db_name,Name},
- {update_seq, fabric_view_changes:pack_seqs(C2)} |
- merge_results(lists:flatten([Info|Acc]))
- ]}
- end
+ {stop, [{shards, {format_results(C1)}}]}
end;
-handle_message(_, _, Acc) ->
- {ok, Acc}.
+
+handle_set_message({rexi_DOWN, _, {_,NodeRef},_}, _Shard, Counters) ->
+ %% TODO: add error info to Counters rather than deleting
+ case fabric_util:remove_down_workers(Counters, NodeRef) of
+ {ok, NewCounters} ->
+ {ok, NewCounters};
+ error ->
+ {error, {nodedown, <<"progress not possible">>}}
+ end;
+
+handle_set_message({rexi_EXIT, Reason}, Shard, Counters) ->
+ %% TODO: add error info to Counters rather than deleting
+ NewCounters = fabric_dict:erase(Shard, Counters),
+ case fabric_view:is_progress_possible(NewCounters) of
+ true ->
+ {ok, NewCounters};
+ false ->
+ {error, Reason}
+ end;
+
+handle_set_message(Else, _Shard, _Acc) ->
+ %% TODO: do we want this behavior change?
+ {error, Else}.
+
merge_results(Info) ->
Dict = lists:foldl(fun({K,V},D0) -> orddict:append(K,V,D0) end,
@@ -111,6 +151,29 @@
Acc
end, [{instance_start_time, <<"0">>}], Dict).
+
+format_results(Counters) ->
+ dict:to_list(lists:foldl(
+ fun({S,I0}, D) ->
+ #shard{
+ range = [B, E],
+ node = Node
+ } = S,
+ I = [{node, Node} | I0],
+ HB = couch_util:to_hex(<<B:32/integer>>),
+ HE = couch_util:to_hex(<<E:32/integer>>),
+ R = list_to_binary(HB ++ "-" ++ HE),
+ case dict:find(R, D) of
+ {ok, L} ->
+ dict:store(R, [{I} | L], D);
+ error ->
+ dict:store(R, [{I}], D)
+ end
+ end,
+ dict:new(),
+ fabric_dict:to_list(Counters)
+ )).
+
merge_other_results(Results) ->
Dict = lists:foldl(fun({Props}, D) ->
lists:foldl(fun({K,V},D0) -> orddict:append(K,V,D0) end, D, Props)