Current section

Files

Jump to
cloudi_service_monitoring src cloudi_service_monitoring_erlang.erl
Raw

src/cloudi_service_monitoring_erlang.erl

%-*-Mode:erlang;coding:utf-8;tab-width:4;c-basic-offset:4;indent-tabs-mode:()-*-
% ex: set ft=erlang fenc=utf-8 sts=4 ts=4 sw=4 et nomod:
%%%
%%%------------------------------------------------------------------------
%%% @doc
%%% ==CloudI Erlang Metrics==
%%% All memory values are in bytes (unless "words" is explicitly specified).
%%% @end
%%%
%%% MIT License
%%%
%%% Copyright (c) 2015-2018 Michael Truog <mjtruog at protonmail dot com>
%%%
%%% Permission is hereby granted, free of charge, to any person obtaining a
%%% copy of this software and associated documentation files (the "Software"),
%%% to deal in the Software without restriction, including without limitation
%%% the rights to use, copy, modify, merge, publish, distribute, sublicense,
%%% and/or sell copies of the Software, and to permit persons to whom the
%%% Software is furnished to do so, subject to the following conditions:
%%%
%%% The above copyright notice and this permission notice shall be included in
%%% all copies or substantial portions of the Software.
%%%
%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
%%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
%%% DEALINGS IN THE SOFTWARE.
%%%
%%% @author Michael Truog <mjtruog at protonmail dot com>
%%% @copyright 2015-2018 Michael Truog
%%% @version 1.7.5 {@date} {@time}
%%%------------------------------------------------------------------------
-module(cloudi_service_monitoring_erlang).
-author('mjtruog at protonmail dot com').
%% external interface
-export([basic_update/0,
memory_init/1,
memory_update/1,
system_info_init/1,
system_info_update/1,
statistics_init/1,
statistics_update/1,
process_info_init/1,
process_info_update/1,
port_info_init/1,
port_info_update/1]).
%%%------------------------------------------------------------------------
%%% External interface functions
%%%------------------------------------------------------------------------
basic_update() ->
MetricValues0 = case erlang:whereis(error_logger) of
undefined ->
[];
ErrorLoggerPid ->
ErrorLoggerLength = element(2,
erlang:process_info(ErrorLoggerPid, message_queue_len)),
[metric(gauge, [error_logger, message_queue_len],
ErrorLoggerLength)]
end,
MetricValues1 = case erlang:whereis(logger_proxy) of
undefined ->
MetricValues0;
LoggerProxyPid ->
LoggerProxyLength = element(2,
erlang:process_info(LoggerProxyPid, message_queue_len)),
[metric(gauge, [logger_proxy, message_queue_len],
LoggerProxyLength) | MetricValues0]
end,
MetricValuesN = case erlang:whereis(logger) of
undefined ->
MetricValues1;
LoggerPid ->
LoggerLength = element(2,
erlang:process_info(LoggerPid, message_queue_len)),
[metric(gauge, [logger, message_queue_len],
LoggerLength) | MetricValues1]
end,
[metric(gauge, [code, modules],
erlang:length(code:all_loaded())),
metric(gauge, [ets, tables],
erlang:length(ets:all())) | MetricValuesN].
memory_init(Options) ->
Options.
memory_update([]) ->
{[], []};
memory_update(Options) ->
Memory = erlang:memory(),
MetricValues = [metric(gauge, memory_value(Option, Memory))
|| Option <- Options],
{MetricValues, Options}.
system_info_init(Options) ->
[system_info_value(Option) || Option <- Options],
Options.
system_info_update(Options) ->
MetricValues = [metric(gauge, system_info_value(Option))
|| Option <- Options],
{MetricValues, Options}.
statistics_init(Options) ->
case lists:member(scheduler_wall_time, Options) of
true ->
_ = erlang:system_flag(scheduler_wall_time, true);
false ->
ok
end,
[statistics_value(Option) || Option <- Options].
statistics_update(EntriesOld) ->
statistics_update(EntriesOld, [], []).
process_info_init(Options) ->
Options.
process_info_update([]) ->
{[], []};
process_info_update(Options) ->
Processes = erlang:processes(),
MetricValues = [metric(gauge, process_info_value(Option, Processes))
|| Option <- Options],
{MetricValues, Options}.
port_info_init(Options) ->
Options.
port_info_update([]) ->
{[], []};
port_info_update(Options) ->
Ports = erlang:ports(),
MetricValues = [metric(gauge, port_info_value(Option, Ports))
|| Option <- Options],
{MetricValues, Options}.
%%%------------------------------------------------------------------------
%%% Private functions
%%%------------------------------------------------------------------------
memory_value(Option, Memory) ->
{_, Value} = lists:keyfind(Option, 1, Memory),
{Option, Value}.
system_info_value(Option)
when (Option =:= dirty_cpu_schedulers) orelse
(Option =:= dirty_cpu_schedulers_online) orelse
(Option =:= dirty_io_schedulers) ->
Value = try erlang:system_info(Option)
catch
error:badarg ->
0
end,
{Option, Value};
system_info_value(Option)
when (Option =:= logical_processors) orelse
(Option =:= logical_processors_available) orelse
(Option =:= logical_processors_online) ->
Value = case erlang:system_info(Option) of
unknown ->
0;
Count when is_integer(Count) ->
Count
end,
{Option, Value};
system_info_value(Option) ->
Value = erlang:system_info(Option),
true = is_integer(Value),
{Option, Value}.
statistics_value(context_switches) ->
{ContextSwitches, _} = erlang:statistics(context_switches),
{context_switches, ContextSwitches};
statistics_value(garbage_collection) ->
GC = {_GCs, _WordsReclaimed, _} = erlang:statistics(garbage_collection),
{garbage_collection, GC};
statistics_value(io) ->
{{input, TotalBytesIn},
{output, TotalBytesOut}} = erlang:statistics(io),
{io, {TotalBytesIn, TotalBytesOut}};
statistics_value(scheduler_wall_time) ->
{scheduler_wall_time, lists:sort(erlang:statistics(scheduler_wall_time))};
statistics_value(reductions) ->
{TotalReductions, _} = erlang:statistics(reductions),
{reductions, TotalReductions};
statistics_value(Option) ->
Value = erlang:statistics(Option),
true = is_integer(Value),
{Option, Value}.
statistics_value_update(context_switches, ContextSwitchesOld) ->
EntryNew = {_, ContextSwitchesNew} = statistics_value(context_switches),
{[metric(spiral, [context_switches],
ContextSwitchesNew - ContextSwitchesOld)],
EntryNew};
statistics_value_update(garbage_collection,
{GCsOld, WordsReclaimedOld, _}) ->
EntryNew = {_, ValueNew} = statistics_value(garbage_collection),
{GCsNew, WordsReclaimedNew, _} = ValueNew,
{[metric(spiral, [garbage_collection, count],
GCsNew - GCsOld),
metric(spiral, [garbage_collection, words_reclaimed],
WordsReclaimedNew - WordsReclaimedOld)],
EntryNew};
statistics_value_update(io, {TotalBytesInOld, TotalBytesOutOld}) ->
EntryNew = {_, ValueNew} = statistics_value(io),
{TotalBytesInNew, TotalBytesOutNew} = ValueNew,
{[metric(spiral, [io, input], TotalBytesInNew - TotalBytesInOld),
metric(spiral, [io, output], TotalBytesOutNew - TotalBytesOutOld)],
EntryNew};
statistics_value_update(scheduler_wall_time, SchedulersOld) ->
EntryNew = {_, SchedulersNew} = statistics_value(scheduler_wall_time),
MetricValues = lists:foldl(fun({SchedulerId,
ActiveTimeOld, TotalTimeOld}, L) ->
case lists:keyfind(SchedulerId, 1, SchedulersNew) of
{_, ActiveTimeNew, TotalTimeNew} ->
[metric(histogram,
[scheduler_wall_time,
erlang:integer_to_list(SchedulerId), active],
ActiveTimeNew - ActiveTimeOld),
metric(histogram,
[scheduler_wall_time,
erlang:integer_to_list(SchedulerId), total],
TotalTimeNew - TotalTimeOld) | L];
false ->
L
end
end, [], SchedulersOld),
{MetricValues, EntryNew};
statistics_value_update(reductions, TotalReductionsOld) ->
EntryNew = {_, TotalReductionsNew} = statistics_value(reductions),
{[metric(spiral, [reductions],
TotalReductionsNew - TotalReductionsOld)], EntryNew};
statistics_value_update(Option, _) ->
EntryNew = statistics_value(Option),
{[metric(gauge, EntryNew)], EntryNew}.
statistics_update([], EntriesNew, MetricValues) ->
{MetricValues, EntriesNew};
statistics_update([{Option, ValueOld} | EntriesOld],
EntriesNew, MetricValues) ->
{MetricValueL, EntryNew} = statistics_value_update(Option, ValueOld),
statistics_update(EntriesOld,
[EntryNew | EntriesNew], MetricValueL ++ MetricValues).
process_info_value(Option, Processes)
when Option =:= message_queue_len ->
% if more options are added, provide them as
% a list to erlang:process_info/2
TotalCount = lists:foldl(fun(Pid, I) ->
case erlang:process_info(Pid, Option) of
{Option, Count} when is_integer(Count) ->
I + Count;
undefined ->
I
end
end, 0, Processes),
{Option, TotalCount}.
port_info_value(Option, Ports)
when (Option =:= memory) orelse
(Option =:= queue_size) ->
TotalCount = lists:foldl(fun(Port, I) ->
case erlang:port_info(Port, Option) of
{Option, Count} when is_integer(Count) ->
I + Count;
undefined ->
I
end
end, 0, Ports),
{Option, TotalCount}.
metric(gauge, {Option, Value})
when is_atom(Option) ->
{gauge, [Option], Value}.
metric(Type, [_ | _] = Name, Value)
when (Type =:= gauge) orelse
(Type =:= spiral) orelse
(Type =:= histogram) ->
{Type, Name, Value}.