Skip to content
This repository

Integrate the Exometer metrics package #465

Open
wants to merge 19 commits into from

2 participants

Ulf Wiger Jordan West
Ulf Wiger
Collaborator

This PR is part of a set of PRs aimed at integrating the Exometer metrics package into Riak.

From our measurements so far, Exometer offers both better throughput and lower footprint than the previous metrics management, and at the same time offers more flexible and uniform handling and better extensibility.

In addition to maintaining the console command riak-admin status and the HTTP JSON report (which aim to be backwards-compatible), a new console command, riak-admin stat <cmd> has been added, for selective reporting of statistics as well as some management (ability to enable/disable metrics on the fly).

Other noteworthy changes:

  • Exometer entry names are always lists. In riak, only atoms and numbers should be used as list elements.
  • A top-level 'prefix' (default: riak) has been added, in order to differentiate between stats from different riak-style products (e.g. when reporting stats to collectd). The function riak_core_stat:prefix() is generated as a constant expression through the parse transform riak_core_stat_xform, which in its turn checks the OS env variable RIAK_CORE_STAT_PREFIX. A typical entry would thus be e.g. [riak,riak_kv,node,gets,siblings].
  • Internally in riak, stats are referred to symbolically using the same (tuple-based) names as before. These often refer to more than one low-level metric, so it seemed reasonable to keep this naming scheme.
  • Exometer provides similar functionality as 'sidejob' and the riak_core stat cache, so these are no longer used for stats management (although sidejob still maintains some stats on its own, which are accessible via Exometer). Other apps still register with riak_core_stat, but need not provide callbacks in other to query the stats. The query style of exometer is the same as that of riak_core_stat.
Ulf Wiger uwiger referenced this pull request in basho/riak_api
Open

Integrate the Exometer metrics package #45

Ulf Wiger uwiger referenced this pull request in basho/riak_pipe
Open

Integrate the Exometer metrics package #81

Ulf Wiger uwiger referenced this pull request in basho/riak_search
Closed

Integrate the Exometer metrics package #155

Ulf Wiger uwiger referenced this pull request in basho/riak_control
Open

Integrate the Exometer metrics package #175

Ulf Wiger uwiger referenced this pull request in basho/yokozuna
Open

Integrate the Exometer metrics package #246

Ulf Wiger uwiger referenced this pull request in basho/riak
Open

Integrate the Exometer metrics package #448

Jordan West
Collaborator

marked as 2.1 for same reason as #487 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
8 rebar.config
... ... @@ -1,4 +1,5 @@
1   -{erl_first_files, ["src/gen_nb_server.erl", "src/riak_core_gen_server.erl"]}.
  1 +{erl_first_files, ["src/gen_nb_server.erl", "src/riak_core_gen_server.erl",
  2 + "src/riak_core_stat_xform"]}.
2 3 {cover_enabled, true}.
3 4 {erl_opts, [warnings_as_errors, {parse_transform, lager_transform}, debug_info]}.
4 5 {edoc_opts, [{preprocess, true}]}.
@@ -12,8 +13,9 @@
12 13 {poolboy, ".*", {git, "git://github.com/basho/poolboy.git", {tag, "0.8.1p2"}}},
13 14 {basho_stats, ".*", {git, "git://github.com/basho/basho_stats.git", {tag, "1.0.3"}}},
14 15 {riak_sysmon, ".*", {git, "git://github.com/basho/riak_sysmon.git", {branch, "develop"}}},
15   - {folsom, "0.7.4p4", {git, "git://github.com/basho/folsom.git", {tag, "0.7.4p4"}}},
  16 + {folsom, ".*", {git, "git://github.com/basho/folsom.git", {branch, "master"}}},
16 17 {riak_ensemble, ".*", {git, "git://github.com/basho/riak_ensemble", {branch, "develop"}}},
17 18 {pbkdf2, ".*", {git, "git://github.com/basho/erlang-pbkdf2.git", {tag, "2.0.0"}}},
18   - {eleveldb, ".*", {git, "git://github.com/basho/eleveldb.git", {branch, "develop"}}}
  19 + {eleveldb, ".*", {git, "git://github.com/basho/eleveldb.git", {branch, "develop"}}},
  20 + {exometer, ".*", {git, "git://github.com/Feuerlabs/exometer.git", {branch, "master"}}}
19 21 ]}.
9 src/riak_core.app.src
@@ -18,7 +18,8 @@
18 18 basho_stats,
19 19 eleveldb,
20 20 pbkdf2,
21   - poolboy
  21 + poolboy,
  22 + exometer
22 23 ]},
23 24 {mod, {riak_core_app, []}},
24 25 {env, [
@@ -55,11 +56,11 @@
55 56 %% Handoff IP/port
56 57 {handoff_port, 8099},
57 58 {handoff_ip, "0.0.0.0"},
58   -
  59 +
59 60 %% Disterl buffer sizes in bytes.
60 61 %% These sizes (3*128*1024 & 6*128*1024) were
61   - %% derived from a limited amount of testing in a
62   - %% 10GE environment, and may need tuning for your
  62 + %% derived from a limited amount of testing in a
  63 + %% 10GE environment, and may need tuning for your
63 64 %% network and workload. In particular they're likely
64 65 %% too small to be optimal for larger object sizes.
65 66 {dist_send_buf_size, 393216},
142 src/riak_core_console.erl
@@ -28,7 +28,8 @@
28 28 add_source/1, del_source/1, grant/1, revoke/1,
29 29 print_users/1, print_user/1, print_sources/1,
30 30 print_groups/1, print_group/1, print_grants/1,
31   - security_enable/1, security_disable/1, security_status/1, ciphers/1]).
  31 + security_enable/1, security_disable/1, security_status/1, ciphers/1,
  32 + stat_show/1, stat_showall/1, stat_info/1, stat_enable/1, stat_disable/1]).
