Skip to content

Commit

Permalink
add metrics collector for BEAM VM
Browse files Browse the repository at this point in the history
  • Loading branch information
RoadRunnr committed Mar 13, 2024
1 parent 3f13bc8 commit 6e33e2f
Show file tree
Hide file tree
Showing 9 changed files with 943 additions and 0 deletions.
20 changes: 20 additions & 0 deletions instrumentation/opentelemetry_beam/.gitignore
@@ -0,0 +1,20 @@
.rebar3
_build
_checkouts
_vendor
.eunit
*.o
*.beam
*.plt
*.swp
*.swo
.erlang.cookie
ebin
log
erl_crash.dump
.rebar
logs
.idea
*.iml
rebar3.crashdump
*~
17 changes: 17 additions & 0 deletions instrumentation/opentelemetry_beam/README.md
@@ -0,0 +1,17 @@
# opentelemetry_beam

Metrics collector to export statistics from the BEAM VM.

The metrics and their names are heavily borrowed from [prometheus.erl](https://github.com/deadtrickster/prometheus.erl).

After installing, setup the desired metrics in your application behaviour before your
top-level supervisor starts. Make sure the API and SDK applications are started before
your application.

```erlang
opentelemetry_vm_memory:setup(),
opentelemetry_vm_msacc:setup(),
opentelemetry_vm_statistics:setup(),
opentelemetry_vm_system_info:setup(),
...
```
30 changes: 30 additions & 0 deletions instrumentation/opentelemetry_beam/priv/dev.config
@@ -0,0 +1,30 @@
%%-*-Erlang-*-
[{kernel,
[{logger_level, debug},
{logger,
[{handler, default, logger_std_h,
#{level => debug,
formatter =>
{logger_formatter,
#{single_line => true,
legacy_header => false,
template => [time," ",pid," ",level,": ",msg,"\n"]
}},
config =>
#{sync_mode_qlen => 10000,
drop_mode_qlen => 10000,
flush_qlen => 10000}
}
}
]}
]},

{opentelemetry_experimental,
[{readers,
[
#{module => otel_metric_reader_periodic,
config => #{export_interval_ms => 1000,
exporter => {otel_metric_exporter_console, undefined}}}
]}
]}
].
15 changes: 15 additions & 0 deletions instrumentation/opentelemetry_beam/rebar.config
@@ -0,0 +1,15 @@
%%-*-Erlang-*-
{erl_opts, [debug_info]}.
{deps, [
{opentelemetry_api, "~> 1.2.2"},
{opentelemetry_api_experimental, "~> 0.4.0"}
]}.

{plugins, [rebar3_fmt]}.

{xref_checks, [undefined_function_calls, undefined_functions,
deprecated_function_calls, deprecated_functions]}.
{xref_ignores, []}.

%% development setting
{shell, [{config, "priv/dev.config"}]}.
15 changes: 15 additions & 0 deletions instrumentation/opentelemetry_beam/src/opentelemetry_beam.app.src
@@ -0,0 +1,15 @@
{application, opentelemetry_beam,
[{description, "OpenTelemetry BEAM VM Instrumentation"},
{vsn, "0.1.0"},
{registered, []},
{applications,
[kernel,
stdlib,
opentelemetry_api,
opentelemetry_api_experimental
]},
{env,[]},
{modules, []},
{licenses, ["Apache-2.0"]},
{links, []}
]}.
136 changes: 136 additions & 0 deletions instrumentation/opentelemetry_beam/src/opentelemetry_vm_memory.erl
@@ -0,0 +1,136 @@
%% Copyright 2024, OpenTelemetry Authors
%%
%% The documentation and some small code fragments are taken
%% from the prometheus.erl project which is covered by the
%% MIT License and Copyright (c) 2016, Ilya Khaprov <dead.trickster@gmail.com>.
%%
%% @doc
%% Collects information about memory dynamically allocated
%% by the Erlang emulator using
%% <a href="http://erlang.org/doc/man/erlang.html#memory-0">
%% erlang:memory/0
%% </a>, also provides basic (D)ETS statistics.
%%
%% ==Exported metrics==
%% <ul>
%% <li>
%% `erlang.vm_memory.atoms{usage="free|used"}'<br/>
%% Type: gauge.<br/>
%% Unit: Bytes.<br/>
%% The total amount of memory currently allocated for atoms.
%% This memory is part of the memory presented as system memory.
%% </li>
%% <li>
%% `erlang.vm_memory.allocated{kind="system|processes"}'<br/>
%% Type: gauge.<br/>
%% Unit: Bytes.<br/>
%% The total amount of memory currently allocated.
%% This is the same as the sum of the memory size for processes and system.
%% </li>
%% <li>
%% `erlang.vm_memory.dets.tables'<br/>
%% Type: gauge.<br/>
%% Erlang VM DETS Tables count.
%% </li>
%% <li>
%% `erlang.vm_memory.ets.tables'<br/>
%% Type: gauge.<br/>
%% Erlang VM ETS Tables count.
%% </li>
%% <li>
%% `erlang.vm_memory.processes{usage="free|used"}'<br/>
%% Type: gauge.<br/>
%% The total amount of memory currently allocated for the Erlang processes.
%% </li>
%% <li>
%% `erlang.vm_memory.system{usage="atom|binary|code|ets|other"}'
%% <br/>
%% Type: gauge.<br/>
%% The total amount of memory currently allocated for the emulator
%% that is not directly related to any Erlang process.
%% Memory presented as processes is not included in this memory.
%% </li>
%% </ul>
%%
%% @end
-module(opentelemetry_vm_memory).

