From a14c1775b1e5a150b9b35541db4d157b4af46e97 Mon Sep 17 00:00:00 2001 From: Bart Van Der Meerssche Date: Sat, 22 May 2010 13:21:31 +0200 Subject: [PATCH] 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. --- web/api/flukso/src/flukso_resource.erl | 79 ++++++++++++++++++-------- 1 file changed, 54 insertions(+), 25 deletions(-) diff --git a/web/api/flukso/src/flukso_resource.erl b/web/api/flukso/src/flukso_resource.erl index 31b0d6d..94c5e98 100644 --- a/web/api/flukso/src/flukso_resource.erl +++ b/web/api/flukso/src/flukso_resource.erl @@ -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.