Permalink
Browse files

Round out support for multiple instances of services, perform additio…

…nal tidy up, generally make things more ship-shape and OTP-fashion
  • Loading branch information...
1 parent 4ae3960 commit ff3494f582d67d06513e404e0eb7f51315e0a5aa @afternoon committed May 2, 2011
View
@@ -2,6 +2,7 @@
*.sw[nop]
.eunit
.DS_Store
+app.config
data
deps
ebin
View
@@ -10,8 +10,9 @@ Overview
- Monitoring and graphing tool like Munin or collectd.
- Written in Erlang.
-- Asynchronous data gathering.
- Sample frequency down to 1 second, configured per plug-in.
+- Record data on multiple nodes in parallel.
+- Asynchronous data gathering.
- HTTP interface for HTML, graphs and data via JSON.
- Writing plug-ins is simple. Plug-ins are kept resident between updates, as in
collectd.
@@ -20,15 +21,16 @@ Overview
Getting started
---------------
-Configure which node should be master and which services should run on which
-nodes in `priv/recorder.config`.
+Configure which node(s) should be recorders in etc/app.config.
+
+ [{rolf, [recorders, [rolf@john]]}].
- {recorders, [rolf@john]}.
+Configure which services should run on which nodes in `etc/services.config`.
- {services, [{rolf@john, all},
- {rolf@paul, [disk]},
- {rolf@george, [disk]},
- {rolf@ringo, [disk]}]}.
+ {node, rolf@john, [disk, loadtime]}.
+ {node, rolf@paul, [disk]}.
+ {node, rolf@george, [disk]}.
+ {node, rolf@ringo, [disk]}.
Start Rolf on each machine.
@@ -48,14 +50,16 @@ Architecture
- A node manager process starts services and they start emitting samples.
- Services can be implemented as Erlang functions, external commands or external
daemons.
-- Services collect samples and send them to the recorder.
+- Services collect samples and send them to all live recorders.
+- Recorders and collectors can be added and removed from the cluster
+ dynamically.
The supervision tree of an Rolf node looks like this (node has recorder):
rolf_sup
├── rolf_recorder
│ └── errd_server
- └── rolf_service_sup
+ └── rolf_collector_sup
├── rolf_service (svc1)
└── rolf_service (svc2)
└── rolf_external
@@ -65,13 +69,13 @@ Plugins
Plugins add additional collectors to Rolf.
-- Plugins live in app/rolf/priv/plugin.d.
+- Plugins live in `plugins`.
- A plugin has a config file, see
- `app/rolf/priv/plugin.d/loadtime/loadtime.config` for an example.
-- Plugin config includes an options key. Options can be overridden per-site by
- customising `recorder.config`.
+ `plugins/loadtime/loadtime.config` for an example.
+- Plugin config can be overridden per-site by customising `services.config`.
- Plugins can use an Erlang module, a command or a daemon to collect data.
-- Plugin collectors written in Erlang should use the `rolf_collector` behaviour.
+- Plugin collectors written in Erlang should implement the `rolf_collector`
+ behaviour.
- The `collect/1` function should capture data. The argument, `Service`, is a
`service` record, which includes lots of useful info such as name and options
(see `apps/rolf/include/rolf.hrl` for more info).
@@ -32,6 +32,7 @@
%% @doc State record for Rolf services, which contains multiple metrics.
-record(service, {name=undefined,
plugin=undefined,
+ recorders=undefined,
module=undefined,
command=undefined,
frequency=undefined,
View
@@ -1,3 +1,4 @@
+%% vim: ft=erlang
%% @doc Rolf app config.
%% @author Ben Godfrey <ben@ben2.com> [http://aftnn.org/]
%% @copyright 2011 Ben Godfrey
@@ -21,8 +22,21 @@
{application, rolf, [
{description, "System monitoring and graphing tool like Munin or collectd"},
- {vsn, "1"},
+ {vsn, "0.1"},
{registered, []},
{applications, [kernel, stdlib, log4erl]},
{mod, {rolf_app, []}},
- {env, []}]}.
+ {env, [{recorders, []},
+ {services_config, "etc/services.config"},
+ {log4erl_config, "etc/log4erl.conf"},
+ {plugin_dir, "plugins"},
+ {plugin_default_freq, 10},
+ {plugin_default_timeout_multiple, 3},
+ {plugin_default_archives, [{1, 360}, % 1hr of 10s averages
+ {30, 288}, % 1d of 5m averages
+ {180, 336}, % 7d of 30m averages
+ {8640, 365}]}, % 1y of 1d averages
+ {plugin_default_type, gauge},
+ {plugin_default_draw, line},
+ {rrd_dir, "data"},
+ {rrd_ext, "rrd"}]}]}.
View
@@ -0,0 +1,33 @@
+%% @doc Rolf startup.
+%% @author Ben Godfrey <ben@ben2.com> [http://aftnn.org/]
+%% @copyright 2011 Ben Godfrey
+%% @version 1.0.0
+%%
+%% Rolf - a monitoring and graphing tool like Munin or collectd.
+%% Copyright (C) 2011 Ben Godfrey.
+%%
+%% This program is free software: you can redistribute it and/or modify
+%% it under the terms of the GNU General Public License as published by
+%% the Free Software Foundation, either version 3 of the License, or
+%% (at your option) any later version.
+%%
+%% This program is distributed in the hope that it will be useful,
+%% but WITHOUT ANY WARRANTY; without even the implied warranty of
+%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+%% GNU General Public License for more details.
+%%
+%% You should have received a copy of the GNU General Public License
+%% along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+-module(rolf).
+
+%% API
+-export([start/0, stop/0]).
+
+start() ->
+ application:start(log4erl),
+ application:start(rolf).
+
+stop() ->
+ application:stop(rolf),
+ application:stop(log4erl).
View
@@ -26,8 +26,6 @@
%% Application callbacks
-export([start/0, start/2, stop/1]).
--define(LOG4ERL_CONFIG_FILE, "etc/log4erl.conf").
-
%% ===================================================================
%% Application callbacks
%% ===================================================================
@@ -37,13 +35,14 @@ start() ->
start(_StartType, _StartArgs) ->
configure_logger(),
+ log4erl:info("Starting"),
CResult = rolf_collector_sup:start_link(),
case rolf_recorder:is_recorder() of
true ->
- log4erl:info("~p is recorder", [node()]),
+ log4erl:info("~p is a recorder", [node()]),
rolf_recorder_sup:start_link();
_ ->
- log4erl:info("~p is collector only", [node()]),
+ log4erl:info("~p is a collector only", [node()]),
announce_collector(),
CResult
end.
@@ -57,13 +56,9 @@ stop(_State) ->
%% @doc Load log4erl configuration.
configure_logger() ->
- log4erl:conf(?LOG4ERL_CONFIG_FILE).
+ {ok, ConfigFilename} = application:get_env(log4erl_config),
+ log4erl:conf(ConfigFilename).
%% @doc Announce collector start to recorders.
announce_collector() ->
- case application:get_key(recorders) of
- {ok, Recorders} ->
- lists:foreach(fun net_adm:ping/1, Recorders);
- undefined ->
- false
- end.
+ lists:foreach(fun net_adm:ping/1, rolf_recorder:recorders()).
@@ -20,6 +20,7 @@
%% along with this program. If not, see <http://www.gnu.org/licenses/>.
-module(rolf_collector_sup).
+
-behaviour(supervisor).
%% API
@@ -22,26 +22,18 @@
-module(rolf_plugin).
%% API
--export([list/0, load/3]).
+-export([list/0, load/2]).
-include("rolf.hrl").
--define(PLUGIN_DIR, "plugins").
--define(PLUGIN_DEFAULT_FREQ, 10).
--define(PLUGIN_DEFAULT_TIMEOUT_MULTIPLE, 3).
--define(PLUGIN_DEFAULT_ARCHIVES, [{1, 360}, % 1hr of 10s averages
- {30, 288}, % 1d of 5m averages
- {180, 336}, % 7d of 30m averages
- {8640, 365}]). % 1y of 1d averages
--define(PLUGIN_DEFAULT_TYPE, gauge).
--define(PLUGIN_DEFAULT_DRAW, line).
-
%% ===================================================================
%% Configuration
%% ===================================================================
%% @doc List available plugins.
-list() -> list(?PLUGIN_DIR).
+list() ->
+ {ok, PluginDir} = application:get_env(plugin_dir),
+ list(PluginDir).
%% @doc List available plugins in Dir.
list(Dir) ->
@@ -54,58 +46,64 @@ configfilename_to_atom(CFName) ->
list_to_atom(filename:rootname(filename:basename(CFName))).
%% @doc Load plugin config from file.
-load(Plugin, Name, Opts) ->
+load(Plugin, Opts) ->
{ok, Config} = file:consult(config_path(Plugin)),
- parse(Plugin, Name, propmerge(Config, Opts)).
+ parse(Plugin, propmerge(Config, Opts)).
%% @doc Get path to a plugin's config file.
config_path(Plugin) ->
PluginStr = atom_to_list(Plugin),
CfgName = string:join([PluginStr, "config"], "."),
- filename:join([?PLUGIN_DIR, PluginStr, CfgName]).
-
-%% @doc Parse the command for this plugin.
-parse_command(Plugin, Config) ->
- case proplists:get_value(command, Config, undefined) of
- undefined ->
- undefined;
- Cmd ->
- external_path(Plugin, Cmd)
- end.
-
-%% @doc Return the full path to an external program.
-external_path(Plugin, Cmd) ->
- filename:join([?PLUGIN_DIR, atom_to_list(Plugin), Cmd]).
+ {ok, PluginDir} = application:get_env(plugin_dir),
+ filename:join([PluginDir, PluginStr, CfgName]).
%% @doc Parse config file contents into a service record.
-parse(Plugin, Name, Config) ->
- Freq = proplists:get_value(frequency, Config, ?PLUGIN_DEFAULT_FREQ),
+parse(Plugin, Config) ->
+ {ok, PluginDefaultFreq} = application:get_env(plugin_default_freq),
+ {ok, PluginDefaultTimeoutMultiple} = application:get_env(plugin_default_timeout_multiple),
+ {ok, PluginDefaultArchives} = application:get_env(plugin_default_archives),
+ Freq = proplists:get_value(frequency, Config, PluginDefaultFreq),
#service{
plugin=Plugin,
- name=Name,
module=proplists:get_value(module, Config, rolf_command),
command=parse_command(Plugin, Config),
frequency=Freq,
- timeout=proplists:get_value(timeout, Config, Freq * ?PLUGIN_DEFAULT_TIMEOUT_MULTIPLE),
- archives=proplists:get_value(archives, Config, ?PLUGIN_DEFAULT_ARCHIVES),
+ timeout=proplists:get_value(timeout, Config, Freq * PluginDefaultTimeoutMultiple),
+ archives=proplists:get_value(archives, Config, PluginDefaultArchives),
graph_title=proplists:get_value(graph_title, Config, atom_to_list(Plugin)),
graph_vlabel=proplists:get_value(graph_vlabel, Config, ""),
metrics=parse_metrics(Config),
config=Config
}.
+%% @doc Parse the command for this plugin.
+parse_command(Plugin, Config) ->
+ case proplists:get_value(command, Config, undefined) of
+ undefined ->
+ undefined;
+ Cmd ->
+ external_path(Plugin, Cmd)
+ end.
+
+%% @doc Return the full path to an external program.
+external_path(Plugin, Cmd) ->
+ {ok, PluginDir} = application:get_env(plugin_dir),
+ filename:join([PluginDir, atom_to_list(Plugin), Cmd]).
+
%% @doc Parse config for a list of metrics into list of metric records.
parse_metrics(Config) ->
MetricCfg = proplists:get_value(metrics, Config, []),
[parse_metric(M) || M <- MetricCfg].
%% @doc Parse config for a single metric into a metric record.
parse_metric({Metric, MetricCfg}) ->
+ {ok, PluginDefaultType} = application:get_env(plugin_default_type),
+ {ok, PluginDefaultDraw} = application:get_env(plugin_default_draw),
#metric{
name=Metric,
label=proplists:get_value(label, MetricCfg, ""),
- type=proplists:get_value(type, MetricCfg, ?PLUGIN_DEFAULT_TYPE),
- draw=proplists:get_value(draw, MetricCfg, ?PLUGIN_DEFAULT_DRAW),
+ type=proplists:get_value(type, MetricCfg, PluginDefaultType),
+ draw=proplists:get_value(draw, MetricCfg, PluginDefaultDraw),
min=proplists:get_value(min, MetricCfg, undefined),
max=proplists:get_value(max, MetricCfg, undefined),
colour=proplists:get_value(colour, MetricCfg, undefined)
@@ -122,7 +120,8 @@ configfilename_to_atom_test() ->
?assertEqual(disk, configfilename_to_atom("plugins/disk/disk.config")).
config_path_test() ->
- Path = filename:join([?PLUGIN_DIR, "loadtime", "loadtime.config"]),
+ {ok, PluginDir} = application:get_env(plugin_dir),
+ Path = filename:join([PluginDir, "loadtime", "loadtime.config"]),
?assertEqual(Path, config_path(loadtime)).
parse_test() ->
@@ -135,17 +134,17 @@ parse_test() ->
{draw, areastack},
{min, 0},
{colour, "#0091FF"}]}]}],
- Output = parse(loadtime, loadtime0, Input),
+ Output = parse(loadtime, Input),
?assertEqual(loadtime, Output#service.plugin),
- ?assertEqual(loadtime0, Output#service.name),
+ ?assertEqual(undefined, Output#service.name),
?assertEqual(10, Output#service.frequency),
?assertEqual("plugins/loadtime/loadtime.sh", Output#service.command),
?assertEqual(rolf_command, Output#service.module).
parse_options_test() ->
Input = [{command, "loadtime.sh"},
{unit, mb}],
- Output = parse(loadtime, loadtime0, Input),
+ Output = parse(loadtime, Input),
?assertEqual(mb, proplists:get_value(unit, Output#service.config)).
propmerge_test() ->
Oops, something went wrong.

0 comments on commit ff3494f

Please sign in to comment.