866 lines
24 KiB
Erlang
866 lines
24 KiB
Erlang
|
-module(erlrrd).
|
||
|
-include_lib ("eunit/include/eunit.hrl").
|
||
|
|
||
|
-export([create/1, update/1, updatev/1, dump/1, restore/1, last/1,
|
||
|
first/1, info/1, fetch/1, tune/1, resize/1, xport/1,
|
||
|
graph/1, lastupdate/1, ls/0, cd/1, mkdir/1, pwd/0
|
||
|
]).
|
||
|
|
||
|
-export([start_link/1, start_link/0]).
|
||
|
-export([start/0]).
|
||
|
-export([stop/0]).
|
||
|
-export([combine/1, c/1]).
|
||
|
|
||
|
-behaviour(gen_server).
|
||
|
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||
|
terminate/2, code_change/3]).
|
||
|
|
||
|
-record( state2, { port, timeout } ).
|
||
|
|
||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||
|
%% Public
|
||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||
|
|
||
|
|
||
|
%% @equiv erlrrd_app:start()
|
||
|
start() -> erlrrd_app:start().
|
||
|
%% @equiv erlrrd_app:stop()
|
||
|
stop() -> erlrrd_app:stop().
|
||
|
|
||
|
%% @spec start_link(RRDToolCmd) -> Result
|
||
|
%% RRDToolCmd = string()
|
||
|
%% Result = {ok,Pid} | ignore | {error,Error}
|
||
|
%% Pid = pid()
|
||
|
%% Error = {already_started,Pid} | shutdown | term()
|
||
|
%% @doc calls gen_server:start_link
|
||
|
%% RRDToolCmd is the command passed to open_port()
|
||
|
%% usually "rrdtool -"
|
||
|
start_link(RRDToolCmd) when is_list(RRDToolCmd) ->
|
||
|
application:set_env(erlrrd, rrdtoolcmd, RRDToolCmd ),
|
||
|
start_link().
|
||
|
|
||
|
%% @equiv start_link("rrdtool -")
|
||
|
start_link() ->
|
||
|
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||
|
|
||
|
%% @spec combine(List) -> List
|
||
|
%% List = [ term() ]
|
||
|
%% @doc "joins" and quotes the given arg list.
|
||
|
%% takes a list of arguments, and returns a deeplist with
|
||
|
%% each argument surrounded by double quotes
|
||
|
%% then separated by spaces. Note
|
||
|
%% it does not try to escape any double quotes
|
||
|
%% in the arguments.
|
||
|
%%
|
||
|
%% combine(["these", "are", "my args"]). ->
|
||
|
%%
|
||
|
%% [["\"","these","\""]," ",["\"","are","\""]," ",["\"","my args","\""]]
|
||
|
%%
|
||
|
%% it is intended as a convinence function to the
|
||
|
%% rrdtool commands which all take a single iodata() argument
|
||
|
%% which represents the string to be passed as the arguments
|
||
|
%% to the corresponding rrdtool command.
|
||
|
%%
|
||
|
%% erlrrd:xport(erlrrd:c(["DEF:foo=/path with/space/foo.rrd:foo:AVERAGE", "XPORT:foo"])).
|
||
|
combine(Args) ->
|
||
|
join([ [ "\"", X, "\"" ] || X <- Args ], " ").
|
||
|
%% @spec c(List) -> List
|
||
|
%% List = [ term() ]
|
||
|
% @equiv combine(Args)
|
||
|
c(Args) -> combine(Args).
|
||
|
|
||
|
|
||
|
% rrdtool commands
|
||
|
|
||
|
%% @spec create(erlang:iodata()) -> { ok, Response } |
|
||
|
%% { error, Reason }
|
||
|
%% Reason = iolist()
|
||
|
%% Response = iolist()
|
||
|
%% @doc Set up a new Round Robin Database (RRD). Check
|
||
|
%% [http://oss.oetiker.ch/rrdtool/doc/rrdcreate.en.html rrdcreate].
|
||
|
create (Args) when is_list(Args) -> do(create, Args).
|
||
|
|
||
|
%% @spec update(erlang:iodata()) -> { ok, Response } |
|
||
|
%% { error, Reason }
|
||
|
%% Reason = iolist()
|
||
|
%% Response = iolist()
|
||
|
%% @doc Store new data values into an RRD. Check
|
||
|
%% [http://oss.oetiker.ch/rrdtool/doc/rrdupdate.en.html rrdupdate].
|
||
|
update (Args) when is_list(Args) -> do(update, Args).
|
||
|
|
||
|
%% @spec updatev(erlang:iodata()) -> { ok, Response } |
|
||
|
%% { error, Reason }
|
||
|
%% Reason = iolist()
|
||
|
%% Response = iolist()
|
||
|
%% @doc Operationally equivalent to update except for output. Check
|
||
|
%% [http://oss.oetiker.ch/rrdtool/doc/rrdupdate.en.html rrdupdate].
|
||
|
updatev (Args) when is_list(Args) -> do(updatev, Args).
|
||
|
|
||
|
%% @spec dump(erlang:iodata()) -> { ok, Response } |
|
||
|
%% { error, Reason }
|
||
|
%% Reason = iolist()
|
||
|
%% Response = iolist()
|
||
|
%% @doc Dump the contents of an RRD in plain ASCII. In connection with
|
||
|
%% restore you can use this to move an RRD from one computer
|
||
|
%% architecture to another. Check
|
||
|
%% [http://oss.oetiker.ch/rrdtool/doc/rrddump.en.html rrddump].
|
||
|
dump (Args) when is_list(Args) -> do(dump, Args).
|
||
|
|
||
|
%% @spec restore(erlang:iodata()) -> { ok, Response } |
|
||
|
%% { error, Reason }
|
||
|
%% Reason = iolist()
|
||
|
%% Response = iolist()
|
||
|
%% @doc Restore an RRD in XML format to a binary RRD. Check
|
||
|
%% [http://oss.oetiker.ch/rrdtool/doc/rrdrestore.en.html rrdrestore]
|
||
|
restore (Args) when is_list(Args) -> do(restore, Args).
|
||
|
|
||
|
%% @spec last(erlang:iodata()) -> { ok, Response } |
|
||
|
%% { error, Reason }
|
||
|
%% Reason = iolist()
|
||
|
%% Response = integer()
|
||
|
%% @doc Return the date of the last data sample in an RRD. Check
|
||
|
%% [http://oss.oetiker.ch/rrdtool/doc/rrdlast.en.html rrdlast]
|
||
|
last (Args) when is_list(Args) ->
|
||
|
case do(last, Args) of
|
||
|
{ error, Reason } -> { error, Reason };
|
||
|
{ ok, [[Response]] } -> { ok, erlang:list_to_integer(Response) }
|
||
|
end.
|
||
|
|
||
|
%% @spec lastupdate(erlang:iodata()) -> { ok, Response } |
|
||
|
%% { error, Reason }
|
||
|
%% Reason = iolist()
|
||
|
%% Response = iolist()
|
||
|
%% @doc Return the most recent update to an RRD. Check
|
||
|
%% [http://oss.oetiker.ch/rrdtool/doc/rrdlastupdate.en.html rrdlastupdate]
|
||
|
lastupdate (Args) when is_list(Args) -> do(lastupdate, Args).
|
||
|
|
||
|
%% @spec first(erlang:iodata()) -> { ok, Response } |
|
||
|
%% { error, Reason }
|
||
|
%% Reason = iolist()
|
||
|
%% Response = integer()
|
||
|
%% @doc Return the date of the first data sample in an RRA within an
|
||
|
%% RRD. Check
|
||
|
%% [http://oss.oetiker.ch/rrdtool/doc/rrdfirst.en.html rrdfirst]
|
||
|
first (Args) when is_list(Args) ->
|
||
|
case do(first, Args) of
|
||
|
{ error, Reason } -> { error, Reason };
|
||
|
{ ok, [[Response]] } -> { ok, erlang:list_to_integer(Response) }
|
||
|
end.
|
||
|
|
||
|
%% @spec info(erlang:iodata()) -> { ok, Response } |
|
||
|
%% { error, Reason }
|
||
|
%% Reason = iolist()
|
||
|
%% Response = iolist()
|
||
|
%% @doc Get information about an RRD. Check
|
||
|
%% [http://oss.oetiker.ch/rrdtool/doc/rrdinfo.en.html rrdinfo].
|
||
|
info (Args) when is_list(Args) -> do(info, Args).
|
||
|
|
||
|
%% @spec fetch(erlang:iodata()) -> { ok, Response } |
|
||
|
%% { error, Reason }
|
||
|
%% Reason = iolist()
|
||
|
%% Response = iolist()
|
||
|
%% @doc Get data for a certain time period from a RRD. The graph func-
|
||
|
%% tion uses fetch to retrieve its data from an RRD. Check
|
||
|
%% [http://oss.oetiker.ch/rrdtool/doc/rrdfetch.en.html rrdfetch].
|
||
|
fetch (Args) when is_list(Args) -> do(fetch, Args).
|
||
|
|
||
|
%% @spec tune(erlang:iodata()) -> { ok, Response } |
|
||
|
%% { error, Reason }
|
||
|
%% Reason = iolist()
|
||
|
%% Response = iolist()
|
||
|
%% @doc Alter setup of an RRD. Check
|
||
|
%% [http://oss.oetiker.ch/rrdtool/doc/rrdtune.en.html rrdtune].
|
||
|
tune (Args) when is_list(Args) -> do(tune, Args).
|
||
|
|
||
|
%% @spec resize(erlang:iodata()) -> { ok, Response } |
|
||
|
%% { error, Reason }
|
||
|
%% Reason = iolist()
|
||
|
%% Response = iolist()
|
||
|
%% @doc Change the size of individual RRAs. This is dangerous! Check
|
||
|
%% rrdresize.
|
||
|
resize (Args) when is_list(Args) -> do(resize, Args).
|
||
|
|
||
|
%% @spec xport(erlang:iodata()) -> { ok, Response } |
|
||
|
%% { error, Reason }
|
||
|
%% Reason = iolist()
|
||
|
%% Response = iolist()
|
||
|
%% @doc Export data retrieved from one or several RRDs. Check
|
||
|
%% [http://oss.oetiker.ch/rrdtool/doc/rrdxport.en.html rrdxport]
|
||
|
%%
|
||
|
%% erlrrd:xport("'DEF:foo=/path with/space/foo.rrd:foo:AVERAGE' XPORT:foo").
|
||
|
%%
|
||
|
%% erlrrd:xport(erlrrd:c(["DEF:foo=/path with/space/foo.rrd:foo:AVERAGE", "XPORT:foo"])).
|
||
|
xport (Args) when is_list(Args) -> do(xport, Args).
|
||
|
|
||
|
%% @spec graph(erlang:iodata()) -> { ok, Response } |
|
||
|
%% { error, Reason }
|
||
|
%% Reason = iolist()
|
||
|
%% Response = iolist()
|
||
|
%% @doc Create a graph from data stored in one or several RRDs. Apart
|
||
|
%% from generating graphs, data can also be extracted to stdout.
|
||
|
%% Check
|
||
|
%% [http://oss.oetiker.ch/rrdtool/doc/rrdgraph.en.html rrdgraph].
|
||
|
graph (Args) when is_list(Args) ->
|
||
|
% TODO: scan for this pattern w/out flattening the io_list?
|
||
|
% TODO: support graphing to stdout!!! :)
|
||
|
Flat = erlang:binary_to_list(erlang:iolist_to_binary(Args)),
|
||
|
case regexp:match(Flat, "(^| )-( |$)") of
|
||
|
{ match, _, _ } ->
|
||
|
% graph to stdout will break this Ports parsing of reponses..
|
||
|
{ error, "Graphing to stdout not supported." };
|
||
|
nomatch ->
|
||
|
do(graph, Args)
|
||
|
end.
|
||
|
|
||
|
%%%%% rrd "remote" commands %%%%
|
||
|
|
||
|
%% @spec cd(erlang:iodata()) -> ok |
|
||
|
%% { error, Reason }
|
||
|
%% Reason = iolist()
|
||
|
%% @doc ask the rrdtool unix process to change directories
|
||
|
%%
|
||
|
%% erlrrd:cd("/usr/share/rrd/data").
|
||
|
%%
|
||
|
%% erlrrd:cd(erlrrd:combine(["/Users/foo/Library/Application Support/myapp/rrd"]).
|
||
|
cd (Arg) when is_list(Arg) ->
|
||
|
case do(cd, Arg) of
|
||
|
{ error, Reason } -> { error, Reason };
|
||
|
{ ok, _ } -> ok
|
||
|
end.
|
||
|
|
||
|
%% @spec mkdir(erlang:iodata()) -> { ok, Response } |
|
||
|
%% { error, Reason }
|
||
|
%% Reason = iolist()
|
||
|
%% Response = iolist()
|
||
|
%% @doc ask the rrdtool unix process to create a directory
|
||
|
mkdir (Arg) when is_list(Arg) -> do(mkdir, Arg).
|
||
|
|
||
|
%% @spec ls() -> { ok, Response } | { error, Reason }
|
||
|
%% Reason = iolist()
|
||
|
%% Response = iolist()
|
||
|
%% @doc lists all *.rrd files in rrdtool unix process'
|
||
|
%% current working directory
|
||
|
ls () -> do(ls, [] ).
|
||
|
|
||
|
%% @spec pwd() -> { ok, Response } | { error, Reason }
|
||
|
%% Reason = iolist()
|
||
|
%% Response = string()
|
||
|
%% @doc return the rrdtool unix process'
|
||
|
%% current working directory.
|
||
|
pwd () ->
|
||
|
case do(pwd, []) of
|
||
|
{ error, Reason } -> { error, Reason };
|
||
|
{ ok, [[Response]] } -> { ok, Response }
|
||
|
end.
|
||
|
|
||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||
|
%% Gen server interface poo
|
||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||
|
|
||
|
%% @hidden
|
||
|
init([]) ->
|
||
|
RRDToolCmd = case application:get_env(erlrrd, rrdtoolcmd) of
|
||
|
{ ok, Cmd } -> Cmd;
|
||
|
undefined -> "rrdtool -"
|
||
|
end,
|
||
|
Timeout = case application:get_env(erlrrd, timeout) of
|
||
|
{ ok, T } -> T;
|
||
|
undefined -> 3000
|
||
|
end,
|
||
|
process_flag(trap_exit, true),
|
||
|
Port = erlang:open_port(
|
||
|
{spawn, RRDToolCmd},
|
||
|
[ {line, 10000}, eof, exit_status, stream ]
|
||
|
),
|
||
|
{ok, #state2{port = Port, timeout = Timeout}}.
|
||
|
|
||
|
%% handle_call
|
||
|
%% @hidden
|
||
|
handle_call(
|
||
|
{do, Action, Args },
|
||
|
_From,
|
||
|
#state2{port = Port, timeout = Timeout } = State
|
||
|
) ->
|
||
|
Line = [ erlang:atom_to_list(Action), " ", Args , "\n"],
|
||
|
port_command(Port, Line),
|
||
|
case collect_response(Port, Timeout) of
|
||
|
{response, Response} ->
|
||
|
{reply, { ok, Response }, State};
|
||
|
{ error, timeout } ->
|
||
|
{stop, port_timeout, State};
|
||
|
{ error, Error } ->
|
||
|
{reply, { error, Error }, State}
|
||
|
end.
|
||
|
|
||
|
%% handle_cast
|
||
|
%% @hidden
|
||
|
handle_cast(_Msg, State) ->
|
||
|
% io:format(user, "Got unexpected cast msg: ~p~n", [Msg]),
|
||
|
%% TODO error/event loging in erlang style.
|
||
|
{noreply, State}.
|
||
|
|
||
|
%% handle_info
|
||
|
%% @hidden
|
||
|
handle_info({ Port , {exit_status, Status}}, State)
|
||
|
when
|
||
|
Port =:= State#state2.port
|
||
|
->
|
||
|
{ stop, { port_exit, Status }, State};
|
||
|
|
||
|
handle_info(_Msg, State) ->
|
||
|
% io:format(user, "Got unexpected info msg: ~p~n", [Msg]),
|
||
|
%% TODO error/event loging in erlang style.
|
||
|
{noreply, State}.
|
||
|
|
||
|
%% terminate
|
||
|
%% @hidden
|
||
|
terminate(_Reason, _State) -> ok.
|
||
|
|
||
|
%% code_change
|
||
|
%% @hidden
|
||
|
code_change(_OldVsn, State, _Extra) ->
|
||
|
{ok, State}.
|
||
|
|
||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||
|
% Private poo
|
||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||
|
|
||
|
do(Command, Args) ->
|
||
|
case has_newline(Args) of
|
||
|
true -> { error, "No newlines" };
|
||
|
false -> gen_server:call (?MODULE, { do, Command, Args } )
|
||
|
end.
|
||
|
|
||
|
join([Head | [] ], _Sep) ->
|
||
|
[Head];
|
||
|
join([Head | Tail], Sep) ->
|
||
|
[ Head, Sep | join(Tail, Sep) ].
|
||
|
|
||
|
has_newline([]) -> false;
|
||
|
has_newline(<<>>) -> false;
|
||
|
has_newline([ H | T])
|
||
|
when is_list(H); is_binary(H) ->
|
||
|
case has_newline(H) of
|
||
|
true -> true;
|
||
|
false -> has_newline(T)
|
||
|
end;
|
||
|
has_newline([ H | T]) when is_integer(H) ->
|
||
|
if
|
||
|
H =:= $\n -> true;
|
||
|
true -> has_newline(T)
|
||
|
end;
|
||
|
has_newline(<<H:8,T/binary>>) ->
|
||
|
if
|
||
|
H =:= $\n -> true;
|
||
|
true -> has_newline(T)
|
||
|
end.
|
||
|
|
||
|
|
||
|
collect_response(Port, Timeout ) ->
|
||
|
collect_response(Port, [], [], Timeout ).
|
||
|
|
||
|
collect_response( Port, RespAcc, LineAcc, Timeout) ->
|
||
|
receive
|
||
|
{Port, {data, {eol, "OK u:" ++ _T }}} ->
|
||
|
{response, lists:reverse(RespAcc)};
|
||
|
{Port, {data, {eol, "ERROR: " ++ Error }}} ->
|
||
|
{error, [ Error, lists:reverse(RespAcc)]};
|
||
|
{Port, {data, {eol, Result}}} ->
|
||
|
Line = lists:reverse([Result | LineAcc]),
|
||
|
collect_response(Port, [Line | RespAcc], [], Timeout);
|
||
|
{Port, {data, {noeol, Result}}} ->
|
||
|
collect_response(Port, RespAcc, [Result | LineAcc], Timeout)
|
||
|
|
||
|
%% Prevent the gen_server from hanging indefinitely in case the
|
||
|
%% spawned process is taking too long processing the request.
|
||
|
after Timeout ->
|
||
|
{ error, timeout }
|
||
|
end.
|
||
|
-ifdef(EUNIT).
|
||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||
|
%% Test Helpers
|
||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||
|
|
||
|
test_start_stop_(StartFun, StopFun, Tag) ->
|
||
|
{ spawn,
|
||
|
{ inorder,
|
||
|
[
|
||
|
check_stopped_(Tag),
|
||
|
{ Tag, setup,
|
||
|
StartFun,
|
||
|
StopFun,
|
||
|
check_started_(Tag)
|
||
|
},
|
||
|
check_stopped_(Tag)
|
||
|
]
|
||
|
}
|
||
|
}.
|
||
|
|
||
|
check_started_(Tag) ->
|
||
|
wrap_tag_(Tag,
|
||
|
[
|
||
|
?_test(ls()),
|
||
|
?_test(pwd()),
|
||
|
?_test(ok),
|
||
|
?_test(ok)
|
||
|
]
|
||
|
).
|
||
|
|
||
|
check_stopped_(Tag) ->
|
||
|
wrap_tag_(Tag,
|
||
|
[
|
||
|
?_assertExit( { noproc, _ }, pwd()),
|
||
|
?_assertExit( { noproc, _ }, ls())
|
||
|
]
|
||
|
).
|
||
|
|
||
|
check_last_(RRDFile,Now) ->
|
||
|
fun () ->
|
||
|
{ ok, When } = erlrrd:last(RRDFile),
|
||
|
When = Now
|
||
|
end.
|
||
|
|
||
|
start_helper_() ->
|
||
|
application:unset_env(erlrrd, rrdtoolcmd),
|
||
|
application:unset_env(erlrrd, timeout),
|
||
|
{ok, Pid} = start_link(),
|
||
|
Pid.
|
||
|
stop_helper_(Pid) -> stop_helper_(Pid, 3000).
|
||
|
stop_helper_(Pid, Timeout) ->
|
||
|
true = exit(Pid, normal),
|
||
|
receive
|
||
|
{'EXIT', Pid, Reason} -> Reason
|
||
|
after Timeout ->
|
||
|
throw({ timeout, { Pid, "Pid not responding to EXIT?"} })
|
||
|
end.
|
||
|
|
||
|
% check if the dir we're in end's in /tests
|
||
|
check_cwd_helper_() ->
|
||
|
{ ok, L } = file:get_cwd(),
|
||
|
"stset/" ++ _ = lists:reverse(L).
|
||
|
|
||
|
p_func(X,Steps) ->
|
||
|
Pi = math:pi(),
|
||
|
5 * (
|
||
|
math:sin( 3*Pi/2 + X/(Steps / (8*Pi) ) )
|
||
|
+ 1
|
||
|
).
|
||
|
|
||
|
time_since_epoch() ->
|
||
|
calendar:datetime_to_gregorian_seconds( erlang:universaltime())
|
||
|
- ( 719528 * 86400 ).
|
||
|
|
||
|
wrap_tag_(T,L) when is_list(L) ->
|
||
|
[ { T, X } || X <- L ].
|
||
|
|
||
|
cast(Blah) ->
|
||
|
gen_server:cast(?MODULE, Blah ).
|
||
|
|
||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||
|
%% Tests
|
||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||
|
|
||
|
%%%% test starting and stopping %%%%
|
||
|
|
||
|
-define(spew(Args), io:format(user, "~p~n", [{Args}])).
|
||
|
-define(assertExists(File),
|
||
|
{ ok, _ } = file:read_file_info(File)).
|
||
|
-define(assertEnoent(File),
|
||
|
{ error, enoent } = file:read_file_info(File)).
|
||
|
|
||
|
|
||
|
start_link_test_() ->
|
||
|
test_start_stop_(
|
||
|
fun start_helper_/0,
|
||
|
fun stop_helper_/1,
|
||
|
"start_link test"
|
||
|
).
|
||
|
|
||
|
start_stop_test_() ->
|
||
|
test_start_stop_(
|
||
|
fun() -> ok = start() end,
|
||
|
fun(_) -> ok = stop() end,
|
||
|
"start/0 stop/0"
|
||
|
).
|
||
|
|
||
|
start_sup_test_() ->
|
||
|
test_start_stop_(
|
||
|
fun() ->
|
||
|
{ok,Pid} = erlrrd_sup:start_link(),
|
||
|
Pid
|
||
|
end,
|
||
|
fun stop_helper_/1,
|
||
|
"sup:start_link/0 exit/2"
|
||
|
).
|
||
|
|
||
|
start_sup2_test_() ->
|
||
|
test_start_stop_(
|
||
|
fun() ->
|
||
|
check_cwd_helper_(),
|
||
|
{ok,Pid} = erlrrd_sup:start_link("./dummyrrdtool"),
|
||
|
Pid
|
||
|
end,
|
||
|
fun stop_helper_/1,
|
||
|
"sup:start_link/1 exit/2"
|
||
|
).
|
||
|
|
||
|
start_app_test_() ->
|
||
|
test_start_stop_(
|
||
|
fun() -> ok = erlrrd_app:start() end,
|
||
|
fun(_) -> ok = erlrrd_app:stop() end,
|
||
|
"app:start/0 app:stop/0"
|
||
|
).
|
||
|
|
||
|
%%%% test interfaces %%%%
|
||
|
|
||
|
datain_dataout_test_() ->
|
||
|
Prefix = "foo",
|
||
|
RRDFile = Prefix ++ ".rrd",
|
||
|
PNGFile = Prefix ++ ".png",
|
||
|
RRDDump = Prefix ++ ".rrd.xml",
|
||
|
RRDRestoredFile = Prefix ++ ".2.rrd",
|
||
|
Now = time_since_epoch(),
|
||
|
Then = Now - 86400,
|
||
|
StepSize = 60,
|
||
|
Steps = round((Now - Then) / StepSize),
|
||
|
{ setup,
|
||
|
fun() ->
|
||
|
check_cwd_helper_(),
|
||
|
file:delete(RRDFile),
|
||
|
file:delete(RRDDump),
|
||
|
file:delete(RRDRestoredFile),
|
||
|
?assertEnoent(RRDFile),
|
||
|
{ ok, Pid } = start_link(),
|
||
|
Pid
|
||
|
end,
|
||
|
fun(Pid) ->
|
||
|
stop_helper_(Pid),
|
||
|
file:delete(RRDFile),
|
||
|
file:delete(RRDDump),
|
||
|
file:delete(RRDRestoredFile),
|
||
|
ok
|
||
|
end,
|
||
|
{ inorder,
|
||
|
[
|
||
|
% create an rrd
|
||
|
fun() ->
|
||
|
{ok, _ } = erlrrd:create([
|
||
|
io_lib:fwrite("~s --start ~B", [RRDFile, Then]),
|
||
|
io_lib:fwrite(" --step ~B DS:thedata:GAUGE:~B:U:U",
|
||
|
[ StepSize, StepSize ]),
|
||
|
io_lib:fwrite(" RRA:AVERAGE:0.5:1:~B",[Steps])
|
||
|
])
|
||
|
end,
|
||
|
|
||
|
% write sin wave to rrd
|
||
|
fun() ->
|
||
|
lists:foreach(
|
||
|
fun(X) ->
|
||
|
P = p_func(X,Steps),
|
||
|
%io:format(user, "~s ~B:~f~n", [ RRDFile, Then + X * StepSize, P ]),
|
||
|
{ok, _ } = erlrrd:update(
|
||
|
io_lib:format("~s ~B:~f", [ RRDFile, Then + X * StepSize, P ])
|
||
|
)
|
||
|
end,
|
||
|
lists:seq(1, Steps)
|
||
|
)
|
||
|
end,
|
||
|
|
||
|
% check the update times
|
||
|
check_last_(RRDFile, Now),
|
||
|
fun() ->
|
||
|
{ ok, When } = erlrrd:first(RRDFile),
|
||
|
RoundThen = (erlang:trunc(Then/StepSize) + 1) * StepSize,
|
||
|
When = RoundThen
|
||
|
end,
|
||
|
?_test(?assertMatch({error, _}, erlrrd:last("/somenothing/file"))),
|
||
|
?_test(?assertMatch({error, _}, erlrrd:first("/somenothing/file"))),
|
||
|
|
||
|
% make a graph!! :)
|
||
|
fun() ->
|
||
|
{ ok, _ } = erlrrd:graph([
|
||
|
"-l 0 -r", " ",
|
||
|
"-w 700 -h 200 -a PNG ", PNGFile,
|
||
|
" DEF:thedata=", RRDFile, ":thedata:AVERAGE AREA:thedata#CC9945",
|
||
|
io_lib:format(" --start ~B --end ~B", [ Then, Now ])
|
||
|
])
|
||
|
% ok, now how can we check the graph??? hmm.
|
||
|
end,
|
||
|
|
||
|
% run fetch
|
||
|
fun() ->
|
||
|
{ ok, _Data } = erlrrd:fetch([
|
||
|
RRDFile, " AVERAGE ",
|
||
|
io_lib:format(" --start ~B --end ~B", [ Then, Now ])
|
||
|
]),
|
||
|
%?spew(Data),
|
||
|
%TODO check data
|
||
|
ok
|
||
|
end,
|
||
|
|
||
|
% dump the rrd
|
||
|
fun() ->
|
||
|
?assertEnoent(RRDDump),
|
||
|
{ ok, _ } = erlrrd:dump( RRDFile ++ " " ++ RRDDump ),
|
||
|
?assertExists(RRDDump)
|
||
|
end,
|
||
|
|
||
|
% restore the rrd
|
||
|
fun() ->
|
||
|
?assertExists(RRDDump),
|
||
|
?assertEnoent(RRDRestoredFile),
|
||
|
{ ok, _ } = erlrrd:restore( RRDDump ++ " " ++ RRDRestoredFile ),
|
||
|
?assertExists(RRDRestoredFile)
|
||
|
end,
|
||
|
check_last_(RRDRestoredFile, Now),
|
||
|
|
||
|
% xport
|
||
|
{ "xport",
|
||
|
fun() ->
|
||
|
?assertExists(RRDRestoredFile),
|
||
|
{ok, Response} = xport([
|
||
|
"DEF:c=", RRDRestoredFile, ":thedata:AVERAGE",
|
||
|
" XPORT:c",
|
||
|
io_lib:format(" --start ~B --end ~B", [ Then, Now ])
|
||
|
]),
|
||
|
%% TODO check response
|
||
|
Response
|
||
|
end
|
||
|
},
|
||
|
|
||
|
%lastupdate
|
||
|
fun() ->
|
||
|
{ ok, [ _, _, [Last]] } = lastupdate(RRDRestoredFile),
|
||
|
?assertMatch(
|
||
|
{match, _, _ },
|
||
|
regexp:match(Last,
|
||
|
% TODO make the regex match beginning of line? why no work?
|
||
|
io_lib:format("~B", [ Now ])
|
||
|
)
|
||
|
),
|
||
|
ok
|
||
|
end,
|
||
|
|
||
|
% info
|
||
|
fun() ->
|
||
|
{ ok, Info } = info(RRDFile),
|
||
|
[ ["filename = " ++ _L] | _T ] = Info
|
||
|
end,
|
||
|
|
||
|
fun() -> commas_are_cool end
|
||
|
]
|
||
|
}
|
||
|
}.
|
||
|
|
||
|
remote_cmds_test_() ->
|
||
|
Dir = "makadir",
|
||
|
{ setup,
|
||
|
fun() ->
|
||
|
check_cwd_helper_(),
|
||
|
?assertEnoent(Dir),
|
||
|
start_helper_()
|
||
|
end,
|
||
|
fun(P) ->
|
||
|
file:del_dir(Dir),
|
||
|
stop_helper_(P)
|
||
|
end,
|
||
|
{ inorder,
|
||
|
[
|
||
|
% pwd
|
||
|
{ "pwd1", fun() ->
|
||
|
{ ok, Cwd } = erlrrd:pwd(),
|
||
|
"stset/" ++ _ = lists:reverse(Cwd)
|
||
|
end},
|
||
|
{ "mkdir", fun() -> erlrrd:mkdir(Dir) end },
|
||
|
{ "cd ", fun() -> ok = erlrrd:cd(Dir) end},
|
||
|
% pwd
|
||
|
{ "pwd2",
|
||
|
fun() ->
|
||
|
{ ok, Cwd } = erlrrd:pwd(),
|
||
|
{ match, _, _ } =
|
||
|
regexp:match(Cwd, "/tests/" ++ Dir ++ "$")
|
||
|
end
|
||
|
},
|
||
|
{ "ls",
|
||
|
fun() ->
|
||
|
% relys on '.' and '..' always returning first?
|
||
|
% is that a bad idea?
|
||
|
{ ok, List } = ls(),
|
||
|
io:format(user," ~p~n", [List]),
|
||
|
?assert( lists:any(fun(X) -> X =:= ["d .."] end, List))
|
||
|
end },
|
||
|
fun() -> commas_are_cool end
|
||
|
]
|
||
|
}
|
||
|
}.
|
||
|
|
||
|
c_test_() ->
|
||
|
[
|
||
|
?_test(
|
||
|
[
|
||
|
["\"", "these", "\""], " ",
|
||
|
["\"", "are", "\""], " ",
|
||
|
["\"", "my", "\""], " ",
|
||
|
["\"", "args", "\""]
|
||
|
] = c(["these", "are", "my", "args"])),
|
||
|
?_test([[ "\"", "a", "\""]] = c(["a"]))
|
||
|
].
|
||
|
|
||
|
pass_newlines_test_() ->
|
||
|
M = "No newlines",
|
||
|
{ setup,
|
||
|
fun start_helper_/0,
|
||
|
fun stop_helper_/1,
|
||
|
[
|
||
|
?_assertMatch( { error, M }, cd("..\n")),
|
||
|
?_assertMatch( { error, M }, info("fart.rrd\n")),
|
||
|
?_assertMatch( { error, M },
|
||
|
create(["foo.rrd bar baz", [[[[<<"blahdedah\n">>]]]], "haha"])
|
||
|
),
|
||
|
fun() -> ok end
|
||
|
]
|
||
|
}.
|
||
|
|
||
|
graph_to_stdout_saftey_test_() ->
|
||
|
M = { error, "Graphing to stdout not supported." },
|
||
|
[
|
||
|
?_assertMatch( M, graph(" -")),
|
||
|
?_assertMatch( M, graph("-")),
|
||
|
?_assertMatch( M, graph(" - ")),
|
||
|
?_assertMatch( M, graph([" ", "-"," "])),
|
||
|
?_assertMatch( M, graph([" ", [[["-"]]]," "])),
|
||
|
?_assertMatch( M, graph([" ", [[["-"]]]])),
|
||
|
?_assertMatch( M, graph([[[["-"]]]," "])),
|
||
|
?_assertMatch( M, graph([" ", [[[<<"-">>]]]," "])),
|
||
|
?_assertMatch( M, graph([" ", [[[<<"-">>]]]])),
|
||
|
?_assertMatch( M, graph([[[[<<"-">>]]]," "])),
|
||
|
fun() -> ok end
|
||
|
].
|
||
|
|
||
|
%%%% tests for corner cases %%%%
|
||
|
|
||
|
cause_long_response_test_() ->
|
||
|
{ setup,
|
||
|
fun() ->
|
||
|
check_cwd_helper_(),
|
||
|
{ ok, Pid } = start_link("./dummyrrdtool -"),
|
||
|
Pid
|
||
|
end,
|
||
|
fun stop_helper_/1,
|
||
|
?_test(do(longresponse, []))
|
||
|
}.
|
||
|
|
||
|
cause_timeout_test_() ->
|
||
|
{ setup,
|
||
|
fun() ->
|
||
|
check_cwd_helper_(),
|
||
|
ok = application:set_env(erlrrd, timeout, 1),
|
||
|
{ ok, Pid } = erlrrd_sup:start_link("./dummyrrdtool -"),
|
||
|
Pid
|
||
|
end,
|
||
|
fun stop_helper_/1,
|
||
|
?_assertExit({port_timeout, _}, do(timeout, []))
|
||
|
}.
|
||
|
|
||
|
%%%% tests of privates %%%%
|
||
|
|
||
|
join_test() ->
|
||
|
[ "a", " ", "b", " ", "c"] = join(["a", "b", "c"], " ").
|
||
|
join_test_() ->
|
||
|
[
|
||
|
?_test([ "a", " ", "b", " ", "c"] = join(["a", "b", "c"], " ")),
|
||
|
?_assertNot([ "a", "b", " ", "c"] =:= join(["a", "b", "c"], " "))
|
||
|
].
|
||
|
|
||
|
has_newline_test_() ->
|
||
|
[
|
||
|
?_test( true = has_newline("\n")),
|
||
|
?_test( true = has_newline(["\n"])),
|
||
|
?_test( true = has_newline(
|
||
|
["these", ["are", [ "my args" ] | <<"newline\n">> ], "so", "there"])),
|
||
|
?_test( false = has_newline(
|
||
|
["these", ["are", [ "my args" ] | <<"newline">> ], "so", "there"])),
|
||
|
?_test( true = has_newline(
|
||
|
["these\n", ["are", [ "my args" ] | <<"newline">> ], "so", "there"])),
|
||
|
?_test( true = has_newline(
|
||
|
["these", ["are",
|
||
|
[ "my args" | [[[[[[[[ "blah\n"]]]]]]]] ] | <<"newline">> ],
|
||
|
"so", "there"])),
|
||
|
?_test( false = has_newline("")),
|
||
|
?_test( false = has_newline([])),
|
||
|
?_test( false = has_newline(<<>>))
|
||
|
].
|
||
|
|
||
|
%%%%% tests to get coverage %%%%%
|
||
|
|
||
|
should_be_done_better_test_() ->
|
||
|
{ setup,
|
||
|
fun start_helper_/0,
|
||
|
fun stop_helper_/1,
|
||
|
[
|
||
|
?_assertMatch( {error, _ }, tune("blah") ),
|
||
|
?_assertMatch( {error, _ }, resize("blah") ),
|
||
|
?_assertMatch( {error, _ }, updatev("blah") ),
|
||
|
?_test(ok)
|
||
|
]
|
||
|
}.
|
||
|
|
||
|
handle_cast_test_() ->
|
||
|
{ setup,
|
||
|
fun start_helper_/0,
|
||
|
fun stop_helper_/1,
|
||
|
?_test(cast(blah))
|
||
|
}.
|
||
|
|
||
|
handle_info_test_() ->
|
||
|
{ setup,
|
||
|
fun start_helper_/0,
|
||
|
fun stop_helper_/1,
|
||
|
?_test(?MODULE ! yo)
|
||
|
}.
|
||
|
|
||
|
stop_helper_test_() ->
|
||
|
{ setup,
|
||
|
% start fun
|
||
|
fun() ->
|
||
|
F = fun(F) ->
|
||
|
receive
|
||
|
Any -> io:format(user,"~p Hey, got: ~p~n", [ self(), Any ])
|
||
|
end,
|
||
|
F(F)
|
||
|
end,
|
||
|
spawn(
|
||
|
fun() ->
|
||
|
process_flag(trap_exit, true),
|
||
|
F(F)
|
||
|
end
|
||
|
)
|
||
|
end,
|
||
|
% stop fun,
|
||
|
fun(P) -> exit(P, kill) end,
|
||
|
% test gen
|
||
|
fun(P) ->
|
||
|
?_assertThrow({timeout,_}, stop_helper_(P, 1))
|
||
|
end
|
||
|
}.
|
||
|
|
||
|
port_exit_test_() ->
|
||
|
?_assert(
|
||
|
begin
|
||
|
io:format(user, "~n==== test: expect erlrrd exit~n", []),
|
||
|
{ok,Pid} = start_link("./dummyrrdtool"),
|
||
|
{ok, _} = do(die, []),
|
||
|
receive
|
||
|
{ 'EXIT', Pid, {port_exit, 1} } -> true
|
||
|
after 4000 ->
|
||
|
exit(Pid,kill),
|
||
|
false
|
||
|
end
|
||
|
end
|
||
|
).
|
||
|
|
||
|
code_change_test() ->
|
||
|
{ ok, state } = code_change( oldvsn, state, extra ).
|
||
|
|
||
|
-endif.
|