api: allow a start time (&start=...), end time (&end=...) and

resolution(&resolution=...) to be specified

Either an interval or start parameter is required, while the end
parameter is optional when start is specified. Start and end should both
be specified in unix time format. Resolution is in seconds and should be
one of [60 | 900 | 86400 | 604800] meaning [minute | 15min | day | week]
resolution respectively.

The [time, power] pairs returned by the api will be aligned to multiples
of the specified resolution, or maximum resolution available for the
requested time interval.
This commit is contained in:
Bart Van Der Meerssche 2010-05-22 13:21:31 +02:00
parent 1c72a072ff
commit a14c1775b1
1 changed files with 54 additions and 25 deletions

View File

@ -11,6 +11,7 @@
{rrdSensor,
rrdStart,
rrdEnd,
rrdResolution,
rrdFactor,
token,
jsonpCallback}).
@ -24,12 +25,18 @@ allowed_methods(ReqData, State) ->
malformed_request(ReqData, _State) ->
{_Version, ValidVersion} = check_version(wrq:get_req_header("X-Version", ReqData), wrq:get_qs_value("version", ReqData)),
{RrdSensor, ValidSensor} = check_sensor(wrq:path_info(sensor, ReqData)),
{RrdStart, RrdEnd, ValidTime} = check_time(wrq:get_qs_value("interval", ReqData), wrq:get_qs_value("start", ReqData), wrq:get_qs_value("end", ReqData)),
{RrdStart, RrdEnd, RrdResolution, ValidTime} = check_time(wrq:get_qs_value("interval", ReqData), wrq:get_qs_value("start", ReqData), wrq:get_qs_value("end", ReqData), wrq:get_qs_value("resolution", ReqData)),
{RrdFactor, ValidUnit} = check_unit(wrq:get_qs_value("unit", ReqData)),
{Token, ValidToken} = check_token(wrq:get_req_header("X-Token", ReqData), wrq:get_qs_value("token", ReqData)),
{JsonpCallback, ValidJsonpCallback} = check_jsonp_callback(wrq:get_qs_value("jsonp_callback", ReqData)),
State = #state{rrdSensor = RrdSensor, rrdStart = RrdStart, rrdEnd = RrdEnd, rrdFactor = RrdFactor, token = Token, jsonpCallback = JsonpCallback},
State = #state{rrdSensor = RrdSensor,
rrdStart = RrdStart,
rrdEnd = RrdEnd,
rrdResolution = RrdResolution,
rrdFactor = RrdFactor,
token = Token,
jsonpCallback = JsonpCallback},
{case {ValidVersion, ValidSensor, ValidTime, ValidUnit, ValidToken, ValidJsonpCallback} of
{true, true, true, true, true, true} -> false;
@ -49,15 +56,15 @@ is_authorized(ReqData, #state{rrdSensor = RrdSensor, token = Token} = State) ->
content_types_provided(ReqData, State) ->
{[{"application/json", to_json}], ReqData, State}.
to_json(ReqData, #state{rrdSensor = RrdSensor, rrdStart = RrdStart, rrdEnd = RrdEnd, rrdFactor = RrdFactor, jsonpCallback = JsonpCallback} = State) ->
to_json(ReqData, #state{rrdSensor = RrdSensor, rrdStart = RrdStart, rrdEnd = RrdEnd, rrdResolution = RrdResolution, rrdFactor = RrdFactor, jsonpCallback = JsonpCallback} = State) ->
case wrq:get_qs_value("interval", ReqData) of
"night" -> Path = "var/data/night/";
_Interval -> Path = "var/data/base/"
end,
%% debugging: io:format("~s~n", [erlrrd:c([[Path, [RrdSensor|".rrd"]], "AVERAGE", ["-s", RrdStart], ["-e", RrdEnd]])]),
%% debugging: io:format("~s~n", [erlrrd:c([[Path, [RrdSensor|".rrd"]], "AVERAGE", ["-s ", RrdStart], ["-e ", RrdEnd], ["-r ", RrdResolution]])]),
case erlrrd:fetch(erlrrd:c([[Path, [RrdSensor|".rrd"]], "AVERAGE", ["-s", RrdStart], ["-e", RrdEnd]])) of
case erlrrd:fetch(erlrrd:c([[Path, [RrdSensor|".rrd"]], "AVERAGE", ["-s ", RrdStart], ["-e ", RrdEnd], ["-r ", RrdResolution]])) of
{ok, Response} ->
Filtered = [re:split(X, "[:][ ]", [{return,list}]) || [X] <- Response, string:str(X, ":") == 11],
Datapoints = [[list_to_integer(X), round(list_to_float(Y) * RrdFactor)] || [X, Y] <- Filtered, string:len(Y) /= 3],
@ -73,7 +80,7 @@ to_json(ReqData, #state{rrdSensor = RrdSensor, rrdStart = RrdStart, rrdEnd = Rrd
{{halt, 404}, ReqData, State}
end.
%% internal functions
%% checks
check_version(undefined, undefined) ->
{false, false};
check_version(Version, undefined) ->
@ -92,28 +99,42 @@ check_sensor(Sensor) ->
_ -> {false, false}
end.
check_time(undefined, undefined, undefined) ->
{false, false, false};
check_time(Interval, undefined, undefined) ->
Intervals = [{"hour", "end-1h"},
{"day", "end-1d"},
{"month", "end-30d"},
{"year", "end-1y"},
{"night", "end-30d"}],
check_time(undefined, undefined, _End, _Resolution) ->
{false, false, false, false};
check_time(Interval, undefined, undefined, undefined) ->
check_time(Interval, undefined, undefined, "");
check_time(Interval, undefined, undefined, Resolution) ->
Now = unix_time(),
case lists:keyfind(Interval, 1, Intervals) of
false -> {false, false, false};
{_Interval, Start} -> {Start, "now", true}
Intervals = [{"hour", "end-1h", 60},
{"day", "end-1d", 900},
{"month", "end-30d", 86400},
{"year", "end-1y", 604800},
{"night", "end-30d", 86400}],
case {lists:keyfind(Interval, 1, Intervals), re:run(Resolution, "[0-9]+", [])} of
{false, _} -> {false, false, false, false};
{{_Interval, Start, _DefResolution}, {match, [{0,_}]}} ->
AlignedEnd = integer_to_list(time_align(Now, list_to_integer(Resolution))),
{Start, AlignedEnd, Resolution, true};
{{_Interval, Start, DefResolution}, _} ->
AlignedEnd = integer_to_list(time_align(Now, DefResolution)),
{Start, AlignedEnd, integer_to_list(DefResolution), true}
end;
check_time(undefined, Start, undefined) ->
check_time(undefined, Start, "now");
check_time(undefined, Start, End) ->
case {re:run(Start, "[0-9]+", []), re:run(End, "[0-9a-z]+", [])} of
{{match, [{0,_}]}, {match, [{0,_}]}} -> {Start, End, true};
_ -> {false, false, false}
check_time(undefined, Start, undefined, Resolution) ->
check_time(undefined, Start, integer_to_list(unix_time()), Resolution);
check_time(undefined, Start, End, undefined) ->
check_time(undefined, Start, End, "60");
check_time(undefined, Start, End, Resolution) ->
case {re:run(Start, "[0-9]+", []), re:run(End, "[0-9]+", []), re:run(Resolution, "[0-9]+", [])} of
{{match, [{0,_}]}, {match, [{0,_}]}, {match, [{0,_}]}} ->
AlignedStart = integer_to_list(time_align(list_to_integer(Start), list_to_integer(Resolution))),
AlignedEnd = integer_to_list(time_align(list_to_integer(End), list_to_integer(Resolution))),
{AlignedStart, AlignedEnd, Resolution, true};
_ -> {false, false, false, false}
end;
check_time(_, _, _) ->
{false, false, false}.
check_time(_, _, _, _) ->
{false, false, false, false}.
check_unit(Unit) ->
Units = [{"watt", 3600},
@ -144,3 +165,11 @@ check_jsonp_callback(JsonpCallback) ->
{match, [{0, Length}]} -> {JsonpCallback, true};
_ -> {false, false}
end.
%% helper functions
unix_time() ->
{Megaseconds, Seconds, _Microseconds} = erlang:now(),
Megaseconds*1000000 + Seconds.
time_align(Time, Resolution) ->
(Time div Resolution) * Resolution.