32 33
33 34 %% @doc Return for a given ring and node, percentage currently owned and
34 35 %% anticipated after the transitions have been completed.
@@ -1141,3 +1142,142 @@ parse_cidr(CIDR) ->
1141 1142 [IP, Mask] = string:tokens(CIDR, "/"),
1142 1143 {ok, Addr} = inet_parse:address(IP),
1143 1144 {Addr, list_to_integer(Mask)}.
  1145 +
  1146 +
  1147 +stat_show(Arg) ->
  1148 + print_stats(find_entries(Arg)).
  1149 +
  1150 +stat_showall(Arg) ->
  1151 + print_stats(find_entries(Arg, '_')).
  1152 +
  1153 +find_entries(Arg) ->
  1154 + find_entries(Arg, enabled).
  1155 +
  1156 +find_entries(Arg, Status) ->
  1157 + Patterns = lists:flatten([parse_stat_entry(S, Status) || S <- Arg]),
  1158 + exometer:select(Patterns).
  1159 +
  1160 +print_stats(Entries) ->
  1161 + io:fwrite(
  1162 + [io_lib:fwrite("~p: ~p~n", [E, get_value(E, Status)]) || {E, _T, Status} <- Entries]).
  1163 +
  1164 +get_value(_, disabled) ->
  1165 + disabled;
  1166 +get_value(E, _Status) ->
  1167 + case exometer:get_value(E) of
  1168 + {ok, V} -> V;
  1169 + {error,_} -> unavailable
  1170 + end.
  1171 +
  1172 +stat_enable(Arg) ->
  1173 + [io:fwrite("~p: ~p~n", [N, change_status(N, enabled)])
  1174 + || {N, _, _} <- find_entries(Arg, disabled)].
  1175 +
  1176 +stat_disable(Arg) ->
  1177 + [io:fwrite("~p: ~p~n", [N, change_status(N, disabled)])
  1178 + || {N, _, _} <- find_entries(Arg, enabled)].
  1179 +
  1180 +change_status(N, St) ->
  1181 + case exometer:setopts(N, [{status, St}]) of
  1182 + ok ->
  1183 + St;
  1184 + Error ->
  1185 + Error
  1186 + end.
  1187 +
  1188 +stat_info(Arg) ->
  1189 + {Attrs, RestArg} = pick_info_attrs(split_arg(Arg)),
  1190 + [print_info(E, Attrs) || E <- find_entries(RestArg, '_')].
  1191 +
  1192 +pick_info_attrs(Arg) ->
  1193 + lists:foldr(
  1194 + fun("-name" , {As, Ps}) -> {[name |As], Ps};
  1195 + ("-type" , {As, Ps}) -> {[type |As], Ps};
  1196 + ("-module" , {As, Ps}) -> {[module |As], Ps};
  1197 + ("-value" , {As, Ps}) -> {[value |As], Ps};
  1198 + ("-cache" , {As, Ps}) -> {[cache |As], Ps};
  1199 + ("-status" , {As, Ps}) -> {[status |As], Ps};
  1200 + ("-timestamp", {As, Ps}) -> {[timestamp|As], Ps};
  1201 + ("-options" , {As, Ps}) -> {[options |As], Ps};
  1202 + (P, {As, Ps}) -> {As, [P|Ps]}
  1203 + end, {[], []}, Arg).
  1204 +
  1205 +print_info({N, _Type, _Status}, [A|Attrs]) ->
  1206 + Hdr = lists:flatten(io_lib:fwrite("~p: ", [N])),
  1207 + Pad = lists:duplicate(length(Hdr), $\s),
  1208 + Info = exometer:info(N),
  1209 + Body = [io_lib:fwrite("~w = ~p~n", [A, proplists:get_value(A, Info)])
  1210 + | lists:map(fun(Ax) ->
  1211 + io_lib:fwrite(Pad ++ "~w = ~p~n",
  1212 + [Ax, proplists:get_value(Ax, Info)])
  1213 + end, Attrs)],
  1214 + io:fwrite([Hdr, Body]).
  1215 +
  1216 +split_arg([Str]) ->
  1217 + re:split(Str, "\\s", [{return,list}]).
  1218 +
  1219 +parse_stat_entry("[" ++ _ = Expr, _Status) ->
  1220 + case erl_scan:string(ensure_trailing_dot(Expr)) of
  1221 + {ok, Toks, _} ->
  1222 + case erl_parse:parse_exprs(Toks) of
  1223 + {ok, [Abst]} ->
  1224 + partial_eval(Abst);
  1225 + Error ->
  1226 + io:fwrite("(Parse error for ~p: ~p~n", [Expr, Error]),
  1227 + []
  1228 + end;
  1229 + ScanErr ->
  1230 + io:fwrite("(Scan error for ~p: ~p~n", [Expr, ScanErr]),
  1231 + []
  1232 + end;
  1233 +parse_stat_entry(Str, Status) when Status==enabled; Status==disabled; Status=='_' ->
  1234 + Parts = re:split(Str, "\\.", [{return,list}]),
  1235 + {{replace_parts(Parts),'_',Status}, [], ['$_']};
  1236 +parse_stat_entry(_, Status) ->
  1237 + io:fwrite("(Illegal status: ~p~n", [Status]).
  1238 +
  1239 +
  1240 +ensure_trailing_dot(Str) ->
  1241 + case lists:reverse(Str) of
  1242 + "." ++ _ ->
  1243 + Str;
  1244 + _ ->
  1245 + Str ++ "."
  1246 + end.
  1247 +
  1248 +partial_eval({cons,_,H,T}) ->
  1249 + [partial_eval(H) | partial_eval(T)];
  1250 +%% partial_eval({nil,_}) ->
  1251 +%% [];
  1252 +partial_eval({tuple,_,Elems}) ->
  1253 + list_to_tuple([partial_eval(E) || E <- Elems]);
  1254 +%% partial_eval({T,_,X}) when T==atom; T==integer; T==float ->
  1255 +%% X;
  1256 +partial_eval({op,_,'++',L1,L2}) ->
  1257 + partial_eval(L1) ++ partial_eval(L2);
  1258 +partial_eval(X) ->
  1259 + erl_parse:normalise(X).
  1260 +
  1261 +replace_parts([H|T]) ->
  1262 + R = case H of
  1263 + "*" -> '_';
  1264 + "'" ++ _ ->
  1265 + case erl_scan:string(H) of
  1266 + {ok, [{atom, _, A}], _} ->
  1267 + A;
  1268 + Error ->
  1269 + error(Error)
  1270 + end;
  1271 + [C|_] when C >= $0, C =< $9 ->
  1272 + try list_to_integer(H)
  1273 + catch
  1274 + error:_ -> list_to_atom(H)
  1275 + end;
  1276 + _ -> list_to_atom(H)
  1277 + end,
  1278 + case T of
  1279 + ["**"] -> [R] ++ '_';
  1280 + _ -> [R|replace_parts(T)]
  1281 + end;
  1282 +replace_parts([]) ->
  1283 + [].
