├── .gitignore
├── README.md
├── apps
└── rolf
│ ├── include
│ └── rolf.hrl
│ ├── src
│ ├── collectors
│ │ ├── rolf_command.erl
│ │ ├── rolf_loadtime.erl
│ │ └── rolf_munin_node.erl
│ ├── rolf.app.src
│ ├── rolf.erl
│ ├── rolf_app.erl
│ ├── rolf_collector.erl
│ ├── rolf_collector_sup.erl
│ ├── rolf_config_validation.erl
│ ├── rolf_plugin.erl
│ ├── rolf_recorder.erl
│ ├── rolf_recorder_sup.erl
│ ├── rolf_rrd.erl
│ ├── rolf_service.erl
│ └── rolf_util.erl
│ └── test
│ └── rolf_SUITE.erl
├── bin
└── graph.sh
├── doc
└── windows.md
├── plugins
├── rebar
├── rebar.config
└── rel
├── files
├── app.config
├── erl
├── log4erl.conf
├── nodetool
├── plugins
│ ├── disk
│ │ ├── disk.config
│ │ └── disk.sh
│ ├── loadtime
│ │ ├── loadtime.config
│ │ └── loadtime.sh
│ └── uptime
│ │ └── uptime.config
├── rolf
├── services.config
└── vm.args
└── reltool.config
/.gitignore:
--------------------------------------------------------------------------------
1 | *.beam
2 | *.sw[nop]
3 | .eunit
4 | .DS_Store
5 | app.config
6 | data
7 | deps
8 | ebin
9 | etc
10 | log
11 | rel/rolf
12 | tags
13 | TEST-*.xml
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Rolf
2 | ====
3 |
4 | 'Azamat is run Big Data analytics on famous last words in devops. Most common is
5 | "I know, I roll own monitoring tool!"'
6 | -- [@DEVOPS_BORAT](http://twitter.com/#!/DEVOPS_BORAT/status/51324117521141760)
7 |
8 | Overview
9 | --------
10 |
11 | - Monitoring and graphing tool like Munin or collectd.
12 | - Written in Erlang.
13 | - Sample frequency down to 1 second, configured per plug-in.
14 | - Record data on multiple nodes in parallel.
15 | - Asynchronous data gathering.
16 | - HTTP interface for HTML, graphs and data via JSON.
17 | - Writing plug-ins is simple. Plug-ins are kept resident between updates, as in
18 | collectd.
19 | - Runs anywhere Erlang runs (at least Linux, OS X, Windows).
20 |
21 | Getting started
22 | ---------------
23 |
24 | Configure which node(s) should be recorders in etc/app.config.
25 |
26 | [{rolf, [recorders, [rolf@john]]}].
27 |
28 | Configure which services should run on which nodes in `etc/services.config`.
29 |
30 | {node, rolf@john, [disk, loadtime]}.
31 | {node, rolf@paul, [disk]}.
32 | {node, rolf@george, [disk]}.
33 | {node, rolf@ringo, [disk]}.
34 |
35 | Start Rolf on each machine.
36 |
37 | [user@john ~] rolf start
38 | [user@paul ~] rolf start
39 | [user@george ~] rolf start
40 | [user@ringo ~] rolf start
41 |
42 | Architecture
43 | ------------
44 |
45 | - An Erlang cluster is created. Each node runs the rolf application.
46 | - One node is designated the recorder by it's config file.
47 | - If the cluster has no recorder, nothing happens.
48 | - If a recorder has been started, service configuration is distributed to all
49 | other nodes by the recorder.
50 | - A node manager process starts services and they start emitting samples.
51 | - Services can be implemented as Erlang functions, external commands or external
52 | daemons.
53 | - Services collect samples and send them to all live recorders.
54 | - Recorders and collectors can be added and removed from the cluster
55 | dynamically.
56 |
57 | The supervision tree of an Rolf node looks like this (node has recorder):
58 |
59 | rolf_sup
60 | ├── rolf_recorder
61 | │ └── errd_server
62 | └── rolf_collector_sup
63 | ├── rolf_service (svc1)
64 | └── rolf_service (svc2)
65 | └── rolf_external
66 |
67 | Plugins
68 | -------
69 |
70 | Plugins add additional collectors to Rolf.
71 |
72 | - Plugins live in `plugins`.
73 | - A plugin has a config file, see
74 | `plugins/loadtime/loadtime.config` for an example.
75 | - Plugin config can be overridden per-site by customising `services.config`.
76 | - Plugins can use an Erlang module, a command or a daemon to collect data.
77 | - Plugin collectors written in Erlang should implement the `rolf_collector`
78 | behaviour.
79 | - The `collect/1` function should capture data. The argument, `Service`, is a
80 | `service` record, which includes lots of useful info such as name and options
81 | (see `apps/rolf/include/rolf.hrl` for more info).
82 |
83 | Author
84 | ------
85 |
86 | Ben Godfrey, ben@ben2.com, http://aftnn.org/.
87 |
88 | License
89 | -------
90 |
91 | Rolf - a monitoring and graphing tool like Munin or collectd.
92 |
93 | Copyright (C) 2011 Ben Godfrey.
94 |
95 | This program is free software: you can redistribute it and/or modify
96 | it under the terms of the GNU General Public License as published by
97 | the Free Software Foundation, either version 3 of the License, or
98 | (at your option) any later version.
99 |
100 | This program is distributed in the hope that it will be useful,
101 | but WITHOUT ANY WARRANTY; without even the implied warranty of
102 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
103 | GNU General Public License for more details.
104 |
105 | You should have received a copy of the GNU General Public License
106 | along with this program. If not, see .
107 |
--------------------------------------------------------------------------------
/apps/rolf/include/rolf.hrl:
--------------------------------------------------------------------------------
1 | %% @doc Rolf records.
2 | %% @author Ben Godfrey [http://aftnn.org/]
3 | %% @copyright 2011 Ben Godfrey
4 | %% @version 1.0.0
5 | %%
6 | %% Rolf - a monitoring and graphing tool like Munin or collectd.
7 | %% Copyright (C) 2011 Ben Godfrey.
8 | %%
9 | %% This program is free software: you can redistribute it and/or modify
10 | %% it under the terms of the GNU General Public License as published by
11 | %% the Free Software Foundation, either version 3 of the License, or
12 | %% (at your option) any later version.
13 | %%
14 | %% This program is distributed in the hope that it will be useful,
15 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | %% GNU General Public License for more details.
18 | %%
19 | %% You should have received a copy of the GNU General Public License
20 | %% along with this program. If not, see .
21 |
22 | %% import eunit macros into all modules
23 | -ifdef(TEST).
24 | -include_lib("eunit/include/eunit.hrl").
25 | -endif.
26 |
27 | %% @doc State record for Rolf recorder.
28 | -record(recorder, {collectors=[],
29 | rrd=undefined}).
30 |
31 | %% @doc State record for Rolf nodes.
32 | -record(node, {services=[]}).
33 |
34 | %% @doc State record for Rolf services, which contains multiple metrics.
35 | -record(service, {name=undefined,
36 | plugin=undefined,
37 | recorders=undefined,
38 | module=undefined,
39 | command=undefined,
40 | frequency=undefined,
41 | timeout=undefined,
42 | archives=undefined,
43 | graph_title=undefined,
44 | graph_vlabel=undefined,
45 | metrics=undefined,
46 | config=undefined,
47 | tref=undefined}).
48 |
49 | %% @doc Record for a single metric.
50 | -record(metric, {name=undefined,
51 | label="",
52 | type=gauge,
53 | draw=line,
54 | min=undefined,
55 | max=undefined,
56 | colour=undefined}).
57 |
58 | %% @doc Record for Rolf samples. values is a list of tuples of format
59 | %% {Metric, Value}.
60 | -record(sample, {node=undefined,
61 | service=undefined,
62 | values=undefined}).
63 |
--------------------------------------------------------------------------------
/apps/rolf/src/collectors/rolf_command.erl:
--------------------------------------------------------------------------------
1 | %% @doc Plugin module for generic commands.
2 | %% @author Ben Godfrey [http://aftnn.org/]
3 | %% @copyright 2011 Ben Godfrey
4 | %% @version 1.0.0
5 | %%
6 | %% Rolf - a monitoring and graphing tool like Munin or collectd.
7 | %% Copyright (C) 2011 Ben Godfrey.
8 | %%
9 | %% This program is free software: you can redistribute it and/or modify
10 | %% it under the terms of the GNU General Public License as published by
11 | %% the Free Software Foundation, either version 3 of the License, or
12 | %% (at your option) any later version.
13 | %%
14 | %% This program is distributed in the hope that it will be useful,
15 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | %% GNU General Public License for more details.
18 | %%
19 | %% You should have received a copy of the GNU General Public License
20 | %% along with this program. If not, see .
21 |
22 | -module(rolf_command).
23 | -behaviour(rolf_collector).
24 |
25 | %% rolf_collector callbacks
26 | -export([start/1, collect/2, stop/2]).
27 |
28 | -include("rolf.hrl").
29 |
30 | %% ===================================================================
31 | %% rolf_collector callbacks
32 | %% ===================================================================
33 |
34 | %% @doc Start collector.
35 | start(_Service) -> ok.
36 |
37 | %% @doc Execute the command and return the parsed results.
38 | collect(Service, State) ->
39 | {State, parse_output(Service, os:cmd(Service#service.command))}.
40 |
41 | %% @doc Stop collector.
42 | stop(_Service, _State) -> ok.
43 |
44 | %% ===================================================================
45 | %% Helper functions
46 | %% ===================================================================
47 |
48 | %% @doc Parse output from external command.
49 | parse_output(Service, Output) ->
50 | Values = [parse_line(Line) || Line <- split_lines(Output)],
51 | #sample{node=node(), service=Service, values=Values}.
52 |
53 | %% @doc Split output into lines, drop terminating ".\n" line.
54 | split_lines(Lines) ->
55 | Lines1 = string:tokens(Lines, "\n"),
56 | lists:filter(fun(L) -> L /= "." end, Lines1).
57 |
58 | %% @doc Parse line into {atom, int_or_float} tuple.
59 | parse_line(Line) ->
60 | {K, V} = list_to_tuple(string:tokens(Line, " ")),
61 | {list_to_atom(K), rolf_util:list_to_num(V)}.
62 |
63 | %% ===================================================================
64 | %% Tests
65 | %% ===================================================================
66 |
67 | -ifdef(TEST).
68 |
69 | split_lines_test() ->
70 | Result = split_lines("loadtime 0.99\n.\n"),
71 | ?assertEqual(["loadtime 0.99"], Result).
72 |
73 | parse_line_test() ->
74 | Result = parse_line("loadtime 0.99"),
75 | ?assertEqual({loadtime, 0.99}, Result).
76 |
77 | parse_output_test() ->
78 | Result = parse_output(loadtime, "loadtime 0.99\n.\n"),
79 | ?assertEqual(#sample{node=node(),
80 | service=loadtime,
81 | values=[{loadtime, 0.99}]},
82 | Result).
83 |
84 | parse_output_many_test() ->
85 | Result = parse_output(loadtime, "loadtime 0.99\nttfb 0.65\nrendertime 2\n.\n"),
86 | ?assertEqual(#sample{node=node(),
87 | service=loadtime,
88 | values=[{loadtime, 0.99},
89 | {ttfb, 0.65},
90 | {rendertime, 2}]},
91 | Result).
92 |
93 | -endif.
94 |
--------------------------------------------------------------------------------
/apps/rolf/src/collectors/rolf_loadtime.erl:
--------------------------------------------------------------------------------
1 | %% @doc Plugin module for measuring web site load time.
2 | %% @author Ben Godfrey [http://aftnn.org/]
3 | %% @copyright 2011 Ben Godfrey
4 | %% @version 1.0.0
5 | %%
6 | %% Rolf - a monitoring and graphing tool like Munin or collectd.
7 | %% Copyright (C) 2011 Ben Godfrey.
8 | %%
9 | %% This program is free software: you can redistribute it and/or modify
10 | %% it under the terms of the GNU General Public License as published by
11 | %% the Free Software Foundation, either version 3 of the License, or
12 | %% (at your option) any later version.
13 | %%
14 | %% This program is distributed in the hope that it will be useful,
15 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | %% GNU General Public License for more details.
18 | %%
19 | %% You should have received a copy of the GNU General Public License
20 | %% along with this program. If not, see .
21 |
22 | -module(rolf_loadtime).
23 | -behaviour(rolf_collector).
24 |
25 | %% rolf_collector callbacks
26 | -export([start/1, collect/2, stop/2]).
27 |
28 | %% helpers
29 | -export([time_url/1]).
30 |
31 | -include("rolf.hrl").
32 |
33 | %% ===================================================================
34 | %% rolf_collector callbacks
35 | %% ===================================================================
36 |
37 | %% @doc Start collector.
38 | start(_Service) ->
39 | inets:start().
40 |
41 | %% @doc HTTP load time collector function for Rolf. Options should contain a key
42 | %% urls with value [{Name, Url}].
43 | collect(Service, State) ->
44 | Config = Service#service.config,
45 | Url = proplists:get_value(url, Config, []),
46 | Values = [{loadtime, time_url(Url)}],
47 | Sample = #sample{node=node(), service=Service, values=Values},
48 | {State, Sample}.
49 |
50 | %% @doc Stop collector.
51 | stop(_Service, _State) ->
52 | inets:stop().
53 |
54 | %% ===================================================================
55 | %% Helper functions
56 | %% ===================================================================
57 |
58 | %% @doc Return how long it took to load resource at Url in milliseconds.
59 | time_url(Url) ->
60 | {T, _} = timer:tc(httpc, request, [head, {Url, []}, [], []]),
61 | round(T / 1000).
62 |
--------------------------------------------------------------------------------
/apps/rolf/src/collectors/rolf_munin_node.erl:
--------------------------------------------------------------------------------
1 | %% @doc Plugin module for retrieving measurements from a munin node.
2 | %% @author Ben Godfrey [http://aftnn.org/]
3 | %% @copyright 2011 Ben Godfrey
4 | %% @version 1.0.0
5 | %%
6 | %% Rolf - a monitoring and graphing tool like Munin or collectd.
7 | %% Copyright (C) 2011 Ben Godfrey.
8 | %%
9 | %% This program is free software: you can redistribute it and/or modify
10 | %% it under the terms of the GNU General Public License as published by
11 | %% the Free Software Foundation, either version 3 of the License, or
12 | %% (at your option) any later version.
13 | %%
14 | %% This program is distributed in the hope that it will be useful,
15 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | %% GNU General Public License for more details.
18 | %%
19 | %% You should have received a copy of the GNU General Public License
20 | %% along with this program. If not, see .
21 |
22 | -module(rolf_munin_node).
23 | -behaviour(rolf_collector).
24 |
25 | %% rolf_collector callbacks
26 | -export([start/1, collect/2, stop/2]).
27 |
28 | -include("rolf.hrl").
29 |
30 | %% ===================================================================
31 | %% rolf_collector callbacks
32 | %% ===================================================================
33 |
34 | %% @doc Start collector.
35 | start(Service) ->
36 | munin_node_params(Service).
37 |
38 | %% @doc HTTP load time collector function for Rolf. Options should contain a key
39 | %% urls with value [{Name, Url}].
40 | collect(Service, {Host, Port, Opts, Plugin}=State) ->
41 | case gen_tcp:connect(Host, Port, Opts) of
42 | {ok, Sock} ->
43 | case gen_tcp:recv(Sock, 0) of
44 | {ok, _Banner} ->
45 | Values = fetch(Sock, Plugin),
46 | ok = gen_tcp:close(Sock),
47 | {State, #sample{node=node(), service=Service, values=Values}};
48 | {error, Reason} ->
49 | log4erl:error("Error reading from ~p:~p: ~p", [Host, Port, Reason]),
50 | {State, #sample{node=node(), service=Service, values=[]}}
51 | end;
52 | {error, Reason} ->
53 | log4erl:error("Couldn't connect to ~p:~p: ~p", [Host, Port, Reason]),
54 | {State, #sample{node=node(), service=Service, values=[]}}
55 | end.
56 |
57 | %% @doc Stop collector.
58 | stop(_State, _Service) ->
59 | ok.
60 |
61 | %% ===================================================================
62 | %% Utility functions
63 | %% ===================================================================
64 |
65 | %% @doc Extract the socket parameters from service config.
66 | munin_node_params(Service) ->
67 | Config = Service#service.config,
68 | Host = proplists:get_value(host, Config),
69 | Port = proplists:get_value(port, Config, 4949),
70 | {ok, Opts} = application:get_env(munin_node_sock_opts),
71 | Plugin = proplists:get_value(plugin, Config),
72 | {Host, Port, Opts, Plugin}.
73 |
74 | %% @doc Fetch values from socket connection to Munin node.
75 | fetch(Sock, Plugin) ->
76 | ok = gen_tcp:send(Sock, rolf_util:string_format("fetch ~p\n", [Plugin])),
77 | read_values(Sock).
78 |
79 | %% @doc Read values from Sock until . on it's own on a line is encountered.
80 | read_values(Sock) ->
81 | read_values(Sock, []).
82 | read_values(Sock, Values) ->
83 | case gen_tcp:recv(Sock, 0) of
84 | {ok, Data} ->
85 | case Data of
86 | ".\n" ->
87 | log4erl:debug("Munin node fetch complete: ~p", [Values]),
88 | Values;
89 | Line ->
90 | log4erl:debug("Munin node fetch got: ~p", [Line]),
91 | read_values(Sock, [parse_line(Line)|Values])
92 | end;
93 | {error, closed} ->
94 | Values
95 | end.
96 |
97 | %% @doc Parse a line returned by Munin node. E.g. "blah.value 99\n"
98 | parse_line(Line) ->
99 | K = list_to_atom(hd(string:tokens(Line, "."))),
100 | case rolf_util:list_to_num(hd(tl(string:tokens(Line, " ")))) of
101 | error -> {K, undefined};
102 | S -> {K, S}
103 | end.
104 |
105 | %% ===================================================================
106 | %% Tests
107 | %% ===================================================================
108 |
109 | -ifdef(TEST).
110 |
111 | parse_line_test() ->
112 | ?assertEqual({blah, 99}, parse_line("blah.value 99\n")),
113 | ?assertEqual({blah, undefined}, parse_line("blah.value U\n")).
114 |
115 | -endif.
116 |
--------------------------------------------------------------------------------
/apps/rolf/src/rolf.app.src:
--------------------------------------------------------------------------------
1 | %% vim: ft=erlang
2 | %% @doc Rolf app config.
3 | %% @author Ben Godfrey [http://aftnn.org/]
4 | %% @copyright 2011 Ben Godfrey
5 | %% @version 1.0.0
6 | %%
7 | %% Rolf - a monitoring and graphing tool like Munin or collectd.
8 | %% Copyright (C) 2011 Ben Godfrey.
9 | %%
10 | %% This program is free software: you can redistribute it and/or modify
11 | %% it under the terms of the GNU General Public License as published by
12 | %% the Free Software Foundation, either version 3 of the License, or
13 | %% (at your option) any later version.
14 | %%
15 | %% This program is distributed in the hope that it will be useful,
16 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | %% GNU General Public License for more details.
19 | %%
20 | %% You should have received a copy of the GNU General Public License
21 | %% along with this program. If not, see .
22 |
23 | {application, rolf, [
24 | {description, "System monitoring and graphing tool like Munin or collectd"},
25 | {vsn, "0.1"},
26 | {registered, []},
27 | {applications, [kernel, stdlib, sasl, log4erl]},
28 | {mod, {rolf_app, []}},
29 | {env, [{recorders, []},
30 | {services_config, "etc/services.config"},
31 | {log4erl_config, "etc/log4erl.conf"},
32 | {plugin_dir, "plugins"},
33 | {plugin_default_freq, 10},
34 | {plugin_default_timeout_multiple, 3},
35 | {plugin_default_archives, [{1, 360}, % 1hr of 10s averages
36 | {30, 288}, % 1d of 5m averages
37 | {180, 336}, % 7d of 30m averages
38 | {8640, 365}]}, % 1y of 1d averages
39 | {plugin_default_type, gauge},
40 | {plugin_default_draw, line},
41 | {rrd_dir, "data"},
42 | {rrd_ext, "rrd"},
43 | {munin_node_sock_opts, [list, {packet, line}, {active, false}]}]}]}.
44 |
--------------------------------------------------------------------------------
/apps/rolf/src/rolf.erl:
--------------------------------------------------------------------------------
1 | %% @doc Rolf startup.
2 | %% @author Ben Godfrey [http://aftnn.org/]
3 | %% @copyright 2011 Ben Godfrey
4 | %% @version 1.0.0
5 | %%
6 | %% Rolf - a monitoring and graphing tool like Munin or collectd.
7 | %% Copyright (C) 2011 Ben Godfrey.
8 | %%
9 | %% This program is free software: you can redistribute it and/or modify
10 | %% it under the terms of the GNU General Public License as published by
11 | %% the Free Software Foundation, either version 3 of the License, or
12 | %% (at your option) any later version.
13 | %%
14 | %% This program is distributed in the hope that it will be useful,
15 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | %% GNU General Public License for more details.
18 | %%
19 | %% You should have received a copy of the GNU General Public License
20 | %% along with this program. If not, see .
21 |
22 | -module(rolf).
23 |
24 | %% API
25 | -export([start/0, stop/0]).
26 |
27 | start() ->
28 | application:start(sasl),
29 | application:start(log4erl),
30 | application:start(rolf).
31 |
32 | stop() ->
33 | application:stop(rolf),
34 | application:stop(log4erl),
35 | application:stop(sasl).
36 |
--------------------------------------------------------------------------------
/apps/rolf/src/rolf_app.erl:
--------------------------------------------------------------------------------
1 | %% @doc Rolf app startup.
2 | %% @author Ben Godfrey [http://aftnn.org/]
3 | %% @copyright 2011 Ben Godfrey
4 | %% @version 1.0.0
5 | %%
6 | %% Rolf - a monitoring and graphing tool like Munin or collectd.
7 | %% Copyright (C) 2011 Ben Godfrey.
8 | %%
9 | %% This program is free software: you can redistribute it and/or modify
10 | %% it under the terms of the GNU General Public License as published by
11 | %% the Free Software Foundation, either version 3 of the License, or
12 | %% (at your option) any later version.
13 | %%
14 | %% This program is distributed in the hope that it will be useful,
15 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | %% GNU General Public License for more details.
18 | %%
19 | %% You should have received a copy of the GNU General Public License
20 | %% along with this program. If not, see .
21 |
22 | -module(rolf_app).
23 |
24 | -behaviour(application).
25 |
26 | %% Application callbacks
27 | -export([start/0, start/2, stop/1]).
28 |
29 | %% ===================================================================
30 | %% Application callbacks
31 | %% ===================================================================
32 |
33 | start() ->
34 | start(normal, undefined).
35 |
36 | start(_StartType, _StartArgs) ->
37 | configure_logger(),
38 | log4erl:info("Starting"),
39 | CResult = rolf_collector_sup:start_link(),
40 | case rolf_recorder:is_recorder() of
41 | true ->
42 | log4erl:info("~p is a recorder", [node()]),
43 | rolf_recorder_sup:start_link();
44 | _ ->
45 | log4erl:info("~p is a collector only", [node()]),
46 | announce_collector(),
47 | CResult
48 | end.
49 |
50 | stop(_State) ->
51 | ok.
52 |
53 | %% ===================================================================
54 | %% Utility functions
55 | %% ===================================================================
56 |
57 | %% @doc Load log4erl configuration.
58 | configure_logger() ->
59 | {ok, ConfigFilename} = application:get_env(log4erl_config),
60 | log4erl:conf(ConfigFilename).
61 |
62 | %% @doc Announce collector start to recorders.
63 | announce_collector() ->
64 | lists:foreach(fun net_adm:ping/1, rolf_recorder:recorders()).
65 |
--------------------------------------------------------------------------------
/apps/rolf/src/rolf_collector.erl:
--------------------------------------------------------------------------------
1 | %% @doc Define a behaviour for collectors.
2 | %% @author Ben Godfrey [http://aftnn.org/]
3 | %% @copyright 2011 Ben Godfrey
4 | %% @version 1.0.0
5 | %%
6 | %% Rolf - a monitoring and graphing tool like Munin or collectd.
7 | %% Copyright (C) 2011 Ben Godfrey.
8 | %%
9 | %% This program is free software: you can redistribute it and/or modify
10 | %% it under the terms of the GNU General Public License as published by
11 | %% the Free Software Foundation, either version 3 of the License, or
12 | %% (at your option) any later version.
13 | %%
14 | %% This program is distributed in the hope that it will be useful,
15 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | %% GNU General Public License for more details.
18 | %%
19 | %% You should have received a copy of the GNU General Public License
20 | %% along with this program. If not, see .
21 |
22 | -module(rolf_collector).
23 |
24 | %% Behaviour definition callbacks
25 | -export([behaviour_info/1]).
26 |
27 | %% @doc Return behaviour information.
28 | behaviour_info(callbacks) -> [{start, 1}, {collect, 2}, {stop, 2}];
29 | behaviour_info(_) -> undefined.
30 |
--------------------------------------------------------------------------------
/apps/rolf/src/rolf_collector_sup.erl:
--------------------------------------------------------------------------------
1 | %% @doc Rolf collector node supervisor.
2 | %% @author Ben Godfrey [http://aftnn.org/]
3 | %% @copyright 2011 Ben Godfrey
4 | %% @version 1.0.0
5 | %%
6 | %% Rolf - a monitoring and graphing tool like Munin or collectd.
7 | %% Copyright (C) 2011 Ben Godfrey.
8 | %%
9 | %% This program is free software: you can redistribute it and/or modify
10 | %% it under the terms of the GNU General Public License as published by
11 | %% the Free Software Foundation, either version 3 of the License, or
12 | %% (at your option) any later version.
13 | %%
14 | %% This program is distributed in the hope that it will be useful,
15 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | %% GNU General Public License for more details.
18 | %%
19 | %% You should have received a copy of the GNU General Public License
20 | %% along with this program. If not, see .
21 |
22 | -module(rolf_collector_sup).
23 |
24 | -behaviour(supervisor).
25 |
26 | %% API
27 | -export([start_link/0, start_services/1]).
28 |
29 | %% supervisor callbacks
30 | -export([init/1]).
31 |
32 | -include("rolf.hrl").
33 |
34 | %% ===================================================================
35 | %% API functions
36 | %% ===================================================================
37 |
38 | start_link() ->
39 | supervisor:start_link({local, ?MODULE}, ?MODULE, []).
40 |
41 | %% ===================================================================
42 | %% Supervisor callbacks
43 | %% ===================================================================
44 |
45 | %% @doc Collector supervisor initially does nothing. The recorder will send
46 | %% configuration.
47 | init([]) ->
48 | SupFlags = {simple_one_for_one, 1, 10},
49 | ChildTemplate = {rolf_service,
50 | {rolf_service, start_link, []},
51 | permanent, 2000, worker, [rolf_service]},
52 | {ok, {SupFlags, [ChildTemplate]}}.
53 |
54 | %% @doc Start a set of service process. Called by
55 | %% rolf_recorder:start_collectors.
56 | start_services([]) -> ok;
57 | start_services([S|Services]) ->
58 | log4erl:info("Starting ~p service on ~p", [S#service.name, node()]),
59 | supervisor:start_child(?MODULE, [S]),
60 | rolf_service:start_emitting(S#service.name),
61 | start_services(Services).
62 |
--------------------------------------------------------------------------------
/apps/rolf/src/rolf_config_validation.erl:
--------------------------------------------------------------------------------
1 | %% Look at lists:concat!
2 |
3 | %% Validate plugin values
4 |
5 | %% @doc Validate a list of values returned by a plugin.
6 | values_ok(Values, Metrics) when is_list(Values) ->
7 | lists:foldl(fun(V) -> value_ok(V, Metrics), Values);
8 | values_ok(_, _Metrics) -> false.
9 |
10 | %% @doc Validate a single value returned by a plugin.
11 | value_ok({Name, Value}, Metrics) when is_atom(Name) and is_int(Value) ->
12 | lists:member(Name, Metrics);
13 | value_ok(_, _Metrics) -> false.
14 |
15 | %% Validate (normalised) configuration
16 |
17 | %% - Node is atom()
18 | %% - Plugins all exist
19 | %% - Service names are unique
20 | %% - Options is [{atom(), term()}]
21 |
--------------------------------------------------------------------------------
/apps/rolf/src/rolf_plugin.erl:
--------------------------------------------------------------------------------
1 | %% @doc Represent a service plugin, which gathers data.
2 | %% @author Ben Godfrey [http://aftnn.org/]
3 | %% @copyright 2011 Ben Godfrey
4 | %% @version 1.0.0
5 | %%
6 | %% Rolf - a monitoring and graphing tool like Munin or collectd.
7 | %% Copyright (C) 2011 Ben Godfrey.
8 | %%
9 | %% This program is free software: you can redistribute it and/or modify
10 | %% it under the terms of the GNU General Public License as published by
11 | %% the Free Software Foundation, either version 3 of the License, or
12 | %% (at your option) any later version.
13 | %%
14 | %% This program is distributed in the hope that it will be useful,
15 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | %% GNU General Public License for more details.
18 | %%
19 | %% You should have received a copy of the GNU General Public License
20 | %% along with this program. If not, see .
21 |
22 | -module(rolf_plugin).
23 |
24 | %% API
25 | -export([list/0, load/2]).
26 |
27 | -include("rolf.hrl").
28 |
29 | %% ===================================================================
30 | %% Configuration
31 | %% ===================================================================
32 |
33 | %% @doc List available plugins.
34 | list() ->
35 | {ok, PluginDir} = application:get_env(plugin_dir),
36 | list(PluginDir).
37 |
38 | %% @doc List available plugins in Dir.
39 | list(Dir) ->
40 | ConfigPat = filename:join([Dir, "*", "*.config"]),
41 | Configs = filelib:wildcard(ConfigPat),
42 | [configfilename_to_atom(C) || C <- Configs].
43 |
44 | %% @doc Translate a config file pathname to an atom
45 | configfilename_to_atom(CFName) ->
46 | list_to_atom(filename:rootname(filename:basename(CFName))).
47 |
48 | %% @doc Load plugin config from file.
49 | load(Plugin, Opts) ->
50 | {ok, Config} = file:consult(config_path(Plugin)),
51 | parse(Plugin, propmerge(Config, Opts)).
52 |
53 | %% @doc Get path to a plugin's config file.
54 | config_path(Plugin) ->
55 | PluginStr = atom_to_list(Plugin),
56 | CfgName = string:join([PluginStr, "config"], "."),
57 | {ok, PluginDir} = application:get_env(plugin_dir),
58 | filename:join([PluginDir, PluginStr, CfgName]).
59 |
60 | %% @doc Parse config file contents into a service record.
61 | parse(Plugin, Config) ->
62 | {ok, PluginDefaultFreq} = application:get_env(plugin_default_freq),
63 | {ok, PluginDefaultTimeoutMultiple} = application:get_env(plugin_default_timeout_multiple),
64 | {ok, PluginDefaultArchives} = application:get_env(plugin_default_archives),
65 | Freq = proplists:get_value(frequency, Config, PluginDefaultFreq),
66 | #service{
67 | plugin=Plugin,
68 | module=proplists:get_value(module, Config, rolf_command),
69 | command=parse_command(Plugin, Config),
70 | frequency=Freq,
71 | timeout=proplists:get_value(timeout, Config, Freq * PluginDefaultTimeoutMultiple),
72 | archives=proplists:get_value(archives, Config, PluginDefaultArchives),
73 | graph_title=proplists:get_value(graph_title, Config, atom_to_list(Plugin)),
74 | graph_vlabel=proplists:get_value(graph_vlabel, Config, ""),
75 | metrics=parse_metrics(Config),
76 | config=Config
77 | }.
78 |
79 | %% @doc Parse the command for this plugin.
80 | parse_command(Plugin, Config) ->
81 | case proplists:get_value(command, Config, undefined) of
82 | undefined ->
83 | undefined;
84 | Cmd ->
85 | external_path(Plugin, Cmd)
86 | end.
87 |
88 | %% @doc Return the full path to an external program.
89 | external_path(Plugin, Cmd) ->
90 | {ok, PluginDir} = application:get_env(plugin_dir),
91 | filename:join([PluginDir, atom_to_list(Plugin), Cmd]).
92 |
93 | %% @doc Parse config for a list of metrics into list of metric records.
94 | parse_metrics(Config) ->
95 | MetricCfg = proplists:get_value(metrics, Config, []),
96 | [parse_metric(M) || M <- MetricCfg].
97 |
98 | %% @doc Parse config for a single metric into a metric record.
99 | parse_metric({Metric, MetricCfg}) ->
100 | {ok, PluginDefaultType} = application:get_env(plugin_default_type),
101 | {ok, PluginDefaultDraw} = application:get_env(plugin_default_draw),
102 | #metric{
103 | name=Metric,
104 | label=proplists:get_value(label, MetricCfg, ""),
105 | type=proplists:get_value(type, MetricCfg, PluginDefaultType),
106 | draw=proplists:get_value(draw, MetricCfg, PluginDefaultDraw),
107 | min=proplists:get_value(min, MetricCfg, undefined),
108 | max=proplists:get_value(max, MetricCfg, undefined),
109 | colour=proplists:get_value(colour, MetricCfg, undefined)
110 | }.
111 |
112 | propmerge(L1, L2) ->
113 | dict:to_list(dict:merge(fun(_K, _V1, V2) -> V2 end, dict:from_list(L1), dict:from_list(L2))).
114 |
115 | %% ===================================================================
116 | %% Tests
117 | %% ===================================================================
118 |
119 | -ifdef(TEST).
120 |
121 | configfilename_to_atom_test() ->
122 | ?assertEqual(disk, configfilename_to_atom("plugins/disk/disk.config")).
123 |
124 | config_path_test() ->
125 | {ok, PluginDir} = application:get_env(plugin_dir),
126 | Path = filename:join([PluginDir, "loadtime", "loadtime.config"]),
127 | ?assertEqual(Path, config_path(loadtime)).
128 |
129 | parse_test() ->
130 | Input = [{command, "loadtime.sh"},
131 | {frequency, 10},
132 | {graph_title, "Load Time"},
133 | {graph_vlabel, "Secs"},
134 | {metrics, [{loadtime, [{label, "Load Time"},
135 | {type, gauge},
136 | {draw, areastack},
137 | {min, 0},
138 | {colour, "#0091FF"}]}]}],
139 | Output = parse(loadtime, Input),
140 | ?assertEqual(loadtime, Output#service.plugin),
141 | ?assertEqual(undefined, Output#service.name),
142 | ?assertEqual(10, Output#service.frequency),
143 | ?assertEqual("plugins/loadtime/loadtime.sh", Output#service.command),
144 | ?assertEqual(rolf_command, Output#service.module).
145 |
146 | parse_options_test() ->
147 | Input = [{command, "loadtime.sh"},
148 | {unit, mb}],
149 | Output = parse(loadtime, Input),
150 | ?assertEqual(mb, proplists:get_value(unit, Output#service.config)).
151 |
152 | propmerge_test() ->
153 | ?assertEqual([{a, 1}, {b, 2}], propmerge([{a, 1}], [{b, 2}])),
154 | ?assertEqual([{a, 2}], propmerge([{a, 1}], [{a, 2}])).
155 |
156 | -endif.
157 |
--------------------------------------------------------------------------------
/apps/rolf/src/rolf_recorder.erl:
--------------------------------------------------------------------------------
1 | %% @doc gen_server to which services can send samples for recording.
2 | %% @author Ben Godfrey [http://aftnn.org/]
3 | %% @copyright 2011 Ben Godfrey
4 | %% @version 1.0.0
5 | %%
6 | %% Rolf - a monitoring and graphing tool like Munin or collectd.
7 | %% Copyright (C) 2011 Ben Godfrey.
8 | %%
9 | %% This program is free software: you can redistribute it and/or modify
10 | %% it under the terms of the GNU General Public License as published by
11 | %% the Free Software Foundation, either version 3 of the License, or
12 | %% (at your option) any later version.
13 | %%
14 | %% This program is distributed in the hope that it will be useful,
15 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | %% GNU General Public License for more details.
18 | %%
19 | %% You should have received a copy of the GNU General Public License
20 | %% along with this program. If not, see .
21 |
22 | -module(rolf_recorder).
23 |
24 | -behaviour(gen_server).
25 |
26 | %% API
27 | -export([config/0, recorders/0, live_recorders/0, is_recorder/0, start_link/0,
28 | stop/0, store/1]).
29 |
30 | %% gen_server callbacks
31 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
32 | code_change/3]).
33 |
34 | -include("rolf.hrl").
35 |
36 | %% ===================================================================
37 | %% API
38 | %% ===================================================================
39 |
40 | %% @doc Load configuration of recorders, collectors and services.
41 | config() ->
42 | {ok, ConfigFilename} = application:get_env(services_config),
43 | log4erl:debug("Loading config from ~p", [ConfigFilename]),
44 | case catch file:consult(ConfigFilename) of
45 | {ok, Config} ->
46 | log4erl:debug("Config: ~p", [Config]),
47 | Config;
48 | Else ->
49 | log4erl:error("Couldn't parse config file ~p: ~p", [ConfigFilename, Else])
50 | end.
51 |
52 | %% @doc Return list of recorders.
53 | recorders() ->
54 | case application:get_env(recorders) of
55 | {ok, Recorders} -> Recorders;
56 | _ -> []
57 | end.
58 |
59 | %% @doc Return list of live recorders.
60 | live_recorders() ->
61 | [R || R <- recorders(), net_adm:ping(R) =:= pong].
62 |
63 | %% @doc Return true if current node is a recorder.
64 | is_recorder() ->
65 | lists:member(node(), recorders()).
66 |
67 | %% @doc Start a recorder on this node.
68 | start_link() ->
69 | gen_server:start_link({global, ?MODULE}, ?MODULE, [], []).
70 |
71 | %% @doc Stop recorder.
72 | stop() ->
73 | gen_server:call({global, ?MODULE}, stop).
74 |
75 | %% @doc Pass samples to all recorders in the cluster.
76 | store(Sample) ->
77 | gen_server:cast({global, ?MODULE}, {store, Sample}).
78 |
79 | %% ===================================================================
80 | %% gen_server callbacks
81 | %% ===================================================================
82 |
83 | init([]) ->
84 | process_flag(trap_exit, true),
85 | Config = rolf_recorder:config(),
86 |
87 | % start errd_server
88 | case errd_server:start_link() of
89 | {ok, RRD} ->
90 | log4erl:debug("Started errd_server"),
91 | Collectors = parse_collector_config(Config),
92 | start_collectors(RRD, Collectors),
93 | net_kernel:monitor_nodes(true),
94 | log4erl:info("Recorder started"),
95 | {ok, #recorder{collectors=Collectors, rrd=RRD}};
96 | Else ->
97 | log4erl:error("Error starting errd_server: ~p", [Else]),
98 | {stop, Else}
99 | end.
100 |
101 | %% @doc Log unhandled calls.
102 | handle_call(Req, From, Service) ->
103 | log4erl:debug("Unhandled call from ~p: ~p", [From, Req]),
104 | {reply, Service}.
105 |
106 | handle_cast({store, Sample}, #recorder{rrd=RRD}=State) ->
107 | Service = Sample#sample.service,
108 | log4erl:debug("~p sample from ~p, values: ~p", [Service#service.name, Sample#sample.node, Sample#sample.values]),
109 | rolf_rrd:update(RRD, Sample),
110 | {noreply, State};
111 |
112 | %% @doc Log unhandled casts.
113 | handle_cast(Req, Service) ->
114 | log4erl:debug("Unhandled cast: ~p", [Req]),
115 | {noreply, Service}.
116 |
117 | %% @doc Handle nodeup messages from monitoring nodes. Start services if the node
118 | %% is a collector and this is the highest priority live recorder (first in the
119 | %% list from app.config).
120 | handle_info({nodeup, Node}, #recorder{collectors=Collectors, rrd=RRD}=State) ->
121 | log4erl:info("Node ~p up", [Node]),
122 | Primary = hd(live_recorders()),
123 | case node() of
124 | Primary ->
125 | case lists:keyfind(Node, 1, Collectors) of
126 | {N, Ss} ->
127 | log4erl:info("Configured node ~p", [Node]),
128 | start_services(N, Ss, RRD)
129 | end;
130 | _ ->
131 | noop
132 | end,
133 | {noreply, State};
134 |
135 | %% @doc Handle nodedown messages from monitoring nodes.
136 | handle_info({nodedown, Node}, State) ->
137 | log4erl:info("Node ~p down", [Node]),
138 | {noreply, State};
139 |
140 | %% @doc Log unhandled info messages.
141 | handle_info(Info, Service) ->
142 | log4erl:debug("Unhandled info: ~p", [Info]),
143 | {noreply, Service}.
144 |
145 | terminate(_Reason, #recorder{rrd=RRD}) ->
146 | errd_server:stop(RRD),
147 | log4erl:info("Recorder stopped").
148 |
149 | code_change(_OldVsn, State, _Extra) -> {ok, State}.
150 |
151 | %% ===================================================================
152 | %% Utility functions
153 | %% ===================================================================
154 |
155 | %% @doc Parse node service definitions from services.config. Return list of
156 | %% {Node, Service} tuples.
157 | %% @spec parse_collector_config([term()]) -> [{node(), [{Plugin, Name, Opts}]}]
158 | parse_collector_config(Defs) ->
159 | parse_collector_config(Defs, []).
160 |
161 | %% @doc Parse node service defintions and populate accumulator. Don't use this
162 | %% function, call parse_collector_config/1.
163 | parse_collector_config([{node, Node, Services}|Defs], Acc) ->
164 | NormalisedServices = [normalise_service_config(S) || S <- Services],
165 | parse_collector_config(Defs, [{Node, NormalisedServices}|Acc]);
166 | parse_collector_config([_|Defs], Acc) ->
167 | parse_collector_config(Defs, Acc);
168 | parse_collector_config([], Acc) ->
169 | Acc.
170 |
171 | %% @doc Normalise various short-hand versions of service definition accepted in
172 | %% services.config.
173 | normalise_service_config(Plugin) when is_atom(Plugin) ->
174 | {Plugin, Plugin, []};
175 | normalise_service_config({Plugin}) ->
176 | {Plugin, Plugin, []};
177 | normalise_service_config({Plugin, Opts}) when is_list(Opts) ->
178 | {Plugin, Plugin, Opts};
179 | normalise_service_config({Plugin, Name}) when is_atom(Name) ->
180 | {Plugin, Name, []};
181 | normalise_service_config({Plugin, Name, Opts}) when is_atom(Name) and is_list(Opts) ->
182 | {Plugin, Name, Opts}.
183 |
184 | %% @doc Ping collector nodes and give them service configuration.
185 | start_collectors(RRD, Collectors) ->
186 | LiveCollectors = connect_cluster(Collectors),
187 | log4erl:info("Starting collectors"),
188 | lists:foreach(fun({N, Ss}) -> start_services(RRD, N, Ss) end, LiveCollectors).
189 |
190 | %% @doc Ping nodes that we're expected to record from.
191 | connect_cluster(Config) ->
192 | [{N, S} || {N, S} <- Config, net_adm:ping(N) =:= pong].
193 |
194 | %% @doc Start collectors on a set of nodes.
195 | start_services(RRD, Node, SDefs) ->
196 | Recs = live_recorders(),
197 | Services = [load_service(Plugin, Name, Opts, Recs) || {Plugin, Name, Opts} <- SDefs],
198 | lists:foreach(fun(S) -> rolf_rrd:ensure(RRD, Node, S) end, Services),
199 | rpc:call(Node, rolf_collector_sup, start_services, [Services]).
200 |
201 | load_service(Plugin, Name, Opts, Recs) ->
202 | S = rolf_plugin:load(Plugin, Opts),
203 | S#service{name=Name, recorders=Recs}.
204 |
--------------------------------------------------------------------------------
/apps/rolf/src/rolf_recorder_sup.erl:
--------------------------------------------------------------------------------
1 | %% @doc Rolf supervisor configuration.
2 | %% @author Ben Godfrey [http://aftnn.org/]
3 | %% @copyright 2011 Ben Godfrey
4 | %% @version 1.0.0
5 | %%
6 | %% Rolf - a monitoring and graphing tool like Munin or collectd.
7 | %% Copyright (C) 2011 Ben Godfrey.
8 | %%
9 | %% This program is free software: you can redistribute it and/or modify
10 | %% it under the terms of the GNU General Public License as published by
11 | %% the Free Software Foundation, either version 3 of the License, or
12 | %% (at your option) any later version.
13 | %%
14 | %% This program is distributed in the hope that it will be useful,
15 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | %% GNU General Public License for more details.
18 | %%
19 | %% You should have received a copy of the GNU General Public License
20 | %% along with this program. If not, see .
21 |
22 | -module(rolf_recorder_sup).
23 |
24 | -behaviour(supervisor).
25 |
26 | %% API
27 | -export([start_link/0]).
28 |
29 | %% supervisor callbacks
30 | -export([init/1]).
31 |
32 | -include("rolf.hrl").
33 |
34 | %% ===================================================================
35 | %% API functions
36 | %% ===================================================================
37 |
38 | start_link() ->
39 | supervisor:start_link({local, ?MODULE}, ?MODULE, []).
40 |
41 | %% ===================================================================
42 | %% Supervisor callbacks
43 | %% ===================================================================
44 |
45 | %% @doc Supervisor configuration for rolf recorder. Start rolf_recorder
46 | %% gen_server which will start an errd_server.
47 | init([]) ->
48 | Recorder = {rolf_recorder, {rolf_recorder, start_link, []},
49 | permanent, 2000, worker, [rolf_recorder]},
50 | {ok, {{one_for_one, 1, 10}, [Recorder]}}.
51 |
--------------------------------------------------------------------------------
/apps/rolf/src/rolf_rrd.erl:
--------------------------------------------------------------------------------
1 | %% @doc Utilities for managing RRD files with errd.
2 | %% @author Ben Godfrey [http://aftnn.org/]
3 | %% @copyright 2011 Ben Godfrey
4 | %% @version 1.0.0
5 | %%
6 | %% Rolf - a monitoring and graphing tool like Munin or collectd.
7 | %% Copyright (C) 2011 Ben Godfrey.
8 | %%
9 | %% This program is free software: you can redistribute it and/or modify
10 | %% it under the terms of the GNU General Public License as published by
11 | %% the Free Software Foundation, either version 3 of the License, or
12 | %% (at your option) any later version.
13 | %%
14 | %% This program is distributed in the hope that it will be useful,
15 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | %% GNU General Public License for more details.
18 | %%
19 | %% You should have received a copy of the GNU General Public License
20 | %% along with this program. If not, see .
21 |
22 | -module(rolf_rrd).
23 |
24 | %% API
25 | -export([ensure/3, update/2]).
26 |
27 | -include_lib("errd/include/errd.hrl").
28 | -include("rolf.hrl").
29 |
30 | %% ===================================================================
31 | %% API
32 | %% ===================================================================
33 |
34 | %% @doc Ensure data dir and RRD file for Service on Node exist.
35 | ensure(RRD, Node, Service) ->
36 | Path = rrd_path(Node, Service),
37 | case filelib:ensure_dir(Path) of
38 | {error, Reason} ->
39 | log4erl:error("Couldn't create RRD dir ~p: ~p", [Path, Reason]),
40 | {error, Reason};
41 | ok ->
42 | case filelib:is_file(Path) of
43 | false -> create(RRD, Path, Service);
44 | true -> ok
45 | end
46 | end.
47 |
48 | %% @doc Create an RRD file for a service. Metrics should be a list of
49 | %% {Name, Type} tuples, e.g. [{signups, counter}, {downloads, counter}]. Type
50 | %% must be one of gauge, counter, derive or absolute.
51 | create(RRD, Path, Service) ->
52 | log4erl:info("Creating ~p", [Path]),
53 | send_command(RRD, make_rrd_create(Path, Service)).
54 |
55 | %% @doc Update an RRD file with a new sample.
56 | update(RRD, #sample{node=Node, service=Service, values=Values}) ->
57 | Path = rrd_path(Node, Service),
58 | send_command(RRD, make_update(Path, Values)).
59 |
60 | %% ===================================================================
61 | %% Utility functions
62 | %% ===================================================================
63 |
64 | %% @doc Send command to RRD server, return ok or {error, Reason}.
65 | send_command(RRD, Cmd) ->
66 | log4erl:debug("RRD command: ~p", [Cmd]),
67 | FormattedCmd = errd_command:format(Cmd),
68 | case errd_server:raw(RRD, FormattedCmd) of
69 | {error, Reason} ->
70 | log4erl:error("errd_server error: ~p", [Reason]),
71 | {error, Reason};
72 | {ok, _Lines} ->
73 | ok
74 | end.
75 |
76 | %% @doc Create absolute path for RRD file for Service running on Node. A single
77 | %% RRD file contains values for multiple metrics (data sources).
78 | rrd_path(Node, Service) ->
79 | {ok, RRDExt} = application:get_env(rrd_ext),
80 | Filename = string:join([atom_to_list(Service#service.name), RRDExt], "."),
81 | {ok, RRDDir} = application:get_env(rrd_dir),
82 | filename:join([RRDDir, atom_to_list(Node), Filename]).
83 |
84 | %% @doc Generate command to create an RRD with a set of metrics.
85 | make_rrd_create(Path, #service{frequency=Frequency, timeout=Timeout,
86 | archives=Archives, metrics=Metrics}) ->
87 | RRAs = [make_rra(S, C) || {S, C} <- Archives],
88 | DSs = [make_ds(M, Timeout) || M <- Metrics],
89 | #rrd_create{file=Path, step=Frequency, ds_defs=DSs, rra_defs=RRAs}.
90 |
91 | %% @doc Create an average rrd_rra record from a step and a count.
92 | make_rra(Step, Count) ->
93 | #rrd_rra{cf=average, args=rolf_util:string_format("0.5:~b:~b", [Step, Count])}.
94 |
95 | %% @doc Make a rrd_ds record from a metric definition.
96 | make_ds(#metric{name=Name, type=Type}, Timeout) ->
97 | #rrd_ds{name=atom_to_list(Name), type=Type, args=rolf_util:string_format("~b:U:U", [Timeout])}.
98 |
99 | %% @doc Make an rrd_update record from an RRD path and a set of values.
100 | make_update(Path, Values) ->
101 | Updates = [make_ds_update(M, V) || {M, V} <- Values],
102 | #rrd_update{file=Path, updates=Updates}.
103 |
104 | %% @doc Make an rrd_ds_update record for a measurement.
105 | make_ds_update(Metric, Value) ->
106 | #rrd_ds_update{name=atom_to_list(Metric), value=mochinum:digits(Value)}.
107 |
108 | %% ===================================================================
109 | %% Tests
110 | %% ===================================================================
111 |
112 | -ifdef(TEST).
113 |
114 | rrd_path_test() ->
115 | {ok, RRDDir} = application:get_env(rrd_dir),
116 | Path = rrd_path(frank@josie, #service{name=loadtime}),
117 | ?assertEqual(filename:join([RRDDir, "frank@josie", "loadtime.rrd"]), Path).
118 |
119 | make_rrd_create_test() ->
120 | {ok, RRDDir} = application:get_env(rrd_dir),
121 | Path = filename:join([RRDDir, "frank@josie", "loadtime.rrd"]),
122 | DSs = [#rrd_ds{name="loadtime", type=gauge, args="900:U:U"}],
123 | RRAs = [#rrd_rra{cf=average, args="0.5:1:60"}],
124 | Metrics = [#metric{name=loadtime, type=gauge}],
125 | Create = make_rrd_create(Path, #service{frequency=60, timeout=900,
126 | archives=[{1, 60}], metrics=Metrics}),
127 | ?assertEqual(#rrd_create{file=Path, step=60, ds_defs=DSs, rra_defs=RRAs}, Create).
128 |
129 | make_update_test() ->
130 | {ok, RRDDir} = application:get_env(rrd_dir),
131 | Path = filename:join([RRDDir, "frank@josie", "loadtime.rrd"]),
132 | Update = make_update(Path, [{loadtime, 0.99}]),
133 | DSUpdates = [#rrd_ds_update{name="loadtime", value=0.99}],
134 | Expected = #rrd_update{file=Path, updates=DSUpdates},
135 | ?assertEqual(Expected, Update).
136 |
137 | -endif.
138 |
--------------------------------------------------------------------------------
/apps/rolf/src/rolf_service.erl:
--------------------------------------------------------------------------------
1 | %% @doc gen_server which provides monitoring information for a single service on
2 | %% a machine.
3 | %% @author Ben Godfrey [http://aftnn.org/]
4 | %% @copyright 2011 Ben Godfrey
5 | %% @version 1.0.0
6 | %%
7 | %% Rolf - a monitoring and graphing tool like Munin or collectd.
8 | %% Copyright (C) 2011 Ben Godfrey.
9 | %%
10 | %% This program is free software: you can redistribute it and/or modify
11 | %% it under the terms of the GNU General Public License as published by
12 | %% the Free Software Foundation, either version 3 of the License, or
13 | %% (at your option) any later version.
14 | %%
15 | %% This program is distributed in the hope that it will be useful,
16 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | %% GNU General Public License for more details.
19 | %%
20 | %% You should have received a copy of the GNU General Public License
21 | %% along with this program. If not, see .
22 |
23 | -module(rolf_service).
24 |
25 | -behaviour(gen_server).
26 |
27 | %% API
28 | -export([start_link/1, stop/1, publish/1, start_emitting/1, stop_emitting/1]).
29 |
30 | %% gen_server callbacks
31 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
32 | code_change/3]).
33 |
34 | -include("rolf.hrl").
35 |
36 | %% ===================================================================
37 | %% API
38 | %% ===================================================================
39 |
40 | %% @doc Start a service using Service as initial state.
41 | start_link(Service) ->
42 | gen_server:start_link({local, server_name(Service)}, ?MODULE, [Service], []).
43 |
44 | %% @doc Stop service Name.
45 | stop(Name) ->
46 | gen_server:call(server_name(Name), stop).
47 |
48 | %% @doc Start emitting samples. Emit one straight away and then set a timer to
49 | %% emit regularly.
50 | start_emitting(Name) ->
51 | gen_server:cast(server_name(Name), start_emitting).
52 |
53 | %% @doc Stop emitting samples.
54 | stop_emitting(Name) ->
55 | gen_server:cast(server_name(Name), stop_emitting).
56 |
57 | %% @doc Trigger polling of this service manually, useful for inspecting and debugging
58 | publish(Name) ->
59 | gen_server:cast(server_name(Name), publish).
60 |
61 | %% ===================================================================
62 | %% gen_server callbacks
63 | %% ===================================================================
64 |
65 | %% @doc Start service, create a timer which will sample for results regularly and
66 | %% publish them to the recorder.
67 | init([Service]) ->
68 | process_flag(trap_exit, true),
69 | net_kernel:monitor_nodes(true),
70 | CState = apply(Service#service.module, start, [Service]),
71 | {ok, {Service, CState}}.
72 |
73 | %% @doc Log unhandled calls.
74 | handle_call(Req, From, State) ->
75 | log4erl:debug("Unhandled call from ~p: ~p", [From, Req]),
76 | {reply, State}.
77 |
78 | handle_cast(start_emitting, {Service, CState}) ->
79 | Name = Service#service.name,
80 | Freq = Service#service.frequency,
81 | log4erl:info("~p started emitting (frequency ~p)", [Name, Freq]),
82 | apply(?MODULE, publish, [Name]),
83 | case timer:apply_interval(timer:seconds(Freq), ?MODULE, publish, [Name]) of
84 | {ok, TRef} ->
85 | {noreply, {Service#service{tref=TRef}, CState}};
86 | _ ->
87 | {noreply, {Service, CState}}
88 | end;
89 |
90 | handle_cast(stop_emitting, {Service, CState}) ->
91 | log4erl:info("~p stopped emitting", [Service#service.name]),
92 | timer:cancel(Service#service.tref),
93 | {noreply, {#service{tref=undefined}, CState}};
94 |
95 | handle_cast(publish, {Service, CState}) ->
96 | {CState1, Sample} = apply(Service#service.module, collect, [Service, CState]),
97 | send(Service#service.recorders, Sample),
98 | {noreply, {Service, CState1}};
99 |
100 | %% @doc Log unhandled casts.
101 | handle_cast(Req, State) ->
102 | log4erl:debug("Unhandled cast: ~p", [Req]),
103 | {noreply, State}.
104 |
105 | %% @doc Handle nodeup messages from monitoring nodes.
106 | handle_info({nodeup, Node}, {Service, CState}) ->
107 | case lists:member(Node, rolf_recorder:recorders()) of
108 | true ->
109 | log4erl:info("Recorder ~p up", [Node]),
110 | OldRecs = Service#service.recorders,
111 | {noreply, {Service#service{recorders=[Node|OldRecs]}, CState}};
112 | _ ->
113 | {noreply, {Service, CState}}
114 | end;
115 |
116 | %% @doc Handle nodedown messages from monitoring nodes.
117 | handle_info({nodedown, Node}, {Service, CState}) ->
118 | Live = rolf_recorder:live_recorders(),
119 | case Live of
120 | [] ->
121 | log4erl:info("Recorder ~p down, exiting", [Node]),
122 | {stop, no_recorders, {Service, CState}};
123 | _ ->
124 | log4erl:info("Recorder ~p down", [Node]),
125 | {noreply, Service#service{recorders=Live}}
126 | end;
127 |
128 | %% @doc Log unhandled info messages.
129 | handle_info(Info, State) ->
130 | log4erl:debug("Unhandled info: ~p", [Info]),
131 | {noreply, State}.
132 |
133 | %% @doc Terminate
134 | terminate(_Reason, {Service, CState}) ->
135 | stop_emitting(Service),
136 | Module = Service#service.module,
137 | apply(Module, stop, [Service, CState]),
138 | ok.
139 |
140 | code_change(_OldVsn, State, _Extra) -> {ok, State}.
141 |
142 | %% ===================================================================
143 | %% Utility functions
144 | %% ===================================================================
145 |
146 | %% @doc Get canonical name of service from name atom or service record.
147 | server_name(Name) when is_atom(Name) ->
148 | list_to_atom(string:join([atom_to_list(A) || A <- [?MODULE, Name]], "_"));
149 | server_name(#service{name=Name}) ->
150 | server_name(Name).
151 |
152 | %% @doc Send sample to all live recorders.
153 | send(Recorders, Sample) ->
154 | lists:foreach(fun(R) -> rpc:call(R, rolf_recorder, store, [Sample]) end,
155 | Recorders).
156 |
157 | %% ===================================================================
158 | %% Tests
159 | %% ===================================================================
160 |
161 | -ifdef(TEST).
162 |
163 | server_name_test() ->
164 | Name = list_to_atom("rolf_service_loadtime"),
165 | ?assertEqual(Name, server_name(loadtime)),
166 | ?assertEqual(Name, server_name(#service{name=loadtime})).
167 |
168 | -endif.
169 |
--------------------------------------------------------------------------------
/apps/rolf/src/rolf_util.erl:
--------------------------------------------------------------------------------
1 | %% @doc Plugin module for retrieving measurements from a munin node.
2 | %% @author Ben Godfrey [http://aftnn.org/]
3 | %% @copyright 2011 Ben Godfrey
4 | %% @version 1.0.0
5 | %%
6 | %% Rolf - a monitoring and graphing tool like Munin or collectd.
7 | %% Copyright (C) 2011 Ben Godfrey.
8 | %%
9 | %% This program is free software: you can redistribute it and/or modify
10 | %% it under the terms of the GNU General Public License as published by
11 | %% the Free Software Foundation, either version 3 of the License, or
12 | %% (at your option) any later version.
13 | %%
14 | %% This program is distributed in the hope that it will be useful,
15 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | %% GNU General Public License for more details.
18 | %%
19 | %% You should have received a copy of the GNU General Public License
20 | %% along with this program. If not, see .
21 |
22 | -module(rolf_util).
23 |
24 | %% rolf_collector callbacks
25 | -export([string_format/2, list_to_num/1, strip_whitespace/1]).
26 |
27 | -include("rolf.hrl").
28 |
29 | %% ===================================================================
30 | %% Utility functions
31 | %% ===================================================================
32 |
33 | %% @doc Sane string formatting.
34 | string_format(Pattern, Values) ->
35 | lists:flatten(io_lib:format(Pattern, Values)).
36 |
37 | %% @doc Coerce a string to a float or an integer.
38 | list_to_num(S) ->
39 | S1 = strip_whitespace(S),
40 | try list_to_float(S1) catch
41 | error:badarg ->
42 | try list_to_integer(S1) catch
43 | error:badarg -> error
44 | end
45 | end.
46 |
47 | %% @doc Strip surrounding whitespace from a string.
48 | strip_whitespace(S) ->
49 | strip_whitespace(S, []).
50 | strip_whitespace([C|Cs], S) when C == $\s; C == $\n; C == $\r; C == $\t ->
51 | S ++ strip_whitespace(Cs);
52 | strip_whitespace([C|Cs], S) ->
53 | S ++ [C|strip_whitespace(Cs)];
54 | strip_whitespace([], S) ->
55 | S.
56 |
57 | %% ===================================================================
58 | %% Tests
59 | %% ===================================================================
60 |
61 | -ifdef(TEST).
62 |
63 | string_format_test() ->
64 | ?assertEqual("X:1:9.5:z", string_format("~s:~b:~.1f:~p", ["X", 1, 9.5, z])).
65 |
66 | list_to_num_test() ->
67 | ?assertEqual(99, list_to_num("99")),
68 | ?assertEqual(-1, list_to_num("-1")),
69 | ?assertEqual(0.999, list_to_num("0.999")),
70 | ?assertEqual(-3.14, list_to_num("-3.14")),
71 | ?assertEqual(99, list_to_num(" 99\n ")),
72 | ?assertEqual(error, list_to_num("monkey")).
73 |
74 | strip_whitespace_test() ->
75 | ?assertEqual("blah", strip_whitespace(" blah\n\t ")).
76 |
77 | -endif.
78 |
--------------------------------------------------------------------------------
/apps/rolf/test/rolf_SUITE.erl:
--------------------------------------------------------------------------------
1 | %% @doc Common test suite for Rolf.
2 | %% @author Ben Godfrey [http://aftnn.org/]
3 | %% @copyright 2011 Ben Godfrey
4 | %% @version 1.0.0
5 | %%
6 | %% Rolf - a monitoring and graphing tool like Munin or collectd.
7 | %% Copyright (C) 2011 Ben Godfrey.
8 | %%
9 | %% This program is free software: you can redistribute it and/or modify
10 | %% it under the terms of the GNU General Public License as published by
11 | %% the Free Software Foundation, either version 3 of the License, or
12 | %% (at your option) any later version.
13 | %%
14 | %% This program is distributed in the hope that it will be useful,
15 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | %% GNU General Public License for more details.
18 | %%
19 | %% You should have received a copy of the GNU General Public License
20 | %% along with this program. If not, see .
21 |
22 | -module(rolf_SUITE).
23 | -compile(export_all).
24 |
25 | -include_lib("common_test/include/ct.hrl").
26 |
27 | %% ===================================================================
28 | %% Common Test callbacks
29 | %% ===================================================================
30 |
31 | %% Specify a list of all unit test functions
32 | all() -> [test1].
33 |
34 | %% required, but can just return Config. this is a suite level setup function.
35 | init_per_suite(Config) ->
36 | %% do custom per suite setup here
37 | Config.
38 |
39 | %% required, but can just return Config. this is a suite level tear down function.
40 | end_per_suite(Config) ->
41 | %% do custom per suite cleanup here
42 | Config.
43 |
44 | %% optional, can do function level setup for all functions,
45 | %% or for individual functions by matching on TestCase.
46 | init_per_testcase(_TestCase, Config) ->
47 | %% do custom test case setup here
48 | Config.
49 |
50 | %% optional, can do function level tear down for all functions,
51 | %% or for individual functions by matching on TestCase.
52 | end_per_testcase(_TestCase, Config) ->
53 | %% do custom test case cleanup here
54 | Config.
55 |
56 | %% ===================================================================
57 | %% Test cases
58 | %% ===================================================================
59 |
60 | test1(_Config) ->
61 | %% write standard erlang code to test whatever you want
62 | %% use pattern matching to specify expected return values
63 | ok.
64 |
65 |
--------------------------------------------------------------------------------
/bin/graph.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | now=`date +%s`
3 | start=`expr $now - 3600`
4 | rrdtool graph /var/www/aftnn/stuff/rolf_loadtime.png -s $start -S 10 \
5 | -w 1359 -h 851 -l 0 -u 1000 -r \
6 | -c BACK#191919 -c CANVAS#191919 -c SHADEA#191919 -c SHADEB#191919 -c FONT#ffffff \
7 | 'DEF:bbc=data/rolf@127.0.0.1/loadtime.rrd:bbc:AVERAGE' \
8 | 'DEF:guardian=data/rolf@127.0.0.1/loadtime.rrd:guardian:AVERAGE' \
9 | 'DEF:lastminute=data/rolf@127.0.0.1/loadtime.rrd:lastminute:AVERAGE' \
10 | 'DEF:aws=data/rolf@127.0.0.1/loadtime.rrd:aws:AVERAGE' \
11 | 'DEF:appengine=data/rolf@127.0.0.1/loadtime.rrd:appengine:AVERAGE' \
12 | 'DEF:twitter=data/rolf@127.0.0.1/loadtime.rrd:twitter:AVERAGE' \
13 | 'DEF:argos=data/rolf@127.0.0.1/loadtime.rrd:argos:AVERAGE' \
14 | 'DEF:aarouteplanner=data/rolf@127.0.0.1/loadtime.rrd:aarouteplanner:AVERAGE' \
15 | 'DEF:ocado=data/rolf@127.0.0.1/loadtime.rrd:ocado:AVERAGE' \
16 | 'DEF:dailymail=data/rolf@127.0.0.1/loadtime.rrd:dailymail:AVERAGE' \
17 | 'LINE:bbc#0091ff' \
18 | 'LINE:guardian#91ff00' \
19 | 'LINE:lastminute#9100ff' \
20 | 'LINE:aws#91ff00' \
21 | 'LINE:appengine#ff0091' \
22 | 'LINE:twitter#ff9100' \
23 | 'LINE:argos#00ff91' \
24 | 'LINE:aarouteplanner#cc0066' \
25 | 'LINE:ocado#0066cc' \
26 | 'LINE:dailymail#66cc00'
27 |
--------------------------------------------------------------------------------
/doc/windows.md:
--------------------------------------------------------------------------------
1 | Rolf on Windows
2 | ===============
3 |
4 | 1. Install Erlang R14B02.
5 |
6 | http://www.erlang.org/download.html
7 |
8 | 2. Download Rolf release package (or generate your own with rebar).
9 |
10 | https://github.com/downloads/afternoon/rolf/rolf-0.1.zip
11 |
12 | 3. Add Rolf to Windows Services
13 |
14 | c:\erl5.8.3\erts-5.8.3\bin\erlsrv.exe add Rolf -c "Collects system data for monitoring." -w c:\rolf -m c:\erl5.8.3\erts-5.8.3\bin\start_erl.exe -debugtype reuse -args "-setcookie rolf123 -boot c:\rolf\release\0.1\rolf.boot -embedded -config c:\rolf\etc\app.config -args_file c:\rolf\etc\vm.args ++ -reldir c:\rolf\releases"
15 | c:\erl5.8.3\erts-5.8.3\bin\erlsrv.exe add Rolf -c "Collects system data for monitoring." -w c:\rolf -m c:\erl5.8.3\erts-5.8.3\bin\start_erl.exe -debugtype reuse -args "-setcookie rolf123 ++ -reldir c:\rolf\releases"
16 |
17 | 4. Start the service!
18 |
19 | c:\erl5.8.3\erts-5.8.3\bin\erlsrv.exe start Rolf
20 |
--------------------------------------------------------------------------------
/plugins:
--------------------------------------------------------------------------------
1 | rel/files/plugins
--------------------------------------------------------------------------------
/rebar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/afternoon/rolf/7a5445d28363e4219be2947c2aaff58d72374cb8/rebar
--------------------------------------------------------------------------------
/rebar.config:
--------------------------------------------------------------------------------
1 | %% vim: ft=erlang
2 |
3 | {sub_dirs, ["apps/rolf", "rel"]}.
4 |
5 | %% dependencies
6 | {deps, [{errd, ".*", {git, "git://github.com/Vagabond/errd.git", "master"}},
7 | {log4erl, ".*", {git, "git://github.com/ahmednawras/log4erl.git", "master"}},
8 | {plists, ".*", {git, "git://github.com/yrashk/plists.git", "master"}}]}.
9 |
10 | %% testing options
11 | {cover_enabled, true}.
12 | {erl_opts, [debug_info]}.
13 | {eunit_opts, [verbose, {report, {eunit_surefire, [{dir, "."}]}}]}.
14 |
--------------------------------------------------------------------------------
/rel/files/app.config:
--------------------------------------------------------------------------------
1 | %% vim: ft=erlang
2 | [{rolf, [{recorders, ['rolf@127.0.0.1']}]},
3 | {sasl, [{sasl_error_logger, {file, "log/sasl-error.log"}},
4 | {errlog_type, error},
5 | {error_logger_mf_dir, "log/sasl"},
6 | {error_logger_mf_maxbytes, 10485760},
7 | {error_logger_mf_maxfiles, 5}]}].
8 |
--------------------------------------------------------------------------------
/rel/files/erl:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | ## This script replaces the default "erl" in erts-VSN/bin. This is necessary
4 | ## as escript depends on erl and in turn, erl depends on having access to a
5 | ## bootscript (start.boot). Note that this script is ONLY invoked as a side-effect
6 | ## of running escript -- the embedded node bypasses erl and uses erlexec directly
7 | ## (as it should).
8 | ##
9 | ## Note that this script makes the assumption that there is a start_clean.boot
10 | ## file available in $ROOTDIR/release/VSN.
11 |
12 | # Determine the abspath of where this script is executing from.
13 | ERTS_BIN_DIR=$(cd ${0%/*} && pwd)
14 |
15 | # Now determine the root directory -- this script runs from erts-VSN/bin,
16 | # so we simply need to strip off two dirs from the end of the ERTS_BIN_DIR
17 | # path.
18 | ROOTDIR=${ERTS_BIN_DIR%/*/*}
19 |
20 | # Parse out release and erts info
21 | START_ERL=`cat $ROOTDIR/releases/start_erl.data`
22 | ERTS_VSN=${START_ERL% *}
23 | APP_VSN=${START_ERL#* }
24 |
25 | BINDIR=$ROOTDIR/erts-$ERTS_VSN/bin
26 | EMU=beam
27 | PROGNAME=`echo $0 | sed 's/.*\\///'`
28 | CMD="$BINDIR/erlexec"
29 | export EMU
30 | export ROOTDIR
31 | export BINDIR
32 | export PROGNAME
33 |
34 | exec $CMD -boot $ROOTDIR/releases/$APP_VSN/start_clean ${1+"$@"}
--------------------------------------------------------------------------------
/rel/files/log4erl.conf:
--------------------------------------------------------------------------------
1 | logger {
2 | file_appender file{
3 | dir = "log",
4 | level = debug,
5 | file = "rolf",
6 | type = size,
7 | max = 100000,
8 | suffix = log,
9 | rotation = 5,
10 | format = '%I %L %l%n'
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/rel/files/nodetool:
--------------------------------------------------------------------------------
1 | %% -*- erlang -*-
2 | %% -------------------------------------------------------------------
3 | %%
4 | %% nodetool: Helper Script for interacting with live nodes
5 | %%
6 | %% -------------------------------------------------------------------
7 |
8 | main(Args) ->
9 | %% Extract the args
10 | {RestArgs, TargetNode} = process_args(Args, [], undefined),
11 |
12 | %% See if the node is currently running -- if it's not, we'll bail
13 | case {net_kernel:hidden_connect_node(TargetNode), net_adm:ping(TargetNode)} of
14 | {true, pong} ->
15 | ok;
16 | {_, pang} ->
17 | io:format("Node ~p not responding to pings.\n", [TargetNode]),
18 | halt(1)
19 | end,
20 |
21 | case RestArgs of
22 | ["ping"] ->
23 | %% If we got this far, the node already responsed to a ping, so just dump
24 | %% a "pong"
25 | io:format("pong\n");
26 | ["stop"] ->
27 | io:format("~p\n", [rpc:call(TargetNode, init, stop, [], 60000)]);
28 | ["restart"] ->
29 | io:format("~p\n", [rpc:call(TargetNode, init, restart, [], 60000)]);
30 | ["reboot"] ->
31 | io:format("~p\n", [rpc:call(TargetNode, init, reboot, [], 60000)]);
32 | ["rpc", Module, Function | RpcArgs] ->
33 | case rpc:call(TargetNode, list_to_atom(Module), list_to_atom(Function), [RpcArgs], 60000) of
34 | ok ->
35 | ok;
36 | {badrpc, Reason} ->
37 | io:format("RPC to ~p failed: ~p\n", [TargetNode, Reason]),
38 | halt(1);
39 | _ ->
40 | halt(1)
41 | end;
42 | Other ->
43 | io:format("Other: ~p\n", [Other]),
44 | io:format("Usage: nodetool {ping|stop|restart|reboot}\n")
45 | end,
46 | net_kernel:stop().
47 |
48 | process_args([], Acc, TargetNode) ->
49 | {lists:reverse(Acc), TargetNode};
50 | process_args(["-setcookie", Cookie | Rest], Acc, TargetNode) ->
51 | erlang:set_cookie(node(), list_to_atom(Cookie)),
52 | process_args(Rest, Acc, TargetNode);
53 | process_args(["-name", TargetName | Rest], Acc, _) ->
54 | ThisNode = append_node_suffix(TargetName, "_maint_"),
55 | {ok, _} = net_kernel:start([ThisNode, longnames]),
56 | process_args(Rest, Acc, nodename(TargetName));
57 | process_args(["-sname", TargetName | Rest], Acc, _) ->
58 | ThisNode = append_node_suffix(TargetName, "_maint_"),
59 | {ok, _} = net_kernel:start([ThisNode, shortnames]),
60 | process_args(Rest, Acc, nodename(TargetName));
61 | process_args([Arg | Rest], Acc, Opts) ->
62 | process_args(Rest, [Arg | Acc], Opts).
63 |
64 |
65 | nodename(Name) ->
66 | case string:tokens(Name, "@") of
67 | [_Node, _Host] ->
68 | list_to_atom(Name);
69 | [Node] ->
70 | [_, Host] = string:tokens(atom_to_list(node()), "@"),
71 | list_to_atom(lists:concat([Node, "@", Host]))
72 | end.
73 |
74 | append_node_suffix(Name, Suffix) ->
75 | case string:tokens(Name, "@") of
76 | [Node, Host] ->
77 | list_to_atom(lists:concat([Node, Suffix, os:getpid(), "@", Host]));
78 | [Node] ->
79 | list_to_atom(lists:concat([Node, Suffix, os:getpid()]))
80 | end.
81 |
--------------------------------------------------------------------------------
/rel/files/plugins/disk/disk.config:
--------------------------------------------------------------------------------
1 | %% vim: ft=erlang
2 | %% use this external program to collect data
3 | {command, "disk.sh"}.
4 |
5 | %% update frequency in seconds
6 | {frequency, 300}.
7 |
8 | %% number of seconds allowed before service becomes "unknown"
9 | {timeout, 3600}.
10 |
11 | %% define the RRAs (round robin archives) to be stored for this service
12 | {archives, [{1, 288}, % 1d of 5m averages
13 | {12, 336}, % 7d of 1hr averages
14 | {288, 365}]}. % 1y of 1d averages
15 |
16 | %% graph parameters
17 | {graph_title, "Disk Space"}.
18 | {graph_vlabel, "GB"}.
19 |
20 | %% metric configuration
21 | {metrics, [{freespace, [{label, "Free Space"},
22 | {type, gauge},
23 | {draw, areastack},
24 | {min, 0},
25 | {colour, "#66BDFF"}]}]}.
26 |
--------------------------------------------------------------------------------
/rel/files/plugins/disk/disk.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Get free space on root filesystem
3 | echo -n "freespace "; df -h / | tail -n 1 | awk '{ print $5 }' | cut -d % -f 1
4 | echo "."
5 |
--------------------------------------------------------------------------------
/rel/files/plugins/loadtime/loadtime.config:
--------------------------------------------------------------------------------
1 | %% vim: ft=erlang
2 | %% use this Erlang module to collect data
3 | {module, rolf_loadtime}.
4 |
5 | %% update frequency in seconds
6 | {frequency, 5}.
7 |
8 | %% number of seconds allowed before service becomes "unknown"
9 | {timeout, 60}.
10 |
11 | %% define the RRAs (round robin archives) to be stored for this service
12 | {archives, [{1, 720}, % 1hr of 5s averages
13 | {6, 2880}, % 1d of 30s averages
14 | {360, 336}, % 7d of 30m averages
15 | {17280, 365}]}. % 1y of 1d averages
16 |
17 | %% graph parameters
18 | {graph_title, "Load Time"}.
19 | {graph_vlabel, "Secs"}.
20 |
21 | %% metric configuration - tricky for this plugin as each url is a metric
22 | {metrics, [{loadtime, [{label, "Load Time"},
23 | {type, gauge},
24 | {draw, areastack},
25 | {min, 0},
26 | {colour, "#0091FF"}]}]}.
27 |
28 | %% url to measure
29 | {url, "http://localhost/"}.
30 |
--------------------------------------------------------------------------------
/rel/files/plugins/loadtime/loadtime.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Get load time of a bunch of sites (sequentially)
3 | for url in $*; do
4 | curl -s -o /dev/null -w "loadtime %{time_total}\n" "$url"
5 | done
6 | echo "."
7 |
--------------------------------------------------------------------------------
/rel/files/plugins/uptime/uptime.config:
--------------------------------------------------------------------------------
1 | %% vim: ft=erlang
2 | %% use this Erlang module to collect data
3 | {module, rolf_munin_node}.
4 |
5 | %% update frequency in seconds
6 | {frequency, 300}.
7 |
8 | %% number of seconds allowed before service becomes "unknown"
9 | {timeout, 600}.
10 |
11 | %% define the RRAs (round robin archives) to be stored for this service
12 | %% matches Munin (http://munin-monitoring.org/browser/trunk/master/lib/Munin/Master/UpdateWorker.pm)
13 | {archives, [{1, 576}, % 2d of 5m averages
14 | {6, 432}, % 9d of 30m averages
15 | {24, 540}, % 45d of 2h averages
16 | {288, 450}]}. % 450d of 1d averages
17 |
18 | %% graph parameters
19 | {graph_title, "Uptime"}.
20 | {graph_vlabel, "Days"}.
21 |
22 | %% metric configuration - tricky for this plugin as each url is a metric
23 | {metrics, [{uptime, [{label, "Uptime"},
24 | {type, gauge},
25 | {min, 0},
26 | {draw, areastack},
27 | {colour, "#0091FF"}]}]}.
28 |
29 | %% host to collect from
30 | {host, localhost}.
31 |
32 | %% Munin plugin to fetch
33 | {plugin, uptime}.
34 |
--------------------------------------------------------------------------------
/rel/files/rolf:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # -*- tab-width:4;indent-tabs-mode:nil -*-
3 | # ex: ts=4 sw=4 et
4 |
5 | RUNNER_SCRIPT_DIR=$(cd ${0%/*} && pwd)
6 |
7 | RUNNER_BASE_DIR=${RUNNER_SCRIPT_DIR%/*}
8 | RUNNER_ETC_DIR=$RUNNER_BASE_DIR/etc
9 | RUNNER_LOG_DIR=$RUNNER_BASE_DIR/log
10 | PIPE_DIR=/tmp/$RUNNER_BASE_DIR/
11 | RUNNER_USER=
12 |
13 | # Make sure this script is running as the appropriate user
14 | if [ ! -z "$RUNNER_USER" ] && [ `whoami` != "$RUNNER_USER" ]; then
15 | exec sudo -u $RUNNER_USER -i $0 $@
16 | fi
17 |
18 | # Make sure CWD is set to runner base dir
19 | cd $RUNNER_BASE_DIR
20 |
21 | # Make sure log directory exists
22 | mkdir -p $RUNNER_LOG_DIR
23 |
24 | # Extract the target node name from node.args
25 | NAME_ARG=`grep -e '-[s]*name' $RUNNER_ETC_DIR/vm.args`
26 | if [ -z "$NAME_ARG" ]; then
27 | echo "vm.args needs to have either -name or -sname parameter."
28 | exit 1
29 | fi
30 |
31 | # Extract the target cookie
32 | COOKIE_ARG=`grep -e '-setcookie' $RUNNER_ETC_DIR/vm.args`
33 | if [ -z "$COOKIE_ARG" ]; then
34 | echo "vm.args needs to have a -setcookie parameter."
35 | exit 1
36 | fi
37 |
38 | # Identify the script name
39 | SCRIPT=`basename $0`
40 |
41 | # Parse out release and erts info
42 | START_ERL=`cat $RUNNER_BASE_DIR/releases/start_erl.data`
43 | ERTS_VSN=${START_ERL% *}
44 | APP_VSN=${START_ERL#* }
45 |
46 | # Add ERTS bin dir to our path
47 | ERTS_PATH=$RUNNER_BASE_DIR/erts-$ERTS_VSN/bin
48 |
49 | # Setup command to control the node
50 | NODETOOL="$ERTS_PATH/escript $ERTS_PATH/nodetool $NAME_ARG $COOKIE_ARG"
51 |
52 | # Check the first argument for instructions
53 | case "$1" in
54 | start)
55 | # Make sure there is not already a node running
56 | RES=`$NODETOOL ping`
57 | if [ "$RES" = "pong" ]; then
58 | echo "Node is already running!"
59 | exit 1
60 | fi
61 | HEART_COMMAND="$RUNNER_BASE_DIR/bin/$SCRIPT start"
62 | export HEART_COMMAND
63 | mkdir -p $PIPE_DIR
64 | # Note the trailing slash on $PIPE_DIR/
65 | $ERTS_PATH/run_erl -daemon $PIPE_DIR/ $RUNNER_LOG_DIR "exec $RUNNER_BASE_DIR/bin/$SCRIPT console" 2>&1
66 | ;;
67 |
68 | stop)
69 | # Wait for the node to completely stop...
70 | case `uname -s` in
71 | Linux|Darwin|FreeBSD|DragonFly|NetBSD|OpenBSD)
72 | # PID COMMAND
73 | PID=`ps ax -o pid= -o command=|\
74 | grep "$RUNNER_BASE_DIR/.*/[b]eam"|awk '{print $1}'`
75 | ;;
76 | SunOS)
77 | # PID COMMAND
78 | PID=`ps -ef -o pid= -o args=|\
79 | grep "$RUNNER_BASE_DIR/.*/[b]eam"|awk '{print $1}'`
80 | ;;
81 | CYGWIN*)
82 | # UID PID PPID TTY STIME COMMAND
83 | PID=`ps -efW|grep "$RUNNER_BASE_DIR/.*/[b]eam"|awk '{print $2}'`
84 | ;;
85 | esac
86 | $NODETOOL stop
87 | while `kill -0 $PID 2>/dev/null`;
88 | do
89 | sleep 1
90 | done
91 | ;;
92 |
93 | restart)
94 | ## Restart the VM without exiting the process
95 | $NODETOOL restart
96 | ;;
97 |
98 | reboot)
99 | ## Restart the VM completely (uses heart to restart it)
100 | $NODETOOL reboot
101 | ;;
102 |
103 | ping)
104 | ## See if the VM is alive
105 | $NODETOOL ping
106 | ;;
107 |
108 | attach)
109 | # Make sure a node IS running
110 | RES=`$NODETOOL ping`
111 | if [ "$RES" != "pong" ]; then
112 | echo "Node is not running!"
113 | exit 1
114 | fi
115 |
116 | shift
117 | $ERTS_PATH/to_erl $PIPE_DIR
118 | ;;
119 |
120 | console)
121 | # Setup beam-required vars
122 | ROOTDIR=$RUNNER_BASE_DIR
123 | BINDIR=$ROOTDIR/erts-$ERTS_VSN/bin
124 | EMU=beam
125 | PROGNAME=`echo $0 | sed 's/.*\\///'`
126 | CMD="$BINDIR/erlexec -boot $RUNNER_BASE_DIR/releases/$APP_VSN/$SCRIPT -embedded -config $RUNNER_ETC_DIR/app.config -args_file $RUNNER_ETC_DIR/vm.args -- ${1+"$@"}"
127 | export EMU
128 | export ROOTDIR
129 | export BINDIR
130 | export PROGNAME
131 |
132 | # Dump environment info for logging purposes
133 | echo "Exec: $CMD"
134 | echo "Root: $ROOTDIR"
135 |
136 | # Log the startup
137 | logger -t "$SCRIPT[$$]" "Starting up"
138 |
139 | # Start the VM
140 | exec $CMD
141 | ;;
142 |
143 | *)
144 | echo "Usage: $SCRIPT {start|stop|restart|reboot|ping|console|attach}"
145 | exit 1
146 | ;;
147 | esac
148 |
149 | exit 0
150 |
--------------------------------------------------------------------------------
/rel/files/services.config:
--------------------------------------------------------------------------------
1 | %% vim: ft=erlang
2 | %% Service configuration
3 | %%
4 | %% Format: {node, Nodename, [ServiceDef]}
5 | %%
6 | %% Service formats:
7 | %%
8 | %% - service
9 | %% Run service.
10 | %%
11 | %% - {service, [{option, value}]}
12 | %% Run service with custom options.
13 | %%
14 | %% - {service, name}
15 | %% Run service with custom name.
16 | %%
17 | %% - {service, name, [{option, value}]}
18 | %% Run service with custom name and options.
19 |
20 | {node, 'rolf@li153-242', [disk,
21 | {loadtime, [{url, "http://www.bbc.co.uk/"}]},
22 | {sql, mysql_users, [{type, mysql},
23 | {database, "mysql"},
24 | {user, "root"},
25 | {password, "xxxxxxxx"},
26 | {query, "select count(*) as users from user"},
27 | {metrics, [users]}]}]}.
28 |
--------------------------------------------------------------------------------
/rel/files/vm.args:
--------------------------------------------------------------------------------
1 |
2 | ## Name of the node
3 | -name rolf@127.0.0.1
4 |
5 | ## Cookie for distributed erlang
6 | -setcookie rolf123
7 |
--------------------------------------------------------------------------------
/rel/reltool.config:
--------------------------------------------------------------------------------
1 | %% vim: ft=erlang
2 | {sys, [{lib_dirs, ["../apps", "../deps"]},
3 | {rel, "rolf", "0.1", [kernel, stdlib, sasl, log4erl, rolf]},
4 | {rel, "start_clean", "", [kernel, stdlib]},
5 | {boot_rel, "rolf"},
6 | {profile, embedded},
7 | {excl_sys_filters, ["^bin/.*", "^erts.*/bin/(dialyzer|typer)"]},
8 | {app, sasl, [{incl_cond, include}]},
9 | {app, log4erl, [{incl_cond, include}]},
10 | {app, rolf, [{incl_cond, include}]}]}.
11 |
12 | {target_dir, "rolf"}.
13 |
14 | {overlay, [{mkdir, "data"},
15 | {mkdir, "log"},
16 | {copy, "files/erl", "{{erts_vsn}}/bin/erl"},
17 | {copy, "files/nodetool", "{{erts_vsn}}/bin/nodetool"},
18 | {copy, "files/rolf", "bin/rolf"},
19 | {copy, "files/app.config", "etc/app.config"},
20 | {copy, "files/vm.args", "etc/vm.args"},
21 | {copy, "files/services.config", "etc/services.config"},
22 | {copy, "files/log4erl.conf", "etc/log4erl.conf"},
23 | {copy, "files/plugins", "plugins"}]}.
24 |
--------------------------------------------------------------------------------