Browse files

Initial commit

  • Loading branch information...
0 parents commit 468886e765c6327aa45576796f53824e4df26a9b @ferd committed Feb 27, 2012
Showing with 261 additions and 0 deletions.
  1. +9 −0 .gitignore
  2. +22 −0 Makefile
  3. +20 −0 README.markdown
  4. BIN rebar
  5. +7 −0 rebar.config
  6. +16 −0 src/vmstats.app.src
  7. +12 −0 src/vmstats.erl
  8. +69 −0 src/vmstats_server.erl
  9. +21 −0 src/vmstats_sup.erl
  10. +39 −0 test/statsderl.erl
  11. +46 −0 test/vmstats_server_tests.erl
9 .gitignore
@@ -0,0 +1,9 @@
+/ebin
+/deps
+/logs
+/log
+.DS_Store
+/test/*.beam
+*.swp
+erl_crash.dump
+/.eunit
22 Makefile
@@ -0,0 +1,22 @@
+all: deps compile
+
+compile:
+ ./rebar compile
+
+deps:
+ ./rebar get-deps
+
+clean:
+ ./rebar clean
+
+distclean: clean
+ ./rebar delete-deps
+
+test:
+ ./rebar eunit
+
+dialyzer: compile
+ @dialyzer -Wno_return -c ebin
+
+doc :
+ @./rebar doc skip_deps=true
20 README.markdown
@@ -0,0 +1,20 @@
+# vmstats #
+
+vmstats is a tiny Erlang app that works in conjunction with [statsderl](https://github.com/ferd/statsderl) in order to generate information on the Erlang VM for graphite logs.
+
+The different fields include:
+ - the error\_logger queue lenght
+ - the number of modules loaded
+ - the number of processes
+ - the process limit
+ - the length of the run queue
+ - memory used for ETS tables, atoms, processes, binaries and the total memory
+
+## How to build ##
+
+ `$ ./rebar compile`
+
+## Other Stuff
+
+It is recommended to leave the interval at 1000ms (1 second) as graphite seems to dampen missing data points on intervals larger than that, or to accumulate them when they're smaller. At roughly 1 second, the values seem to represent what the Erlang VM outputs the best.
+
BIN rebar
Binary file not shown.
7 rebar.config
@@ -0,0 +1,7 @@
+%% Erlang compiler options
+{erl_opts, [debug_info]}.
+
+{deps, [
+ {statsderl, "0.2",
+ {git, "https://github.com/ferd/statsderl.git", "master"}}
+ ]}.
16 src/vmstats.app.src
@@ -0,0 +1,16 @@
+{application, vmstats, [
+ {description, "Tiny application to gather VM statistics for StatsD client"},
+ {vsn, "0.1"},
+ {registered, [vmstats_sup, vmstats_server]},
+ {applications, [
+ kernel,
+ stdlib,
+ statsderl
+ ]},
+ {mod, {vmstats, []}},
+ {applications, [statsderl]},
+ {modules, [vmstats, vmstats_sup, vmstats_server]},
+ {env, [
+ {delay, 5000} % in milliseconds
+ ]}
+]}.
12 src/vmstats.erl
@@ -0,0 +1,12 @@
+%%% vmstats is a tiny Erlang application to be used in conjuction with
+%%% statsderl in order to gather running statistics of a virtual machine
+%%% over its lifetime, helping diagnose or prevent problems in the long run.
+-module(vmstats).
+-behaviour(application).
+-export([start/2, stop/1]).
+
+start(normal, []) -> % single node support. node ID in the key.
+ vmstats_sup:start_link("vmstats").
+
+stop(_) ->
+ ok.
69 src/vmstats_server.erl
@@ -0,0 +1,69 @@
+%%% Main worker for vmstats. This module sits in a loop fired off with
+%%% timers with the main objective of routinely sending data to
+%%% statsderl.
+-module(vmstats_server).
+-behaviour(gen_server).
+%% Interface
+-export([start_link/1]).
+%% Internal Exports
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ code_change/3, terminate/2]).
+
+-define(TIMER_MSG, '#delay').
+
+-record(state, {key :: string(),
+ timer_ref :: reference(),
+ delay :: integer()}). % milliseconds
+
+%%% INTERFACE
+%% the base key is passed from the supervisor. This function
+%% should not be called manually.
+start_link(BaseKey) ->
+ gen_server:start_link({local, ?MODULE}, ?MODULE, BaseKey, []).
+
+%%% INTERNAL EXPORTS
+init(BaseKey) ->
+ {ok, Delay} = application:get_env(vmstats, delay),
+ Ref = erlang:start_timer(Delay, self(), ?TIMER_MSG),
+ {ok, #state{key=BaseKey, timer_ref=Ref, delay=Delay}}.
+
+handle_call(_Msg, _From, State) ->
+ {noreply, State}.
+
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+handle_info({timeout, R, ?TIMER_MSG}, S = #state{key=K, delay=D, timer_ref=R}) ->
+ %% Processes
+ statsderl:increment([K,"proc_count"], erlang:system_info(process_count), 1.00),
+ statsderl:increment([K,"proc_limit"], erlang:system_info(process_limit), 1.00),
+
+ %% Modules loaded
+ statsderl:increment([K,"modules"], length(code:all_loaded()), 1.00),
+
+ %% Queued up processes (lower is better)
+ statsderl:increment([K,"run_queue"], erlang:statistics(run_queue), 1.00),
+
+ %% Error logger backlog (lower is better)
+ {_, MQL} = process_info(whereis(error_logger), message_queue_len),
+ statsderl:increment([K,"error_logger_queue_len"], MQL, 1.00),
+
+ %% Memory usage. There are more options available, but not all were kept.
+ %% Memory usage is in bytes.
+ K2 = [K,"memory."],
+ Mem = erlang:memory(),
+ statsderl:increment([K2,"total"], proplists:get_value(total, Mem), 1.00),
+ statsderl:increment([K2,"procs_used"], proplists:get_value(processes_used,Mem), 1.00),
+ statsderl:increment([K2,"atom_used"], proplists:get_value(atom_used,Mem), 1.00),
+ statsderl:increment([K2,"binary"], proplists:get_value(binary, Mem), 1.00),
+ statsderl:increment([K2,"ets"], proplists:get_value(ets, Mem), 1.00),
+
+ {noreply, S#state{timer_ref=erlang:start_timer(D, self(), ?TIMER_MSG)}};
+handle_info(_Msg, State) ->
+ {noreply, State}.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+terminate(_Reason, _State) ->
+ ok.
21 src/vmstats_sup.erl
@@ -0,0 +1,21 @@
+-module(vmstats_sup).
+-behaviour(supervisor).
+%% Interface
+-export([start_link/1]).
+%% Internal Exports
+-export([init/1]).
+
+start_link(BaseKey) ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, BaseKey).
+
+init(BaseKey) ->
+ %% The stats are mixed in for all nodes. Differentiate keys by node name
+ %% is the only way to make sure stats won't be mixed for all different
+ %% systems. Hopefully, you remember to name nodes when you start them!
+ ChildSpec = {vmstats,
+ {vmstats_server, start_link, [BaseKey]},
+ permanent,
+ 1000,
+ worker,
+ [vmstats_server]},
+ {ok, {{one_for_all,5,3600}, [ChildSpec]}}.
39 test/statsderl.erl
@@ -0,0 +1,39 @@
+-module(statsderl).
+-export([start_link/0, increment/3, called/0, stop/0]).
+
+start_link() ->
+ spawn_link(fun() -> init() end).
+
+increment(Key, Data, Freq) ->
+ call({incr, Key, Data, Freq}).
+
+called() -> call(called).
+
+stop() -> call(stop).
+
+init() ->
+ register(?MODULE, self()),
+ loop([]).
+
+loop(Stack) ->
+ receive
+ {From, {incr, K, D, F}} ->
+ reply(From, ok),
+ loop([{K,D,F}|Stack]);
+ {From, called} ->
+ reply(From, lists:reverse(Stack)),
+ loop([]);
+ {From, stop} ->
+ reply(From, ok)
+ end.
+
+
+call(Msg) ->
+ Ref = make_ref(),
+ ?MODULE ! {{self(), Ref}, Msg},
+ receive
+ {Ref, Reply} -> Reply
+ end.
+
+reply({Pid, Ref}, Reply) ->
+ Pid ! {Ref, Reply}.
46 test/vmstats_server_tests.erl
@@ -0,0 +1,46 @@
+-module(vmstats_server_tests).
+-include_lib("eunit/include/eunit.hrl").
+
+%% Gotta mock some stuff for this to work. The module
+%% statsderl used here is a fake copy for the sake of
+%% being able to write tests without dependencies.
+
+timer_500ms_test() ->
+ application:set_env(vmstats, delay, 500),
+ Key = "",
+ statsderl:start_link(),
+ {ok, Pid} = vmstats_server:start_link(Key),
+ unlink(Pid),
+ timer:sleep(750),
+ %% First match works
+ ?assertMatch(
+ [{"error_logger_queue_len", _, 1.00},
+ {"memory.atom_used", _, 1.00},
+ {"memory.binary", _, 1.00},
+ {"memory.ets", _, 1.00},
+ {"memory.procs_used", _, 1.00},
+ {"memory.total", _, 1.00},
+ {"modules", _, 1.00},
+ {"proc_count", _, 1.00},
+ {"proc_limit", _, 1.00},
+ {"run_queue", _, 1.00}],
+ lists:sort([{lists:flatten(K), V, Freq} || {K, V, Freq} <- statsderl:called()])
+ ),
+ timer:sleep(600),
+ exit(Pid, shutdown),
+ %% Done, we know it loops!
+ ?assertMatch(
+ [{"error_logger_queue_len", _, 1.00},
+ {"memory.atom_used", _, 1.00},
+ {"memory.binary", _, 1.00},
+ {"memory.ets", _, 1.00},
+ {"memory.procs_used", _, 1.00},
+ {"memory.total", _, 1.00},
+ {"modules", _, 1.00},
+ {"proc_count", _, 1.00},
+ {"proc_limit", _, 1.00},
+ {"run_queue", _, 1.00}],
+ lists:sort([{lists:flatten(K), V, Freq} || {K, V, Freq} <- statsderl:called()])
+ ),
+ ?assertEqual([], lists:sort(statsderl:called())),
+ statsderl:stop().

0 comments on commit 468886e

Please sign in to comment.