162 src/riak_core_stat.erl
@@ -23,13 +23,17 @@
23 23 -behaviour(gen_server).
24 24
25 25 %% API
26   --export([start_link/0, get_stats/0, update/1,
27   - register_stats/0, produce_stats/0]).
  26 +-export([start_link/0, get_stats/0, get_stats/1, update/1,
  27 + register_stats/0, produce_stats/0, vnodeq_stats/0,
  28 + register_stats/2,
  29 + prefix/0]).
28 30
29 31 %% gen_server callbacks
30 32 -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
31 33 terminate/2, code_change/3]).
32 34
  35 +-compile({parse_transform, riak_core_stat_xform}).
  36 +
33 37 -ifdef(TEST).
34 38 -include_lib("eunit/include/eunit.hrl").
35 39 -endif.
@@ -42,18 +46,41 @@ start_link() ->
42 46 gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
43 47
44 48 register_stats() ->
45   - _ = [(catch folsom_metrics:delete_metric({?APP, Name})) || {Name, _Type} <- stats()],
46   - _ = [register_stat({?APP, Name}, Type) || {Name, Type} <- stats()],
47   - riak_core_stat_cache:register_app(?APP, {?MODULE, produce_stats, []}).
  49 + register_stats(common, system_stats()),
  50 + register_stats(?APP, stats()).
  51 +
  52 +%% @spec register_stats(App, Stats) -> ok
  53 +%% @doc (Re-)Register a list of metrics for App.
  54 +register_stats(App, Stats) ->
  55 + P = prefix(),
  56 + lists:foreach(fun(Stat) ->
  57 + register_stat(P, App, Stat)
  58 + end, Stats).
  59 +
  60 +register_stat(P, App, Stat) ->
  61 + {Name, Type, Opts} = case Stat of
  62 + {N, T} -> {N, T, []};
  63 + {N, T, Os} -> {N, T, Os}
  64 + end,
  65 + exometer:re_register(stat_name(P,App,Name), Type, Opts).
  66 +
  67 +stat_name(P, App, N) when is_atom(N) ->
  68 + stat_name_([P, App, N]);
  69 +stat_name(P, App, N) when is_list(N) ->
  70 + stat_name_([P, App | N]).
  71 +
  72 +stat_name_([P, [] | Rest]) -> [P | Rest];
  73 +stat_name_(N) -> N.
  74 +
48 75
49 76 %% @spec get_stats() -> proplist()
50 77 %% @doc Get the current aggregation of stats.
51 78 get_stats() ->
52   - case riak_core_stat_cache:get_stats(?APP) of
53   - {ok, Stats, _TS} ->
54   - Stats;
55   - Error -> Error
56   - end.
  79 + get_stats(?APP) ++ vnodeq_stats().
  80 +
  81 +get_stats(App) ->
  82 + P = prefix(),
  83 + exometer:get_values([P, App]).
57 84
58 85 update(Arg) ->
59 86 gen_server:cast(?SERVER, {update, Arg}).
@@ -65,6 +92,9 @@ produce_stats() ->
65 92 lists:append([gossip_stats(),
66 93 vnodeq_stats()]).
67 94
  95 +prefix() ->
  96 + app_helper:get_env(riak_core, stat_prefix, riak).
  97 +
68 98 %% gen_server
69 99
70 100 init([]) ->
@@ -75,7 +105,8 @@ handle_call(_Req, _From, State) ->
75 105 {reply, ok, State}.
76 106
77 107 handle_cast({update, Arg}, State) ->
78   - ok = update1(Arg),
  108 + exometer:update([prefix(), ?APP, Arg], update_value(Arg)),
  109 + %% update1(Arg),
79 110 {noreply, State};
80 111 handle_cast(_Req, State) ->
81 112 {noreply, State}.
@@ -89,35 +120,11 @@ terminate(_Reason, _State) ->
89 120 code_change(_OldVsn, State, _Extra) ->
90 121 {ok, State}.
91 122
92   -%% @spec update1(term()) -> ok
93   -%% @doc Update the given stat.
94   -update1(rejected_handoffs) ->
95   - folsom_metrics:notify_existing_metric({?APP, rejected_handoffs}, {inc, 1}, counter);
96   -
97   -update1(handoff_timeouts) ->
98   - folsom_metrics:notify_existing_metric({?APP, handoff_timeouts}, {inc, 1}, counter);
99   -
100   -update1(ignored_gossip) ->
101   - folsom_metrics:notify_existing_metric({?APP, ignored_gossip_total}, {inc, 1}, counter);
102   -
103   -update1(gossip_received) ->
104   - folsom_metrics:notify_existing_metric({?APP, gossip_received}, 1, spiral);
105   -
106   -update1(rings_reconciled) ->
107   - folsom_metrics:notify_existing_metric({?APP, rings_reconciled}, 1, spiral);
108   -
109   -update1(dropped_vnode_requests) ->
110   - folsom_metrics:notify_existing_metric({?APP, dropped_vnode_requests_total}, {inc, 1}, counter);
111   -
112   -update1(converge_timer_begin) ->
113   - folsom_metrics:notify_existing_metric({?APP, converge_delay}, timer_start, duration);
114   -update1(converge_timer_end) ->
115   - folsom_metrics:notify_existing_metric({?APP, converge_delay}, timer_end, duration);
116   -
117   -update1(rebalance_timer_begin) ->
118   - folsom_metrics:notify_existing_metric({?APP, rebalance_delay}, timer_start, duration);
119   -update1(rebalance_timer_end) ->
120   - folsom_metrics:notify_existing_metric({?APP, rebalance_delay}, timer_end, duration).
  123 +update_value(converge_timer_begin) -> timer_begin;
  124 +update_value(rebalance_timer_begin) -> timer_begin;
  125 +update_value(converge_timer_end) -> timer_begin;
  126 +update_value(rebalance_timer_end) -> timer_begin;
  127 +update_value(_) -> 1.
121 128
122 129 %% private
123 130 stats() ->
@@ -130,12 +137,9 @@ stats() ->
130 137 {converge_delay, duration},
131 138 {rebalance_delay, duration}].
132 139
133   -register_stat(Name, counter) ->
134   - folsom_metrics:new_counter(Name);
135   -register_stat(Name, spiral) ->
136   - folsom_metrics:new_spiral(Name);
137   -register_stat(Name, duration) ->
138   - folsom_metrics:new_duration(Name).
  140 +system_stats() ->
  141 + [{cpu_stats, cpu, [{sample_interval, 5000}]}].
  142 +
