summary | shortlog | log | commit | commitdiff | tree
raw | patch | inline | side by side (parent: eac4c08)
raw | patch | inline | side by side (parent: eac4c08)
author | Sebastian Harl <sh@tokkee.org> | |
Sat, 16 Aug 2014 19:12:05 +0000 (21:12 +0200) | ||
committer | Sebastian Harl <sh@tokkee.org> | |
Sat, 16 Aug 2014 19:12:05 +0000 (21:12 +0200) |
Syntax is: TIMESERIES <host>.<metric> [START <datetime>] [END <datetime>]
index 0126e892b1774f2071f255a0362ebb9404d77ef3..ef445d36bbbeb636bcf0a26d1355c0975b316ddb 100644 (file)
#include "core/object.h"
#include "core/store.h"
+#include "core/timeseries.h"
#include "utils/strbuf.h"
#include <inttypes.h>
} conn_lookup_t;
#define CONN_LOOKUP(obj) ((conn_lookup_t *)(obj))
+typedef struct {
+ sdb_conn_node_t super;
+ char *hostname;
+ char *metric;
+ sdb_timeseries_opts_t opts;
+} conn_ts_t;
+#define CONN_TS(obj) ((conn_ts_t *)(obj))
+
/*
* type helper functions
*/
sdb_object_deref(SDB_OBJ(CONN_LOOKUP(obj)->filter));
} /* conn_lookup_destroy */
+static void __attribute__((unused))
+conn_ts_destroy(sdb_object_t *obj)
+{
+ if (CONN_TS(obj)->hostname)
+ free(CONN_TS(obj)->hostname);
+ if (CONN_TS(obj)->metric)
+ free(CONN_TS(obj)->metric);
+} /* conn_ts_destroy */
+
#ifdef __cplusplus
} /* extern "C" */
#endif
diff --git a/src/frontend/grammar.y b/src/frontend/grammar.y
index a297714c074f20a22690245c5c51a8ab1f6d89bb..58363df01dbebe37b861394f09632d47397b850a 100644 (file)
--- a/src/frontend/grammar.y
+++ b/src/frontend/grammar.y
#include "core/store.h"
#include "core/store-private.h"
+#include "core/time.h"
#include "utils/error.h"
#include "utils/llist.h"
char *str;
sdb_data_t data;
+ sdb_time_t datetime;
sdb_llist_t *list;
sdb_conn_node_t *node;
%token CMP_LT CMP_LE CMP_GE CMP_GT
%token CONCAT
+%token START END
+
/* NULL token */
%token NULL_T
-%token FETCH LIST LOOKUP
+%token FETCH LIST LOOKUP TIMESERIES
%token <str> IDENTIFIER STRING
%token <data> INTEGER FLOAT
+%token <datetime> DATE TIME
+
/* Precedence (lowest first): */
%left OR
%left AND
fetch_statement
list_statement
lookup_statement
+ timeseries_statement
matching_clause
filter_clause
condition
%type <data> data
interval interval_elem
+%type <datetime> datetime
+ start_clause end_clause
+
%destructor { free($$); } <str>
%destructor { sdb_object_deref(SDB_OBJ($$)); } <node> <m> <expr>
|
lookup_statement
|
+ timeseries_statement
+ |
/* empty */
{
$$ = NULL;
|
/* empty */ { $$ = NULL; }
+/*
+ * TIMESERIES <host>.<metric> [START <datetime>] [END <datetime>];
+ *
+ * Returns a time-series for the specified host's metric.
+ */
+timeseries_statement:
+ TIMESERIES STRING '.' STRING start_clause end_clause
+ {
+ $$ = SDB_CONN_NODE(sdb_object_create_dT(/* name = */ NULL,
+ conn_ts_t, conn_ts_destroy));
+ CONN_TS($$)->hostname = $2;
+ CONN_TS($$)->metric = $4;
+ CONN_TS($$)->opts.start = $5;
+ CONN_TS($$)->opts.end = $6;
+ $$->cmd = CONNECTION_TIMESERIES;
+ }
+ ;
+
+start_clause:
+ START datetime { $$ = $2; }
+ |
+ /* empty */ { $$ = sdb_gettime() - SDB_INTERVAL_HOUR; }
+
+end_clause:
+ END datetime { $$ = $2; }
+ |
+ /* empty */ { $$ = sdb_gettime(); }
+
/*
* Basic expressions.
*/
|
FLOAT { $$ = $1; }
|
+ datetime { $$.type = SDB_TYPE_DATETIME; $$.data.datetime = $1; }
+ |
interval { $$ = $1; }
;
+datetime:
+ DATE TIME { $$ = $1 + $2; }
+ |
+ DATE { $$ = $1; }
+ |
+ TIME { $$ = $1; }
+ ;
+
interval:
interval interval_elem
{
diff --git a/src/frontend/scanner.l b/src/frontend/scanner.l
index 4bd5655e04bd9d870ccadcafa5da15d9c7c3ba74..5af85e047ec2913747f010638d884f9256cb1943 100644 (file)
--- a/src/frontend/scanner.l
+++ b/src/frontend/scanner.l
#include "frontend/grammar.h"
#include "utils/error.h"
+#include <assert.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
+#include <time.h>
+
#define YY_EXTRA_TYPE sdb_fe_yyextra_t *
static struct {
const char *name;
int id;
} reserved_words[] = {
- { "AND", AND },
- { "FETCH", FETCH },
- { "FILTER", FILTER },
- { "IS", IS },
- { "LIST", LIST },
- { "LOOKUP", LOOKUP },
- { "MATCHING", MATCHING },
- { "NOT", NOT },
- { "NULL", NULL_T },
- { "OR", OR },
+ { "AND", AND },
+ { "END", END },
+ { "FETCH", FETCH },
+ { "FILTER", FILTER },
+ { "IS", IS },
+ { "LIST", LIST },
+ { "LOOKUP", LOOKUP },
+ { "MATCHING", MATCHING },
+ { "NOT", NOT },
+ { "NULL", NULL_T },
+ { "OR", OR },
+ { "START", START },
+ { "TIMESERIES", TIMESERIES },
};
void
float5 ([Nn][Aa][Nn])
float ({float1}|{float2}|{float3}|{float4}|{float5})
+/*
+ * Time constants.
+ */
+date ([0-9]{4}-[0-9]{2}-[0-9]{2})
+time ([0-9]{1,2}:[0-9]{1,2}(:[0-9]{1,2}(\.[0-9]{1,9})?)?)
+
%%
{whitespace} |
return FLOAT;
}
+{date} {
+ struct tm tm;
+ memset(&tm, 0, sizeof(tm));
+ if (! strptime(yytext, "%Y-%m-%d", &tm)) {
+ char errmsg[1024];
+ snprintf(errmsg, sizeof(errmsg),
+ "Failed to parse '%s' as date", yytext);
+ sdb_fe_yyerror(yylloc, yyscanner, errmsg);
+ return SCANNER_ERROR;
+ }
+ yylval->datetime = SECS_TO_SDB_TIME(mktime(&tm));
+ return DATE;
+ }
+{time} {
+ struct tm tm;
+ char time[9], ns[10];
+ char *tmp;
+
+ memset(&tm, 0, sizeof(tm));
+ memset(time, '\0', sizeof(time));
+ memset(ns, '0', sizeof(ns));
+ ns[sizeof(ns) - 1] = '\0';
+
+ tmp = strchr(yytext, '.');
+ if (tmp) {
+ size_t i;
+ *tmp = '\0';
+ ++tmp;
+ strncpy(ns, tmp, sizeof(ns));
+ for (i = strlen(ns); i < 9; ++i)
+ ns[i] = '0';
+ }
+ strncpy(time, yytext, sizeof(time));
+ if (tmp) {
+ /* reset for better error messages */
+ --tmp;
+ *tmp = '.';
+ }
+
+ tmp = strchr(time, ':');
+ assert(tmp);
+ tmp = strchr(tmp + 1, ':');
+ if (! tmp)
+ strncat(time, ":00", sizeof(time));
+
+ if (! strptime(time, "%H:%M:%S", &tm)) {
+ char errmsg[1024];
+ snprintf(errmsg, sizeof(errmsg),
+ "Failed to parse '%s' as time", yytext);
+ sdb_fe_yyerror(yylloc, yyscanner, errmsg);
+ return SCANNER_ERROR;
+ }
+
+ yylval->datetime = SECS_TO_SDB_TIME(mktime(&tm));
+ yylval->datetime += (sdb_time_t)strtoll(ns, NULL, 10);
+ return TIME;
+ }
+
= { return CMP_EQUAL; }
!= { return CMP_NEQUAL; }
=~ { return CMP_REGEX; }
index 640e2cf542f4f5f2453d7e0fa4015ae5e0b62847..2e82738449cd6a50826e22648e0dcd735167fca4 100644 (file)
*/
CONNECTION_LOOKUP,
+ /*
+ * CONNECTION_TIMESERIES:
+ * Execute the 'TIMESERIES' command in the server. This command is not yet
+ * supported on the wire. Use CONNECTION_QUERY instead.
+ */
+ CONNECTION_TIMESERIES,
+
/*
* Command subcomponents.
*/
index e6b30635fd3dc94ec14f36f66b4b96f57f44e5c4..9005811d500b68b6ce594fb7fcc861af2d85c449 100644 (file)
"FILTER :age>"
":interval", -1, 1, CONNECTION_LOOKUP },
+ { "TIMESERIES 'host'.'metric' "
+ "START 2014-01-01 "
+ "END 2014-12-31 "
+ "23:59:59", -1, 1, CONNECTION_TIMESERIES },
+ { "TIMESERIES 'host'.'metric' "
+ "START 2014-02-02 "
+ "14:02", -1, 1, CONNECTION_TIMESERIES },
+ { "TIMESERIES 'host'.'metric' "
+ "END 2014-02-02", -1, 1, CONNECTION_TIMESERIES },
+ { "TIMESERIES "
+ "'host'.'metric'", -1, 1, CONNECTION_TIMESERIES },
+
/* numeric constants */
{ "LOOKUP hosts MATCHING "
"attribute.foo = "
{ "attribute.foo = 123", -1, MATCHER_EQ },
{ "attribute.foo >= 123", -1, MATCHER_GE },
{ "attribute.foo > 123", -1, MATCHER_GT },
+ /* datetime expressions */
+ { "attribute.foo = "
+ "2014-08-16", -1, MATCHER_EQ },
+ { "attribute.foo = "
+ "17:23", -1, MATCHER_EQ },
+ { "attribute.foo = "
+ "17:23:53", -1, MATCHER_EQ },
+ { "attribute.foo = "
+ "17:23:53.123", -1, MATCHER_EQ },
+ { "attribute.foo = "
+ "17:23:53.123456789", -1, MATCHER_EQ },
+ { "attribute.foo = "
+ "2014-08-16 17:23", -1, MATCHER_EQ },
+ { "attribute.foo = "
+ "2014-08-16 17:23:53", -1, MATCHER_EQ },
/* NULL; while this is an implementation detail,
* IS NULL currently maps to an equality matcher */
{ "attribute.foo IS NULL", -1, MATCHER_ISNULL },