├── .circleci └── config.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── pr2relnotes.sh ├── rebar.config ├── rebar.lock ├── src ├── vmstats.app.src ├── vmstats.erl ├── vmstats_server.erl ├── vmstats_sink.erl └── vmstats_sup.erl └── test ├── sample_sink.erl └── vmstats_server_tests.erl /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | orbs: 4 | rebar3: tsloughter/rebar3@0.4.0 5 | 6 | workflows: 7 | version: 2.1 8 | build_and_test: 9 | jobs: 10 | - rebar3/compile 11 | - rebar3/dialyzer: 12 | requires: 13 | - rebar3/compile 14 | - rebar3/eunit: 15 | requires: 16 | - rebar3/compile 17 | 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /ebin 2 | /deps 3 | /logs 4 | /log 5 | .DS_Store 6 | /test/*.beam 7 | *.swp 8 | erl_crash.dump 9 | /.eunit 10 | /.rebar 11 | /_build 12 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 2.4.0 4 | 5 | - [Allow passing options directly to server instead of setting global env](https://github.com/ferd/vmstats/pull/29) 6 | - [Add vmstats:child_spec/1 for external supervisors](https://github.com/ferd/vmstats/pull/22) 7 | - [Add missing field value in tests](https://github.com/ferd/vmstats/pull/28) 8 | - [Add vm_uptime function to vmstats](https://github.com/ferd/vmstats/pull/25) 9 | 10 | ## 2.3.1 11 | 12 | - OTP-21 Support (thanks to @hauleth) 13 | 14 | ## 2.3.0 15 | 16 | - Adding Atom counts (thanks to @Coffei and @lexmag) 17 | 18 | ## 2.2.0 19 | 20 | - Making memory metrics configurable (thanks to @gootik) 21 | 22 | ## 2.1.0 23 | 24 | - Adding port counts and port limit gauges 25 | 26 | ## 2.0.0 27 | 28 | - Removing the `statsderl` dependency. 29 | - Adding the `vmstats_sink` behaviour and making sinks configurable. Since version 2.0.0, `vmstats` doesn't rely on `statsderl` anymore: the sink metrics are written to is now configurable. A sink must implement the `vmstats_sink` behaviour. 30 | - Adding a `key_separator` option to decide the separator to use when composing metric keys. 31 | - Enabling scheduler wall time statistics by default. 32 | 33 | ## 1.0.0 34 | 35 | - Updating rebar. 36 | - Updating `statsderl` to 0.3.5. 37 | 38 | ## 0.2.3 39 | 40 | - Adding messages in queues metric, providing a global count of queued up messages in the system. 41 | 42 | ## 0.2.2 43 | 44 | - Adding garbage collection count per interval. 45 | - Adding words reclaimed in garbage collections per interval. 46 | - Adding reduction increment count per interval. 47 | - Adding IO data (bytes in and out) per interval. 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012, BLOOM Digital Platforms, Frederic Trottier-Hebert 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of the BLOOM Digital Platforms nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL BLOOM DIGITAL PLATFORMS BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vmstats 2 | 3 | [![CircleCI](https://circleci.com/gh/ferd/vmstats.svg?style=svg)](https://circleci.com/gh/ferd/vmstats) 4 | 5 | vmstats is a tiny Erlang app that gathers metrics on the Erlang VM and sends them to a configurable sink (e.g., StatsD). 6 | 7 | ## Features 8 | 9 | The different metrics that vmstats gathers include: 10 | - the `error_logger` queue length 11 | - the number of modules loaded 12 | - the number of processes 13 | - the process limit 14 | - the length of the run queue 15 | - the scheduler usage as a percentage (disabled by default) 16 | - memory used for ETS tables, atoms, processes, binaries and the total memory 17 | - garbage collection count per interval 18 | - words reclaimed in garbage collections per interval 19 | - reduction increment count per interval 20 | - IO data (bytes in and out) per interval 21 | - global amount of messages in queues on a node 22 | - the VM uptime 23 | 24 | ## Usage 25 | 26 | vmstats can be built using `rebar3`: 27 | 28 | ```sh 29 | $ rebar3 compile 30 | ``` 31 | 32 | Once you have vmstats set up, just add it to the list of applications to start 33 | in order to start gathering data. You'll need a sink (a module that implements 34 | the `vmstats_sink` behaviour) to send metrics to. 35 | 36 | ### Configuration 37 | 38 | The following is a list of the possible options for the configuration of the 39 | `vmstats` app: 40 | 41 | * `sink` - (module) a module that implements the `vmstats_sink` behaviour; vmstats metrics will be collected through this module. 42 | * `base_key` - (string) every metric name is prepended with this base key. Defaults to `"vmstats"`. 43 | * `key_separator` - (char) used as a separator between the parts of metric keys. Defaults to `$.`. 44 | * `interval` - (integer) the time (in milliseconds) between metric gatherings. Defaults to `1000` (1s). 45 | * `sched_time` - (boolean) whether to gather statistics about scheduler wall time. Defaults to `true`. 46 | * `memory_metrics` - (proplist of metric and key) what fields to collect statistics for. 47 | Available fields can be found [here](http://erlang.org/doc/man/erlang.html#memory-1). 48 | Default list is `[{total, total}, {processes_used, procs_used}, {atom_used, atom_used}, {binary, binary}, {ets, ets}]`. 49 | 50 | ### `vmstats_sink` behaviour 51 | 52 | vmstats sinks must implement the `vmstats_sink` behaviour. This behaviour only 53 | specifies one function: 54 | 55 | ```erlang 56 | -callback collect(Type :: counter | gauge | timing, 57 | Key :: iodata(), 58 | Value :: term()) -> ok. 59 | ``` 60 | 61 | ## I was basing myself on 'master' and stuff started breaking! 62 | 63 | That's because you should use tags for stable versions instead! The [changelog](CHANGELOG.md) should let you know what to expect. 64 | 65 | ## Contributing 66 | 67 | Make changes and be sure to test them (`$ rebar3 eunit`). 68 | 69 | ## Changelog 70 | 71 | See the [CHANGELOG.md file](CHANGELOG.md). 72 | 73 | ## License 74 | 75 | See the [license file](LICENSE). 76 | -------------------------------------------------------------------------------- /pr2relnotes.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | if [ -z $1 ] 4 | then 5 | echo "usage: $0 [pull-request-url]" 6 | exit 0 7 | fi 8 | export url=${2:-"https://github.com/ferd/vmstats/pull/"} 9 | 10 | git log --merges --pretty=medium $1..HEAD | \ 11 | awk -v url=$url ' 12 | # first line of a merge commit entry 13 | /^commit / {mode="new"} 14 | 15 | # merge commit default message 16 | mode=="new" && / +Merge pull request/ { 17 | page_id=substr($4, 2, length($4)-1); 18 | mode="started"; 19 | next; 20 | } 21 | 22 | # line of content including title 23 | mode=="started" && / [^ ]+/ { 24 | print "- [" substr($0, 5) "](" url page_id ")"; mode="done" 25 | }' 26 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | %% Erlang compiler options 2 | {erl_opts, [debug_info]}. 3 | {ct_opts, [{ct_hooks, [cth_surefire]}]}. 4 | -------------------------------------------------------------------------------- /rebar.lock: -------------------------------------------------------------------------------- 1 | []. 2 | -------------------------------------------------------------------------------- /src/vmstats.app.src: -------------------------------------------------------------------------------- 1 | {application, vmstats, [ 2 | {description, "Tiny application to gather VM statistics"}, 3 | {vsn, "2.4.0"}, 4 | {registered, [vmstats_sup, vmstats_server]}, 5 | {applications, [ 6 | kernel, 7 | stdlib 8 | ]}, 9 | {mod, {vmstats, []}}, 10 | {modules, [vmstats, vmstats_sup, vmstats_server]}, 11 | {env, [ 12 | {base_key, "vmstats"}, 13 | {interval, 1000}, % in milliseconds 14 | {key_separator, $.}, 15 | {sched_time, true}, 16 | {memory_metrics, [ 17 | {total, total}, 18 | {processes_used, procs_used}, 19 | {atom_used, atom_used}, 20 | {binary, binary}, 21 | {ets, ets} 22 | ]} 23 | ]}, 24 | {licenses, ["BSD-3"]}, 25 | {links, [{"GitHub", "https://github.com/ferd/vmstats"}]} 26 | ]}. 27 | -------------------------------------------------------------------------------- /src/vmstats.erl: -------------------------------------------------------------------------------- 1 | %%% vmstats is a tiny Erlang application to be used in conjuction with 2 | %%% client app in order to gather running statistics of a virtual machine 3 | %%% over its lifetime, helping diagnose or prevent problems in the long run. 4 | -module(vmstats). 5 | -behaviour(application). 6 | -export([start/2, stop/1, child_spec/2]). 7 | 8 | child_spec(Sink, BaseKey) -> 9 | {vmstats, 10 | {vmstats_server, start_link, [Sink, BaseKey]}, 11 | permanent, 12 | 1000, 13 | worker, 14 | [vmstats_server]}. 15 | 16 | start(normal, []) -> 17 | {ok, Sink} = application:get_env(vmstats, sink), 18 | vmstats_sup:start_link(Sink, base_key()). 19 | 20 | stop(_) -> 21 | ok. 22 | 23 | -spec base_key() -> term(). 24 | base_key() -> 25 | case application:get_env(vmstats, base_key) of 26 | {ok, V} -> V; 27 | undefined -> "vmstats" 28 | end. 29 | 30 | -------------------------------------------------------------------------------- /src/vmstats_server.erl: -------------------------------------------------------------------------------- 1 | %%% Main worker for vmstats. This module sits in a loop fired off with 2 | %%% timers with the main objective of routinely sending data to Sink. 3 | -module(vmstats_server). 4 | -behaviour(gen_server). 5 | %% Interface 6 | -export([start_link/2, start_link/3]). 7 | %% Internal Exports 8 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, 9 | code_change/3, terminate/2]). 10 | 11 | -define(TIMER_MSG, '#interval'). 12 | 13 | -record(state, {sink :: atom(), 14 | key :: string(), 15 | key_separator :: char() | iodata(), 16 | memory_metrics :: [{erlang:memory_type(), atom()}], 17 | sched_time=disabled :: enabled | disabled, 18 | prev_sched :: [{integer(), integer(), integer()}] | undefined, 19 | timer_ref :: reference(), 20 | interval :: integer(), % milliseconds 21 | prev_io :: {In::integer(), Out::integer()}, 22 | prev_gc :: {GCs::integer(), Words::integer()}}). 23 | %%% INTERFACE 24 | start_link(Sink, BaseKey) -> 25 | start_link(Sink, BaseKey, []). 26 | 27 | start_link(Sink, BaseKey, Options) -> 28 | gen_server:start_link(?MODULE, {Sink, BaseKey, Options}, []). 29 | 30 | %%% INTERNAL EXPORTS 31 | init({Sink, BaseKey, UserOptions}) -> 32 | Options = UserOptions ++ application:get_all_env(vmstats), 33 | {interval, Interval} = lists:keyfind(interval, 1, Options), 34 | {key_separator, KeySeparator} = lists:keyfind(key_separator, 1, Options), 35 | {sched_time, SchedTimeEnabled} = lists:keyfind(sched_time, 1, Options), 36 | {memory_metrics, MemoryMetrics} = lists:keyfind(memory_metrics, 1, Options), 37 | 38 | Ref = erlang:start_timer(Interval, self(), ?TIMER_MSG), 39 | {{input, In}, {output, Out}} = erlang:statistics(io), 40 | {GCs, Words, _} = erlang:statistics(garbage_collection), 41 | 42 | State = #state{key = [BaseKey, KeySeparator], 43 | key_separator = KeySeparator, 44 | memory_metrics = MemoryMetrics, 45 | sink = Sink, 46 | timer_ref = Ref, 47 | interval = Interval, 48 | prev_io = {In, Out}, 49 | prev_gc = {GCs, Words}}, 50 | 51 | erlang:system_flag(scheduler_wall_time, true), 52 | case SchedTimeEnabled of 53 | true -> 54 | {ok, State#state{sched_time = enabled, 55 | prev_sched = scheduler_wall_time()}}; 56 | false -> 57 | {ok, State#state{sched_time = disabled}} 58 | end. 59 | 60 | handle_call(_Msg, _From, State) -> 61 | {noreply, State}. 62 | 63 | handle_cast(_Msg, State) -> 64 | {noreply, State}. 65 | 66 | handle_info({timeout, R, ?TIMER_MSG}, S = #state{sink=Sink, key=K, key_separator=KS, memory_metrics=MM, interval=I, timer_ref=R}) -> 67 | %% Processes 68 | Sink:collect(gauge, [K,"proc_count"], erlang:system_info(process_count)), 69 | Sink:collect(gauge, [K,"proc_limit"], erlang:system_info(process_limit)), 70 | 71 | %% Ports 72 | Sink:collect(gauge, [K,"port_count"], erlang:system_info(port_count)), 73 | Sink:collect(gauge, [K,"port_limit"], erlang:system_info(port_limit)), 74 | 75 | %% Atom count, working only on Erlang 20+ 76 | try erlang:system_info(atom_count) of 77 | AtomCount -> Sink:collect(gauge, [K, "atom_count"], AtomCount) 78 | catch 79 | _:badarg -> ok 80 | end, 81 | 82 | %% Messages in queues 83 | TotalMessages = lists:foldl( 84 | fun(Pid, Acc) -> 85 | case process_info(Pid, message_queue_len) of 86 | undefined -> Acc; 87 | {message_queue_len, Count} -> Count+Acc 88 | end 89 | end, 90 | 0, 91 | processes() 92 | ), 93 | Sink:collect(gauge, [K,"messages_in_queues"], TotalMessages), 94 | 95 | %% Modules loaded 96 | Sink:collect(gauge, [K,"modules"], length(code:all_loaded())), 97 | 98 | %% Queued up processes (lower is better) 99 | Sink:collect(gauge, [K,"run_queue"], erlang:statistics(run_queue)), 100 | 101 | %% Erlang VM uptime stats. 102 | Sink:collect(timing, [K, "vm_uptime"], erlang:element(1, erlang:statistics(wall_clock))), 103 | 104 | %% Error logger backlog (lower is better) 105 | case whereis(error_logger) of 106 | undefined -> ok ; 107 | Pid -> 108 | {_, MQL} = process_info(Pid, message_queue_len), 109 | Sink:collect(gauge, [K,"error_logger_queue_len"], MQL) 110 | end, 111 | 112 | collect_memory_stats(Sink, [K, "memory", KS], MM), 113 | 114 | %% Incremental values 115 | IO = collect_io_stats(Sink, [K, "io", KS], S), 116 | GC = collect_gc_stats(Sink, [K, "gc", KS], S), 117 | 118 | %% Reductions across the VM, excluding current time slice, already incremental 119 | {_, Reds} = erlang:statistics(reductions), 120 | Sink:collect(counter, [K,"reductions"], Reds), 121 | 122 | SchedKey = [K, "scheduler_wall_time", KS], 123 | Sched = collect_sched_time_stats(Sink, SchedKey, S), 124 | 125 | Ref = erlang:start_timer(I, self(), ?TIMER_MSG), 126 | {noreply, S#state{timer_ref = Ref, 127 | prev_sched = Sched, 128 | prev_io = IO, prev_gc = GC}}; 129 | handle_info(_Msg, {state, _Key, _TimerRef, _Delay}) -> 130 | exit(forced_upgrade_restart); 131 | handle_info(_Msg, {state, _Key, SchedTime, _PrevSched, _TimerRef, _Delay}) -> 132 | %% The older version may have had the scheduler time enabled by default. 133 | %% We could check for settings and preserve it in memory, but then it would 134 | %% be more confusing if the behaviour changes on the next restart. 135 | %% Instead, we show a warning and restart as usual. 136 | case {application:get_env(vmstats, sched_time), SchedTime} of 137 | {undefined, active} -> % default was on 138 | error_logger:warning_msg("vmstats no longer runs scheduler time by default. Restarting..."); 139 | _ -> 140 | ok 141 | end, 142 | exit(forced_upgrade_restart); 143 | handle_info(_Msg, State) -> 144 | {noreply, State}. 145 | 146 | code_change(_OldVsn, State, _Extra) -> 147 | {ok, State}. 148 | 149 | terminate(_Reason, _State) -> 150 | ok. 151 | 152 | %% Returns the two timeslices as a ratio of each other, 153 | %% as a percentage so that StatsD gets to print something > 1 154 | wall_time_diff(T1, T2) -> 155 | [{I, Active2-Active1, Total2-Total1} 156 | || {{I, Active1, Total1}, {I, Active2, Total2}} <- lists:zip(T1,T2)]. 157 | 158 | collect_io_stats(Sink, Key, #state{prev_io = {PrevIn, PrevOut}}) -> 159 | {{input, In}, {output, Out}} = erlang:statistics(io), 160 | Sink:collect(counter, [Key, "bytes_in"], In - PrevIn), 161 | Sink:collect(counter, [Key, "bytes_out"], Out - PrevOut), 162 | {In, Out}. 163 | 164 | collect_gc_stats(Sink, Key, #state{prev_gc = {PrevGCs, PrevWords}}) -> 165 | {GCs, Words, _} = erlang:statistics(garbage_collection), 166 | Sink:collect(counter, [Key, "count"], GCs - PrevGCs), 167 | Sink:collect(counter, [Key, "words_reclaimed"], Words - PrevWords), 168 | {GCs, Words}. 169 | 170 | %% There are more options available, but not all were kept. 171 | %% Memory usage is in bytes. 172 | collect_memory_stats(Sink, Key, MemoryMetrics) -> 173 | [begin 174 | MetricKey = atom_to_list(Name), 175 | MetricValue = erlang:memory(Metric), 176 | Sink:collect(gauge, [Key, MetricKey], MetricValue) 177 | end 178 | || {Metric, Name} <- MemoryMetrics]. 179 | 180 | collect_sched_time_stats(Sink, Key, #state{sched_time = enabled, prev_sched = PrevSched, key_separator = KS}) -> 181 | Sched = scheduler_wall_time(), 182 | [begin 183 | LSid = integer_to_list(Sid), 184 | Sink:collect(timing, [Key, LSid, KS, "active"], Active), 185 | Sink:collect(timing, [Key, LSid, KS, "total"], Total) 186 | end 187 | || {Sid, Active, Total} <- wall_time_diff(PrevSched, Sched)], 188 | Sched; 189 | 190 | collect_sched_time_stats(_Sink, _Key, #state{prev_sched = PrevSched}) -> PrevSched. 191 | 192 | scheduler_wall_time() -> 193 | lists:sort(erlang:statistics(scheduler_wall_time)). 194 | -------------------------------------------------------------------------------- /src/vmstats_sink.erl: -------------------------------------------------------------------------------- 1 | -module(vmstats_sink). 2 | 3 | -type collect_type() :: counter | gauge | timing. 4 | 5 | -callback collect(Type :: collect_type(), Key :: iodata(), Value :: term()) -> ok. 6 | -------------------------------------------------------------------------------- /src/vmstats_sup.erl: -------------------------------------------------------------------------------- 1 | -module(vmstats_sup). 2 | -behaviour(supervisor). 3 | %% Interface 4 | -export([start_link/2]). 5 | %% Internal Exports 6 | -export([init/1]). 7 | 8 | start_link(Sink, BaseKey) -> 9 | supervisor:start_link({local, ?MODULE}, ?MODULE, [Sink, BaseKey]). 10 | 11 | init([Sink, BaseKey]) -> 12 | %% The stats are mixed in for all nodes. Differentiate keys by node name 13 | %% is the only way to make sure stats won't be mixed for all different 14 | %% systems. Hopefully, you remember to name nodes when you start them! 15 | ChildSpec = vmstats:child_spec(Sink, BaseKey), 16 | {ok, {{one_for_all,5,3600}, [ChildSpec]}}. 17 | -------------------------------------------------------------------------------- /test/sample_sink.erl: -------------------------------------------------------------------------------- 1 | -module(sample_sink). 2 | -behaviour(vmstats_sink). 3 | 4 | -export([collect/3, start_link/0, called/0, stop/0]). 5 | 6 | collect(Type, Key, Value) when Type =:= timing; Type =:= gauge; Type =:= counter -> 7 | K = lists:flatten(Key), 8 | call({store, K, Value}). 9 | 10 | start_link() -> 11 | spawn_link(fun() -> init() end). 12 | 13 | called() -> call(called). 14 | 15 | stop() -> call(stop). 16 | 17 | init() -> 18 | register(?MODULE, self()), 19 | loop([]). 20 | 21 | loop(Stack) -> 22 | receive 23 | {From, {store, K, D}} -> 24 | reply(From, ok), 25 | loop([{K,D}|Stack]); 26 | {From, called} -> 27 | reply(From, lists:reverse(Stack)), 28 | loop([]); 29 | {From, stop} -> 30 | reply(From, ok) 31 | end. 32 | 33 | 34 | call(Msg) -> 35 | Ref = make_ref(), 36 | ?MODULE ! {{self(), Ref}, Msg}, 37 | receive 38 | {Ref, Reply} -> Reply 39 | end. 40 | 41 | reply({Pid, Ref}, Reply) -> 42 | Pid ! {Ref, Reply}. 43 | -------------------------------------------------------------------------------- /test/vmstats_server_tests.erl: -------------------------------------------------------------------------------- 1 | -module(vmstats_server_tests). 2 | -include_lib("eunit/include/eunit.hrl"). 3 | 4 | -ifdef(OTP_RELEASE). 5 | -define(MATCH, [{"key.atom_count", _}, 6 | {"key.gc.count", _}, 7 | {"key.gc.words_reclaimed", _}, 8 | {"key.io.bytes_in", _}, 9 | {"key.io.bytes_out", _}, 10 | {"key.memory.atom_used", _}, 11 | {"key.memory.binary", _}, 12 | {"key.memory.ets", _}, 13 | {"key.memory.procs_used", _}, 14 | {"key.memory.total", _}, 15 | {"key.messages_in_queues", _}, 16 | {"key.modules", _}, 17 | {"key.port_count", _}, 18 | {"key.port_limit", _}, 19 | {"key.proc_count", _}, 20 | {"key.proc_limit", _}, 21 | {"key.reductions", _}, 22 | {"key.run_queue", _}, 23 | {"key.vm_uptime", _}]). 24 | -else. 25 | -define(MATCH, [{"key.atom_count", _}, 26 | {"key.error_logger_queue_len", _}, 27 | {"key.gc.count", _}, 28 | {"key.gc.words_reclaimed", _}, 29 | {"key.io.bytes_in", _}, 30 | {"key.io.bytes_out", _}, 31 | {"key.memory.atom_used", _}, 32 | {"key.memory.binary", _}, 33 | {"key.memory.ets", _}, 34 | {"key.memory.procs_used", _}, 35 | {"key.memory.total", _}, 36 | {"key.messages_in_queues", _}, 37 | {"key.modules", _}, 38 | {"key.port_count", _}, 39 | {"key.port_limit", _}, 40 | {"key.proc_count", _}, 41 | {"key.proc_limit", _}, 42 | {"key.reductions", _}, 43 | {"key.run_queue", _}, 44 | {"key.vm_uptime", _}]). 45 | -endif. 46 | 47 | setup() -> 48 | application:set_env(vmstats, interval, 500), 49 | application:set_env(vmstats, key_separator, $.), 50 | application:set_env(vmstats, sched_time, false), 51 | application:set_env(vmstats, memory_metrics, [ 52 | {total, total}, 53 | {processes_used, procs_used}, 54 | {atom_used, atom_used}, 55 | {binary, binary}, 56 | {ets, ets} 57 | ]), 58 | 59 | Key = "key", 60 | sample_sink:start_link(), 61 | {ok, Pid} = vmstats_server:start_link(sample_sink, Key), 62 | unlink(Pid), 63 | Pid. 64 | 65 | teardown(_) -> sample_sink:stop(). 66 | 67 | get_values() -> 68 | lists:sort([{lists:flatten(K), V} || {K, V} <- sample_sink:called()]). 69 | 70 | timer_500ms_test_() -> 71 | {setup, 72 | fun setup/0, 73 | fun teardown/1, 74 | fun(Pid) -> 75 | [ 76 | ?_test(begin 77 | timer:sleep(750), 78 | ?assertMatch(?MATCH, get_values()) 79 | end), 80 | ?_test(begin 81 | timer:sleep(600), 82 | exit(Pid, shutdown), 83 | ?assertMatch(?MATCH, get_values()) 84 | end), 85 | ?_assertEqual([], get_values()) 86 | ] 87 | end}. 88 | --------------------------------------------------------------------------------