139 143
140 144 gossip_stats() ->
141 145 lists:flatten([backwards_compat(Stat, Type, riak_core_stat_q:calc_stat({{?APP, Stat}, Type})) ||
@@ -143,13 +147,15 @@ gossip_stats() ->
143 147
144 148 backwards_compat(Name, Type, unavailable) when Type =/= counter ->
145 149 backwards_compat(Name, Type, []);
  150 +backwards_compat(Name, Type, {error,not_found}) when Type =/= counter ->
  151 + backwards_compat(Name, Type, []);
146 152 backwards_compat(rings_reconciled, spiral, Stats) ->
147 153 [{rings_reconciled_total, proplists:get_value(count, Stats, unavailable)},
148 154 {rings_reconciled, safe_trunc(proplists:get_value(one, Stats, unavailable))}];
149 155 backwards_compat(gossip_received, spiral, Stats) ->
150 156 {gossip_received, safe_trunc(proplists:get_value(one, Stats, unavailable))};
151 157 backwards_compat(Name, counter, Stats) ->
152   - {Name, Stats};
  158 + {Name, proplists:get_value(value, Stats)};
153 159 backwards_compat(Name, duration, Stats) ->
154 160 [{join(Name, min), safe_trunc(proplists:get_value(min, Stats, unavailable))},
155 161 {join(Name, max), safe_trunc(proplists:get_value(max, Stats, unavailable))},
@@ -197,17 +203,15 @@ vnodeq_aggregate(Service, MQLs0) ->
197 203 1 ->
198 204 lists:nth(Len div 2 + 1, MQLs)
199 205 end,
200   - [{vnodeq_atom(Service, <<"s_running">>), Len},
201   - {vnodeq_atom(Service, <<"q_min">>), lists:nth(1, MQLs)},
202   - {vnodeq_atom(Service, <<"q_median">>), Median},
203   - {vnodeq_atom(Service, <<"q_mean">>), Mean},
204   - {vnodeq_atom(Service, <<"q_max">>), lists:nth(Len, MQLs)},
205   - {vnodeq_atom(Service, <<"q_total">>), Total}].
  206 + P = prefix(),
  207 + [{[P, riak_core, vnodeq_atom(Service,<<"s_running">>)], [{value, Len}]},
  208 + {[P, riak_core, vnodeq_atom(Service,<<"q">>)],
  209 + [{min, lists:nth(1, MQLs)}, {median, Median}, {mean, Mean},
  210 + {max, lists:nth(Len, MQLs)}, {total, Total}]}].
206 211
207 212 vnodeq_atom(Service, Desc) ->
208 213 binary_to_atom(<<(atom_to_binary(Service, latin1))/binary, Desc/binary>>, latin1).
209 214
210   -
211 215 -ifdef(TEST).
212 216
213 217 %% Check vnodeq aggregation function
@@ -215,48 +219,38 @@ vnodeq_aggregate_empty_test() ->
215 219 ?assertEqual([], vnodeq_aggregate(service_vnode, [])).
216 220
217 221 vnodeq_aggregate_odd1_test() ->
218   - ?assertEqual([{service_vnodes_running, 1},
219   - {service_vnodeq_min, 10},
220   - {service_vnodeq_median, 10},
221   - {service_vnodeq_mean, 10},
222   - {service_vnodeq_max, 10},
223   - {service_vnodeq_total, 10}],
  222 + P = prefix(),
  223 + ?assertEqual([{[P, riak_core, service_vnodes_running], 1},
  224 + {[P, riak_core, service_vnodeq],
  225 + [{min, 10}, {median, 10}, {mean, 10}, {max, 10}, {total, 10}]}],
224 226 vnodeq_aggregate(service_vnode, [10])).
225 227
226 228 vnodeq_aggregate_odd3_test() ->
227   - ?assertEqual([{service_vnodes_running, 3},
228   - {service_vnodeq_min, 1},
229   - {service_vnodeq_median, 2},
230   - {service_vnodeq_mean, 2},
231   - {service_vnodeq_max, 3},
232   - {service_vnodeq_total, 6}],
  229 + P = prefix(),
  230 + ?assertEqual([{[P, riak_core, service_vnodes_running], 3},
  231 + {[P, riak_core, service_vnodeq],
  232 + [{min, 1}, {median, 2}, {mean, 2}, {max, 3}, {total, 6}]}],
233 233 vnodeq_aggregate(service_vnode, [1, 2, 3])).
234 234
235 235 vnodeq_aggregate_odd5_test() ->
236   - ?assertEqual([{service_vnodes_running, 5},
237   - {service_vnodeq_min, 0},
238   - {service_vnodeq_median, 1},
239   - {service_vnodeq_mean, 2},
240   - {service_vnodeq_max, 5},
241   - {service_vnodeq_total, 10}],
  236 + P = prefix(),
  237 + ?assertEqual([{[P, riak_core, service_vnodes_running], 5},
  238 + {[P, riak_core, service_vnodeq],
  239 + [{min, 0}, {median, 1}, {mean, 2}, {max, 5}, {total, 10}]}],
242 240 vnodeq_aggregate(service_vnode, [1, 0, 5, 0, 4])).
243 241
244 242 vnodeq_aggregate_even2_test() ->
245   - ?assertEqual([{service_vnodes_running, 2},
246   - {service_vnodeq_min, 10},
247   - {service_vnodeq_median, 15},
248   - {service_vnodeq_mean, 15},
249   - {service_vnodeq_max, 20},
250   - {service_vnodeq_total, 30}],
  243 + P = prefix(),
  244 + ?assertEqual([{[P, riak_core, service_vnodes_running], 2},
  245 + {[P, riak_core, service_vnodeq],
  246 + [{min, 10}, {median, 15}, {mean, 15}, {max, 20}, {total, 30}]}],
251 247 vnodeq_aggregate(service_vnode, [10, 20])).
252 248
253 249 vnodeq_aggregate_even4_test() ->
254   - ?assertEqual([{service_vnodes_running, 4},
255   - {service_vnodeq_min, 0},
256   - {service_vnodeq_median, 5},
257   - {service_vnodeq_mean, 7},
258   - {service_vnodeq_max, 20},
259   - {service_vnodeq_total, 30}],
  250 + P = prefix(),
  251 + ?assertEqual([{[P, riak_core, service_vnodes_running], 4},
  252 + {[P, riak_core, service_vnodeq]
  253 + [{min, 0}, {median, 5}, {mean, 7}, {max, 20}, {total, 30}]}],
260 254 vnodeq_aggregate(service_vnode, [0, 10, 0, 20])).
261 255
262 256 -endif.
243 src/riak_core_stat_cache.erl
@@ -30,28 +30,23 @@
30 30 -behaviour(gen_server).
31 31
32 32 %% API
33   --export([start_link/0, get_stats/1, register_app/2, register_app/3,
34   - clear_cache/1, stop/0]).
  33 +-export([start_link/0, get_stats/1, register_app/2, stop/0]).
35 34
36 35 %% gen_server callbacks
37 36 -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
38 37 terminate/2, code_change/3]).
39 38
40   --type registered_app() :: {MFA::{module(), atom(), [term()]}, RerfreshRateMillis::non_neg_integer()}.
  39 +-type registered_app() :: MFA::mfa().
41 40
42 41 -ifdef(TEST).
43 42 -include_lib("eunit/include/eunit.hrl").
44 43 -endif.
45 44
46 45 -define(SERVER, ?MODULE).
47   -%% @doc Cache item refresh rate in seconds
48   --define(REFRESH_RATE, 1).
49   --define(REFRSH_MILLIS(N), timer:seconds(N)).
50   --define(MAX_REFRESH, timer:seconds(60)).
51 46 -define(ENOTREG(App), {error, {not_registered, App}}).
52   --define(DEFAULT_REG(Mod, RefreshRateMillis), {{Mod, produce_stats, []}, RefreshRateMillis}).
  47 +-define(DEFAULT_REG(Mod), {Mod, produce_stats, []}).
53 48
54   --record(state, {tab, active=orddict:new(), apps=orddict:new()}).
  49 +-record(state, {active=orddict:new(), apps=orddict:new()}).
55 50
56 51 %%%===================================================================
57 52 %%% API
@@ -61,17 +56,15 @@ start_link() ->
61 56 gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
62 57
63 58 register_app(App, {M, F, A}) ->
64   - RefreshRate = app_helper:get_env(riak_core, stat_cache_ttl, ?REFRESH_RATE),
65   - register_app(App, {M, F, A}, RefreshRate).
66   -
67   -register_app(App, {M, F, A}, RefreshRateSecs) ->
68   - gen_server:call(?SERVER, {register, App, {{M, F, A}, ?REFRSH_MILLIS(RefreshRateSecs)}}, infinity).
  59 + gen_server:call(?SERVER, {register, App, {M, F, A}}, infinity).
69 60
70 61 get_stats(App) ->
71   - gen_server:call(?SERVER, {get_stats, App}, infinity).
72   -
73   -clear_cache(App) ->
74   - gen_server:call(?SERVER, {clear, App}, infinity).
  62 + case gen_server:call(?SERVER, {get_stats_mfa, App}) of
  63 + {ok, MFA} ->
  64 + do_get_stats(App, MFA);
  65 + Error ->
  66 + Error
  67 + end.
75 68
76 69 stop() ->
77 70 gen_server:cast(?SERVER, stop).
@@ -80,92 +73,37 @@ stop() ->
80 73
81 74 init([]) ->
82 75 process_flag(trap_exit, true),
83   - Tab = ets:new(?MODULE, [protected, set, named_table]),
84   - RefreshRateSecs = app_helper:get_env(riak_core, stat_cache_ttl, ?REFRESH_RATE),
85   - RefreshRateMillis = ?REFRSH_MILLIS(RefreshRateSecs),
86 76 %% re-register mods, if this is a restart after a crash
87 77 RegisteredMods = lists:foldl(fun({App, Mod}, Registered) ->
88   - register_mod(App, ?DEFAULT_REG(Mod, RefreshRateMillis), Registered) end,
  78 + register_mod(App, ?DEFAULT_REG(Mod), Registered) end,
89 79 orddict:new(),
90 80 riak_core:stat_mods()),
91   - {ok, #state{tab=Tab, apps=orddict:from_list(RegisteredMods)}}.
  81 + {ok, #state{apps=orddict:from_list(RegisteredMods)}}.
92 82
93   -handle_call({register, App, {MFA, RefreshRateMillis}}, _From, State0=#state{apps=Apps0}) ->
  83 +handle_call({register, App, MFA}, _From, State0=#state{apps=Apps0}) ->
94 84 Apps = case registered(App, Apps0) of
95 85 false ->
96   - register_mod(App,{MFA, RefreshRateMillis}, Apps0);
  86 + register_mod(App, MFA, Apps0);
97 87 {true, _} ->
98 88 Apps0
99 89 end,
100 90 {reply, ok, State0#state{apps=Apps}};
101   -handle_call({get_stats, App}, From, State0=#state{apps=Apps, active=Active0, tab=Tab}) ->
102   - Reply = case registered(App, Apps) of
103   - false ->
104   - {reply, ?ENOTREG(App), State0};
105   - {true, {MFA, _RefreshRateMillis}} ->
106   - case cache_get(App, Tab) of
107   - miss ->
108   - Active = maybe_get_stats(App, From, Active0, MFA),
109   - {noreply, State0#state{active=Active}};
110   - {hit, Stats, TS} ->
111   - FreshnessStat = make_freshness_stat(App, TS),
112   - {reply, {ok, [FreshnessStat | Stats], TS}, State0}
113   - end
114   - end,
115   - Reply;
116   -handle_call({clear, App}, _From, State=#state{apps=Apps, tab=Tab}) ->
  91 +handle_call({get_stats_mfa, App}, _From, State0=#state{apps=Apps}) ->
117 92 case registered(App, Apps) of
118   - {true, _} ->
119   - true = ets:delete(Tab, App);
120   - _ -> ok
121   - end,
122   - {reply, ok, State};
  93 + false ->
  94 + {reply, ?ENOTREG(App), State0};
  95 + {true, MFA} ->
  96 + {reply, {ok, MFA}, State0}
  97 + end;
123 98 handle_call(_Request, _From, State) ->
124 99 Reply = ok,
125 100 {reply, Reply, State}.
126 101
127   -%% @doc call back from process executig the stat calculation
128   -handle_cast({stats, App, Stats0, TS}, State0=#state{tab=Tab, active=Active, apps=Apps}) ->
129   - %% @TODO standardise stat mods return type with a behaviour
130   - Stats = case Stats0 of
131   - {App, Stats1} -> Stats1;
132   - Stats1 -> Stats1
133   - end,
134   - ets:insert(Tab, {App, TS, Stats}),
135   - State = case orddict:find(App, Active) of
136   - {ok, {_Pid, Awaiting}} ->
137   - _ = [gen_server:reply(From, {ok, [make_freshness_stat(App, TS) |Stats], TS}) || From <- Awaiting, From /= ?SERVER],
138   - State0#state{active=orddict:erase(App, Active)};
139   - error ->
140   - State0
141   - end,
142   - {ok, {MFA, RefreshRateMillis}} = orddict:find(App, Apps),
143   - schedule_get_stats(RefreshRateMillis, App, MFA),
144   - Apps2 = clear_fail_count(App, Apps),
145   - {noreply, State#state{apps=Apps2}};
146 102 handle_cast(stop, State) ->
147 103 {stop, normal, State};
148 104 handle_cast(_Msg, State) ->
149 105 {noreply, State}.
150 106
151   -%% don't let a crashing stat mod crash the cache
152   -handle_info({'EXIT', FromPid, Reason}, State0=#state{active=Active, apps=Apps}) when Reason /= normal ->
153   - Reply = case awaiting_for_pid(FromPid, Active) of
154   - not_found ->
155   - {stop, Reason, State0};
156   - {ok, {App, Awaiting}} ->
157   - _ = [gen_server:reply(From, {error, Reason}) || From <- Awaiting, From /= ?SERVER],
158   - {ok, {MFA, RefreshRateMillis}} = orddict:find(App, Apps),
159   - Apps2 = update_fail_count(App, Apps),
160   - FailCnt = get_fail_count(App, Apps2),
161   - schedule_get_stats(RefreshRateMillis, App, MFA, FailCnt),
162   - {noreply, State0#state{active=orddict:erase(App, Active), apps=Apps2}}
163   - end,
164   - Reply;
165   -%% @doc callback on timer timeout to keep cache fresh
166   -handle_info({get_stats, {App, MFA}}, State) ->
167   - Active = maybe_get_stats(App, ?SERVER, State#state.active, MFA),
168   - {noreply, State#state{active=Active}};
169 107 handle_info(_Info, State) ->
170 108 {noreply, State}.
171 109
@@ -175,48 +113,12 @@ terminate(_Reason, _State) ->
175 113 code_change(_OldVsn, State, _Extra) ->
176 114 {ok, State}.
177 115
178   -%% internal
179   -get_fail_count(App, Apps) ->
180   - case orddict:find([App, fail], Apps) of
181   - {ok, Cnt} ->
182   - Cnt;
183   - error ->
184   - 0
185   - end.
186   -
187   -clear_fail_count(App, Apps) ->
188   - orddict:erase([App, fail], Apps).
189   -
190   -update_fail_count(App, Apps) ->
191   - orddict:update_counter([App, fail], 1, Apps).
192   -
193   -schedule_get_stats(After, App, MFA) ->
194   - Pid = self(),
195   - erlang:send_after(After, Pid, {get_stats, {App, MFA}}).
196   -
197   -schedule_get_stats(After, Apps, MFA, 0) ->
198   - schedule_get_stats(After, Apps, MFA);
199   -schedule_get_stats(After, Apps, MFA, FailCnt) ->
200   - Millis = back_off(After, FailCnt),
201   - schedule_get_stats(Millis, Apps, MFA).
202   -
203   -back_off(After, FailCnt) ->
204   - min(After * (1 bsl FailCnt), ?MAX_REFRESH).
205   -
206   -make_freshness_stat(App, TS) ->
207   - {make_freshness_stat_name(App), TS}.
208   -
209   -make_freshness_stat_name(App) ->
210   - list_to_atom(atom_to_list(App) ++ "_stat_ts").
211   -
212 116 -spec register_mod(atom(), registered_app(), orddict:orddict()) -> orddict:orddict().
213   -register_mod(App, AppRegistration, Apps0) ->
214   - {{Mod, _, _}=MFA, RefreshRateMillis} = AppRegistration,
215   - ok = folsom_metrics:new_histogram({?MODULE, Mod}),
216   - ok = folsom_metrics:new_meter({?MODULE, App}),
217   - Apps = orddict:store(App, AppRegistration, Apps0),
218   - schedule_get_stats(RefreshRateMillis, App, MFA),
219   - Apps.
  117 +register_mod(App, {Mod, _, _} = MFA, Apps0) ->
  118 + P = riak_core_stat:prefix(),
  119 + exometer:new([P, ?MODULE, Mod], histogram),
  120 + exometer:new([P, ?MODULE, App], meter),
  121 + orddict:store(App, MFA, Apps0).
220 122
221 123 registered(App, Apps) ->
222 124 registered(orddict:find(App, Apps)).
@@ -226,74 +128,22 @@ registered(error) ->
226 128 registered({ok, Val}) ->
227 129 {true, Val}.
228 130
229   -cache_get(App, Tab) ->
230   - Res = case ets:lookup(Tab, App) of
231   - [] ->
232   - miss;
233   - [{App, TStamp, Stats}] ->
234   - {hit, Stats, TStamp}
235   - end,
236   - Res.
237   -
238   -maybe_get_stats(App, From, Active, MFA) ->
239   - %% if a get stats is not under way start one
240   - Awaiting = case orddict:find(App, Active) of
241   - error ->
242   - Pid = do_get_stats(App, MFA),
243   - {Pid, [From]};
244   - {ok, {Pid, Froms}} ->
245   - {Pid, [From|Froms]}
246   - end,
247   - orddict:store(App, Awaiting, Active).
248   -
249 131 do_get_stats(App, {M, F, A}) ->
250   - spawn_link(fun() ->
251   - Stats = folsom_metrics:histogram_timed_update({?MODULE, M}, M, F, A),
252   - ok = folsom_metrics:notify_existing_metric({?MODULE, App}, 1, meter),
253   - gen_server:cast(?MODULE, {stats, App, Stats, folsom_utils:now_epoch()})
254   - end).
255   -
256   -awaiting_for_pid(Pid, Active) ->
257   - case [{App, Awaiting} || {App, {Proc, Awaiting}} <- orddict:to_list(Active),
258   - Proc == Pid] of
259   - [] ->
260   - not_found;
261   - L -> {ok, hd(L)}
262   - end.
  132 + P = riak_core_stat:prefix(),
  133 + Stats = histogram_timed_update([P, ?MODULE, M], M, F, A),
  134 + exometer:update([P, ?MODULE, App], 1),
  135 + Stats.
  136 +
  137 +histogram_timed_update(Name, M, F, A) ->
  138 + {Time, Value} = timer:tc(M, F, A),
  139 + exometer:update(Name, Time),
  140 + Value.
263 141
264 142 -ifdef(TEST).
265 143
266 144 -define(MOCKS, [folsom_utils, riak_core_stat, riak_kv_stat]).
267 145 -define(STATS, [{stat1, 0}, {stat2, 1}, {stat3, 2}]).
268 146
269   -cached(App, Time) ->
270   - [make_freshness_stat(App, Time) | ?STATS].
271   -
272   -cache_test_() ->
273   - {setup,
274   - fun() ->
275   - folsom:start(),
276   - [meck:new(Mock, [non_strict, passthrough]) || Mock <- ?MOCKS],
277   - riak_core_stat_cache:start_link()
278   - end,
279   - fun(_) ->
280   - folsom:stop(),
281   - [meck:unload(Mock) || Mock <- ?MOCKS],
282   - riak_core_stat_cache:stop()
283   - end,
284   -
285   - [{"Register with the cache",
286   - fun register/0},
287   - {"Get cached value",
288   - fun get_cached/0},
289   - {"Expired cache, re-calculate",
290   - fun get_expired/0},
291   - {"Only a single process can calculate stats",
292   - fun serialize_calls/0},
293   - {"Crash test",
294   - fun crasher/0}
295   - ]}.
296   -
297 147 register() ->
298 148 [meck:expect(M, produce_stats, fun() -> ?STATS end)
299 149 || M <- [riak_core_stat, riak_kv_stat]],
@@ -301,32 +151,15 @@ register() ->
301 151 riak_core_stat_cache:register_app(riak_core, {riak_core_stat, produce_stats, []}, 5),
302 152 riak_core_stat_cache:register_app(riak_kv, {riak_kv_stat, produce_stats, []}, 5),
303 153 NonSuch = riak_core_stat_cache:get_stats(nonsuch),
304   - ?assertEqual({ok, cached(riak_core, Now), Now}, riak_core_stat_cache:get_stats(riak_core)),
305   - ?assertEqual({ok, cached(riak_kv, Now), Now}, riak_core_stat_cache:get_stats(riak_kv)),
  154 +
306 155 ?assertEqual(?ENOTREG(nonsuch), NonSuch),
307   - %% and check the cache has the correct values
308   - [?assertEqual([{App, Now, ?STATS}], ets:lookup(riak_core_stat_cache, App))
309   - || App <- [riak_core, riak_kv]],
  156 +
310 157 %% and that a meter and histogram has been registered for all registered modules
311 158 [?assertEqual([{{?MODULE, M}, [{type, histogram}]}], folsom_metrics:get_metric_info({?MODULE, M}))
312 159 || M <- [riak_core_stat, riak_kv_stat]],
313 160 [?assertEqual([{{?MODULE, App}, [{type, meter}]}], folsom_metrics:get_metric_info({?MODULE, App}))
314 161 || App <- [riak_core, riak_kv]].
315 162
316   -get_cached() ->
317   - Now = tick(1000, 0),
318   - [?assertEqual({ok, cached(riak_core, Now), Now}, riak_core_stat_cache:get_stats(riak_core))
319   - || _ <- lists:seq(1, 20)],
320   - ?assertEqual(1, meck:num_calls(riak_core_stat, produce_stats, [])).
321   -
322   -get_expired() ->
323   - CalcTime = 1000,
324   - _Expired = tick(CalcTime, ?REFRESH_RATE+?REFRESH_RATE),
325   - [?assertEqual({ok, cached(riak_core, CalcTime), CalcTime}, riak_core_stat_cache:get_stats(riak_core))
326   - || _ <- lists:seq(1, 20)],
327   - %% Stale stats should no longer trigger a stat calculation
328   - ?assertEqual(1, meck:num_calls(riak_core_stat, produce_stats, [])).
329   -
330 163 serialize_calls() ->
331 164 %% many processes can call get stats at once
332 165 %% they should not block the server
@@ -339,7 +172,6 @@ serialize_calls() ->
339 172 %% b) only one call to produce_stats
340 173 %% But ONLY in the case that the cache is empty. At any other time,
341 174 %% that cached answer should be returned.
342   - riak_core_stat_cache:clear_cache(riak_kv),
343 175 Procs = 20,
344 176 Then = 1000,
345 177 Now = tick(2000, 0),
@@ -367,7 +199,6 @@ serialize_calls() ->
367 199 ?assertEqual(2, meck:num_calls(riak_kv_stat, produce_stats, [])).
368 200
369 201 crasher() ->
370   - riak_core_stat_cache:clear_cache(riak_kv),
371 202 Pid = whereis(riak_core_stat_cache),
372 203 Then = tick(1000, 0),
373 204 %% Now = tick(10000, 0),
79 src/riak_core_stat_q.erl
@@ -23,9 +23,7 @@
23 23 %% A `Path' is a list of atoms | binaries. The module creates a set
24 24 %% of `ets:select/1' guards, one for each element in `Path'
25 25 %% For each stat that has a key that matches `Path' we calculate the
26   -%% current value and return it. This module makes use of
27   -%% `riak_core_stat_calc_proc'
28   -%% to cache and limit stat calculations.
  26 +%% current value and return it.
29 27
30 28 -module(riak_core_stat_q).
31 29
@@ -37,7 +35,7 @@
37 35 -type path() :: [] | [atom()|binary()].
38 36 -type stats() :: [stat()].
39 37 -type stat() :: {stat_name(), stat_value()}.
40   --type stat_name() :: tuple().
  38 +-type stat_name() :: list().
41 39 -type stat_value() :: integer() | [tuple()].
42 40
43 41 %% @doc To allow for namespacing, and adding richer dimensions, stats
@@ -53,80 +51,37 @@
53 51 %% in `Path' as a wild card.
54 52 -spec get_stats(path()) -> stats().
55 53 get_stats(Path) ->
56   - %% get all the stats that are at Path
57   - NamesNTypes = names_and_types(Path),
58   - calculate_stats(NamesNTypes).
59   -
60   -%% @doc queries folsom's metrics table for stats that match our path
61   -names_and_types(Path) ->
62   - Guards = guards_from_path(Path),
63   - ets:select(folsom, [{{'$1','$2'}, Guards,['$_']}]).
64   -
65   -guards_from_path(Path) ->
66   - SizeGuard = size_guard(length(Path)),
67   - %% Going to reverse it is why this way around
68   - Guards = [SizeGuard, {is_tuple, '$1'}],
69   - add_guards(Path, Guards, 1).
70   -
71   -add_guards([], Guards, _Cnt) ->
72   - lists:reverse(Guards);
73   -add_guards(['_'|Path], Guards, Cnt) ->
74   - add_guards(Path, Guards, Cnt+1);
75   -add_guards([Elem|Path], Guards, Cnt) ->
76   - add_guards(Path, [guard(Elem, Cnt) | Guards], Cnt+1).
77   -
78   -guard(Elem, Cnt) when Cnt > 0 ->
79   - {'==', {element, Cnt, '$1'}, Elem}.
80   -
81   --spec size_guard(pos_integer()) -> tuple().
82   -size_guard(N) ->
83   - {'>=', {size, '$1'}, N}.
  54 + exometer:get_values(Path).
  55 + %% %% get all the stats that are at Path
  56 + %% calculate_stats(exometer:select(
  57 + %% [{ {Path ++ '_','_',enabled}, [], ['$_'] }])).
84 58
85 59 calculate_stats(NamesAndTypes) ->
86   - [{Name, get_stat({Name, Type})} || {Name, {metric, _, Type, _}} <- NamesAndTypes].
  60 + [{Name, get_stat(Name)} || {Name, _, _} <- NamesAndTypes].
87 61
88 62 %% Create/lookup a cache/calculation process
89 63 get_stat(Stat) ->
90   - Pid = riak_core_stat_calc_sup:calc_proc(Stat),
91   - riak_core_stat_calc_proc:value(Pid).
92   -
93   -throw_folsom_error({error, _, _} = Err) ->
94   - throw(Err);
95   -throw_folsom_error(Other) -> Other.
  64 + exometer:get_value(Stat).
96 65
97   -%% Encapsulate getting a stat value from folsom.
  66 +%% Encapsulate getting a stat value from exometer.
98 67 %%
99 68 %% If for any reason we can't get a stats value
100 69 %% return 'unavailable'.
101 70 %% @TODO experience shows that once a stat is
102 71 %% broken it stays that way. Should we delete
103 72 %% stats that are broken?
104   -calc_stat({Name, gauge}) ->
105   - try
106   - GaugeVal = throw_folsom_error(folsom_metrics:get_metric_value(Name)),
107   - calc_gauge(GaugeVal)
108   - catch ErrClass:ErrReason ->
109   - log_error(Name, ErrClass, ErrReason),
110   - unavailable
111   - end;
112   -calc_stat({Name, histogram}) ->
113   - try
114   - throw_folsom_error(folsom_metrics:get_histogram_statistics(Name))
115   - catch ErrClass:ErrReason ->
116   - log_error(Name, ErrClass, ErrReason),
117   - unavailable
118   - end;
119   -calc_stat({Name, _Type}) ->
120   - try throw_folsom_error(folsom_metrics:get_metric_value(Name))
121   - catch ErrClass:ErrReason ->
122   - log_error(Name, ErrClass, ErrReason),
123   - unavailable
124   - end.
  73 +calc_stat({Name, _Type}) when is_tuple(Name) ->
  74 + stat_return(exometer:get_value([riak_core_stat:prefix()|tuple_to_list(Name)]));
  75 +calc_stat({[_|_] = Name, _Type}) ->
  76 + stat_return(exometer:get_value([riak_core_stat:prefix()|Name])).
  77 +
  78 +stat_return({error,not_found}) -> unavailable;
  79 +stat_return({ok, Value}) -> Value.
125 80
126 81 log_error(StatName, ErrClass, ErrReason) ->
127 82 lager:warning("Failed to calculate stat ~p with ~p:~p", [StatName, ErrClass, ErrReason]).
128 83
129   -%% some crazy people put funs in folsom gauges
  84 +%% some crazy people put funs in gauges (exometer has a 'function' metric)
130 85 %% so that they can have a consistent interface
131 86 %% to access stats from disperate sources
132 87 calc_gauge({function, Mod, Fun}) ->
3  src/riak_core_stat_sup.erl
@@ -48,8 +48,7 @@ start_link() ->
48 48 init([]) ->
49 49 Children = lists:flatten(
50 50 [?CHILD(folsom_sup, supervisor),
51   - ?CHILD(riak_core_stats_sup, supervisor),
52   - ?CHILD(riak_core_stat_calc_sup, supervisor)
  51 + ?CHILD(riak_core_stats_sup, supervisor)
53 52 ]),
54 53
55 54 {ok, {{rest_for_one, 10, 10}, Children}}.
41 src/riak_core_stat_xform.erl
... ... @@ -0,0 +1,41 @@
  1 +%% -------------------------------------------------------------------
  2 +%%
  3 +%% Copyright (c) 2007-2013 Basho Technologies, Inc. All Rights Reserved.
  4 +%%
  5 +%% This file is provided to you under the Apache License,
  6 +%% Version 2.0 (the "License"); you may not use this file
  7 +%% except in compliance with the License. You may obtain
  8 +%% a copy of the License at
  9 +%%
  10 +%% http://www.apache.org/licenses/LICENSE-2.0
  11 +%%
  12 +%% Unless required by applicable law or agreed to in writing,
  13 +%% software distributed under the License is distributed on an
  14 +%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15 +%% KIND, either express or implied. See the License for the
  16 +%% specific language governing permissions and limitations
  17 +%% under the License.
  18 +%%
  19 +%% -------------------------------------------------------------------
  20 +-module(riak_core_stat_xform).
  21 +
  22 +-export([parse_transform/2]).
  23 +
  24 +
  25 +parse_transform(Forms, _) ->
  26 + case os:getenv("RIAK_CORE_STAT_PREFIX") of
  27 + false ->
  28 + Forms;
  29 + Str ->
  30 + transform(Forms, list_to_atom(Str))
  31 + end.
  32 +
  33 +transform([{function,L,prefix,0,[_]}|T], Pfx) ->
  34 + [{function, L, prefix, 0,
  35 + [{clause, L, [], [], [{atom,L,Pfx}]}]}|T];
  36 +transform([H|T], Pfx) ->
  37 + [H | transform(T, Pfx)];
  38 +transform([], _) ->
  39 + [].
  40 +
  41 +

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.