-export([setup/0, setup/1]).

-include_lib("opentelemetry_api_experimental/include/otel_meter.hrl").

%%%===================================================================
%%% API
%%%===================================================================

setup() ->
setup(?current_meter).

setup(Meter) ->
%% this will fail if not all erts_alloc(3) allocators are enabled
erlang:memory(),

Instruments =
[otel_meter:create_observable_gauge(
Meter, 'erlang.vm.memory.atoms',
#{description => <<"The total amount of memory currently allocated "
"for atoms. This memory is part of the memory "
"presented as system memory.">>,
unit => <<"By">>}),
otel_meter:create_observable_gauge(
Meter, 'erlang.vm.memory.allocated',
#{description => <<"The total amount of memory currently allocated. "
"This is the same as the sum of the memory size "
"for processes and system.">>,
unit => <<"By">>}),
otel_meter:create_observable_gauge(
Meter, 'erlang.vm.memory.processes',
#{description => <<"The total amount of memory currently allocated "
"for the Erlang processes.">>,
unit => <<"By">>}),
otel_meter:create_observable_gauge(
Meter, 'erlang.vm.memory.system',
#{description => <<"The total amount of memory currently allocated "
"for the emulator that is not directly related "
"to any Erlang process. Memory presented as processes "
"is not included in this memory.">>,
unit => <<"By">>})],
otel_meter:register_callback(Meter, Instruments, fun vm_memory_gauges/1, []),

otel_meter:create_observable_gauge(
Meter, 'erlang.vm.memory.dets.tables',
fun(_) -> [{length(dets:all()), #{}}] end, [],
#{description => <<"Erlang VM DETS Tables count.">>}),
otel_meter:create_observable_gauge(
Meter, 'erlang.vm.memory.ets.tables',
fun(_) -> [{length(ets:all()), #{}}] end, [],
#{description => <<"Erlang VM ETS Tables count.">>}),
ok.

%%%===================================================================
%%% Internal functions
%%%===================================================================

-define(gv(Key, PropList), proplists:get_value(Key, PropList)).

memory_other(Data) ->
?gv(system, Data) - ?gv(atom, Data) - ?gv(binary, Data) - ?gv(code, Data) - ?gv(ets, Data).

vm_memory_gauges(_) ->
Data = erlang:memory(),

[{'erlang.vm.memory.atoms',
[{?gv(atom_used, Data), #{usage => used}},
{?gv(atom, Data) - ?gv(atom_used, Data), #{usage => free}}]},
{'erlang.vm.memory.allocated',
[{?gv(system, Data), #{kind => system}},
{?gv(processes, Data), #{kind => processes}}]},
{'erlang.vm.memory.processes',
[{?gv(processes_used, Data), #{usage => used}},
{?gv(processes, Data) - ?gv(processes_used, Data), #{usage => free}}]},
{'erlang.vm.memory.system',
[{?gv(atom, Data), #{usage => atom}},
{?gv(binary, Data), #{usage => binary}},
{?gv(code, Data), #{usage => code}},
{?gv(ets, Data), #{usage => ets}},
{memory_other(Data), #{usage => other}}]}].

0 comments on commit 6e33e2f

Please sign in to comment.