1%%%
2%%% Copyright 2011, Boundary
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
17
18%%%-------------------------------------------------------------------
19%%% File:      folsom_vm_metrics.erl
20%%% @author    joe williams <j@boundary.com>
21%%% @doc
22%%% convert erlang system metrics to proplists
23%%% @end
24%%%-----------------------------------------------------------------
25
26-module(folsom_vm_metrics).
27
28-export([get_system_info/0,
29         get_statistics/0,
30         get_memory/0,
31         get_process_info/0,
32         get_port_info/0,
33         get_ets_info/0,
34         get_dets_info/0
35        ]).
36
37%% exported for eunit test
38-export([convert_system_info/2]).
39
40-include("folsom.hrl").
41
42
43% api
44
45get_memory() ->
46    erlang:memory().
47
48get_statistics() ->
49    [{Key, convert_statistics(Key, get_statistics(Key))} || Key <- ?STATISTICS].
50
51get_system_info() ->
52    [{Key, convert_system_info(Key, get_system_info(Key))} || Key <- ?SYSTEM_INFO].
53
54get_process_info() ->
55    [{pid_port_fun_to_atom(Pid), get_process_info(Pid)} || Pid <- processes()].
56
57get_port_info() ->
58    [{pid_port_fun_to_atom(Port), get_port_info(Port)} || Port <- erlang:ports()].
59
60get_ets_info() ->
61    [{Tab, get_ets_dets_info(ets, Tab)} || Tab <- ets:all()].
62
63get_dets_info() ->
64    [{Tab, get_ets_dets_info(dets, Tab)} || Tab <- dets:all()].
65
66% internal functions
67
68% wrap system_info and statistics in a try/catch in case keys are missing
69% in old/new versions of erlang
70
71get_system_info(Key) ->
72    try erlang:system_info(Key) catch
73                                    error:badarg->undefined
74                                end.
75
76get_statistics(Key) ->
77    try erlang:statistics(Key) catch
78                                   error:badarg->undefined
79                               end.
80
81%% conversion functions for erlang:statistics(Key)
82
83convert_statistics(context_switches, {ContextSwitches, 0}) ->
84    ContextSwitches;
85convert_statistics(garbage_collection, {NumberofGCs, WordsReclaimed, 0}) ->
86    [{number_of_gcs, NumberofGCs}, {words_reclaimed, WordsReclaimed}];
87convert_statistics(io, {Input, Output}) ->
88    [Input, Output];
89convert_statistics(reductions, {TotalReductions, ReductionsSinceLastCall}) ->
90    [{total_reductions, TotalReductions},
91     {reductions_since_last_call, ReductionsSinceLastCall}];
92convert_statistics(runtime, {TotalRunTime, TimeSinceLastCall}) ->
93    [{total_run_time, TotalRunTime}, {time_since_last_call, TimeSinceLastCall}];
94convert_statistics(wall_clock, {TotalWallclockTime, WallclockTimeSinceLastCall}) ->
95    [{total_wall_clock_time, TotalWallclockTime},
96     {wall_clock_time_since_last_call, WallclockTimeSinceLastCall}];
97convert_statistics(_, Value) ->
98    Value.
99
100%% conversion functions for erlang:system_info(Key)
101
102convert_system_info(allocated_areas, List) ->
103    [convert_allocated_areas(Value) || Value <- List];
104convert_system_info(allocator, {_,_,_,List}) ->
105    List;
106convert_system_info(c_compiler_used, {Compiler, Version}) ->
107    [{compiler, Compiler}, {version, convert_c_compiler_version(Version)}];
108convert_system_info(cpu_topology, undefined) ->
109    undefined;
110convert_system_info(cpu_topology, List) when is_list(List) ->
111    [{Type, convert_cpu_topology(Item, [])} || {Type, Item} <- List];
112convert_system_info(cpu_topology, {logical,Item}) ->
113    convert_system_info(cpu_topology, [{processor,[{core,{logical,Item}}]}]);
114convert_system_info(dist_ctrl, List) ->
115    lists:map(fun({Node, Socket}) ->
116                      {ok, Stats} = inet:getstat(Socket),
117                      {Node, Stats}
118              end, List);
119convert_system_info(driver_version, Value) ->
120    list_to_binary(Value);
121convert_system_info(machine, Value) ->
122    list_to_binary(Value);
123convert_system_info(otp_release, Value) ->
124    list_to_binary(Value);
125convert_system_info(scheduler_bindings, Value) ->
126    tuple_to_list(Value);
127convert_system_info(system_version, Value) ->
128    list_to_binary(Value);
129convert_system_info(system_architecture, Value) ->
130    list_to_binary(Value);
131convert_system_info(version, Value) ->
132    list_to_binary(Value);
133convert_system_info(_, Value) ->
134    Value.
135
136convert_allocated_areas({Key, Value1, Value2}) ->
137    {Key, [Value1, Value2]};
138convert_allocated_areas({Key, Value}) ->
139    {Key, Value}.
140
141convert_c_compiler_version({A, B, C}) ->
142    list_to_binary(io_lib:format("~p.~p.~p", [A, B, C]));
143convert_c_compiler_version({A, B}) ->
144    list_to_binary(io_lib:format("~p.~p", [A, B]));
145convert_c_compiler_version(A) ->
146    list_to_binary(io_lib:format("~p", [A])).
147
148convert_cpu_topology([{core, Value}| Tail], Acc) when is_tuple(Value) ->
149    convert_cpu_topology(Tail, lists:append(Acc, [{core, tuple_to_list(Value)}]));
150convert_cpu_topology([{core, Value}| Tail], Acc) when is_list(Value) ->
151    convert_cpu_topology(Tail, lists:append(Acc, [{core, convert_cpu_topology(Value, [])}]));
152convert_cpu_topology([{thread, Value}| Tail], Acc) ->
153    convert_cpu_topology(Tail, lists:append(Acc, [{thread, tuple_to_list(Value)}]));
154convert_cpu_topology([{node, Value}| Tail], Acc) ->
155    convert_cpu_topology(Tail, lists:append(Acc, [{node, convert_cpu_topology(Value, [])}]));
156convert_cpu_topology([{processor, Value}| Tail], Acc) ->
157    convert_cpu_topology(Tail, lists:append(Acc, [{processor, convert_cpu_topology(Value, [])}]));
158convert_cpu_topology({Key, Value}, _) ->
159    [{Key, Value}];
160convert_cpu_topology([], Acc) ->
161    Acc.
162
163get_process_info(Pid) ->
164    Info = [process_info(Pid, Key) || Key <- ?PROCESS_INFO],
165    lists:flatten([convert_pid_info(Item) || Item <- Info]).
166
167get_port_info(Port) ->
168    Stat = get_socket_getstat(Port),
169    SockName = get_socket_sockname(Port),
170    Opts = get_socket_opts(Port),
171    Info = get_erlang_port_info(Port),
172    Protocol = get_socket_protocol(Port),
173    Status = get_socket_status(Port),
174    Type = get_socket_type(Port),
175
176    lists:flatten(lists:append([
177                                Stat,
178                                SockName,
179                                Opts,
180                                Info,
181                                Protocol,
182                                Status,
183                                Type
184                               ])).
185
186get_socket_getstat(Socket) ->
187    case catch inet:getstat(Socket) of
188        {ok, Info} ->
189            Info;
190        _ ->
191            []
192    end.
193
194get_socket_status(Socket) ->
195    case catch prim_inet:getstatus(Socket) of
196        {ok, Status} ->
197            [{status, Status}];
198        _ ->
199         []
200    end.
201
202get_erlang_port_info(Port) ->
203    Info = erlang:port_info(Port),
204    [convert_port_info(Item) || Item <- Info].
205
206get_socket_type(Socket) ->
207    case catch prim_inet:gettype(Socket) of
208        {ok, Type} ->
209            [{type, tuple_to_list(Type)}];
210        _ ->
211         []
212    end.
213
214get_socket_opts(Socket) ->
215    [get_socket_opts(Socket, Key) || Key <- ?SOCKET_OPTS].
216
217get_socket_opts(Socket, Key) ->
218    case catch inet:getopts(Socket, [Key]) of
219        {ok, Opt} ->
220            Opt;
221        _ ->
222            []
223    end.
224
225get_socket_protocol(Socket) ->
226    case erlang:port_info(Socket, name) of
227        {name, "tcp_inet"} ->
228            [{protocol, tcp}];
229        {name, "udp_inet"} ->
230            [{protocol, udp}];
231        {name,"sctp_inet"} ->
232            [{protocol, sctp}];
233        _ ->
234            []
235    end.
236
237get_socket_sockname(Socket) ->
238    case catch inet:sockname(Socket) of
239        {ok, {Ip, Port}} ->
240            [{ip, ip_to_binary(Ip)}, {port, Port}];
241        _ ->
242            []
243    end.
244
245get_ets_dets_info(Type, Tab) ->
246    case Type:info(Tab) of
247        undefined -> [];
248        Entries when is_list(Entries) ->
249            [{Key, pid_port_fun_to_atom(Value)} || {Key, Value} <- Entries]
250    end.
251
252ip_to_binary(Tuple) ->
253    iolist_to_binary(string:join(lists:map(fun integer_to_list/1, tuple_to_list(Tuple)), ".")).
254
255convert_port_info({name, Name}) ->
256    {name, list_to_binary(Name)};
257convert_port_info({links, List}) ->
258    {links, [pid_port_fun_to_atom(Item) || Item <- List]};
259convert_port_info({connected, Pid}) ->
260    {connected, pid_port_fun_to_atom(Pid)};
261convert_port_info(Item) ->
262    Item.
263
264convert_pid_info({current_function, MFA}) ->
265    {current_function, tuple_to_list(MFA)};
266convert_pid_info({Key, Term}) when is_pid(Term) or is_port(Term) or is_function(Term) ->
267    {Key, pid_port_fun_to_atom(Term)};
268convert_pid_info({links, List}) ->
269    {links, [pid_port_fun_to_atom(Item) || Item <- List]};
270convert_pid_info({suspending, List}) ->
271    {suspending, [pid_port_fun_to_atom(Item) || Item <- List]};
272convert_pid_info({monitors, List}) ->
273    {monitors, [{Key, pid_port_fun_to_atom(Value)} || {Key, Value} <- List]};
274convert_pid_info({monitored_by, List}) ->
275    {monitored_by, [pid_port_fun_to_atom(Item) || Item <- List]};
276convert_pid_info({binary, List}) ->
277    {binary, [tuple_to_list(Item) || Item <- List]};
278convert_pid_info({initial_call, MFA}) ->
279    {inital_call, tuple_to_list(MFA)};
280convert_pid_info(Item) ->
281    Item.
282
283pid_port_fun_to_atom(Term) when is_pid(Term) ->
284    erlang:list_to_atom(pid_to_list(Term));
285pid_port_fun_to_atom(Term) when is_port(Term) ->
286    erlang:list_to_atom(erlang:port_to_list(Term));
287pid_port_fun_to_atom(Term) when is_function(Term) ->
288    erlang:list_to_atom(erlang:fun_to_list(Term));
289pid_port_fun_to_atom(Term) ->
290    Term.
291