%-*-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 %%% %%% BSD LICENSE %%% %%% Copyright (c) 2015, Michael Truog %%% All rights reserved. %%% %%% Redistribution and use in source and binary forms, with or without %%% modification, are permitted provided that the following conditions are met: %%% %%% * Redistributions of source code must retain the above copyright %%% notice, this list of conditions and the following disclaimer. %%% * Redistributions in binary form must reproduce the above copyright %%% notice, this list of conditions and the following disclaimer in %%% the documentation and/or other materials provided with the %%% distribution. %%% * All advertising materials mentioning features or use of this %%% software must display the following acknowledgment: %%% This product includes software developed by Michael Truog %%% * The name of the author may not be used to endorse or promote %%% products derived from this software without specific prior %%% written permission %%% %%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND %%% CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, %%% INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES %%% OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE %%% DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR %%% CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, %%% SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, %%% BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR %%% SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS %%% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, %%% WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING %%% NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE %%% OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH %%% DAMAGE. %%% %%% @author Michael Truog %%% @copyright 2015 Michael Truog %%% @version 1.5.1 {@date} {@time} %%%------------------------------------------------------------------------ -module(cloudi_service_monitoring_erlang). -author('mjtruog [at] gmail (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() -> {_, ErrorLoggerN} = erlang:process_info(whereis(error_logger), message_queue_len), [metric(gauge, [code, modules], erlang:length(code:all_loaded())), metric(gauge, [ets, tables], erlang:length(ets:all())), metric(gauge, [error_logger, message_queue_len], ErrorLoggerN)]. 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}.