Code

frontend/parser: Added support for the 'TIMESERIES' command.
authorSebastian Harl <sh@tokkee.org>
Sat, 16 Aug 2014 19:12:05 +0000 (21:12 +0200)
committerSebastian Harl <sh@tokkee.org>
Sat, 16 Aug 2014 19:12:05 +0000 (21:12 +0200)
Syntax is: TIMESERIES <host>.<metric> [START <datetime>] [END <datetime>]

src/frontend/connection-private.h
src/frontend/grammar.y
src/frontend/scanner.l
src/include/frontend/proto.h
t/unit/frontend/parser_test.c

index 0126e892b1774f2071f255a0362ebb9404d77ef3..ef445d36bbbeb636bcf0a26d1355c0975b316ddb 100644 (file)
@@ -36,6 +36,7 @@
 
 #include "core/object.h"
 #include "core/store.h"
+#include "core/timeseries.h"
 #include "utils/strbuf.h"
 
 #include <inttypes.h>
@@ -105,6 +106,14 @@ typedef struct {
 } 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
  */
@@ -136,6 +145,15 @@ conn_lookup_destroy(sdb_object_t *obj)
        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
index a297714c074f20a22690245c5c51a8ab1f6d89bb..58363df01dbebe37b861394f09632d47397b850a 100644 (file)
@@ -33,6 +33,7 @@
 
 #include "core/store.h"
 #include "core/store-private.h"
+#include "core/time.h"
 
 #include "utils/error.h"
 #include "utils/llist.h"
@@ -70,6 +71,7 @@ sdb_fe_yyerror(YYLTYPE *lval, sdb_fe_yyscan_t scanner, const char *msg);
        char *str;
 
        sdb_data_t data;
+       sdb_time_t datetime;
 
        sdb_llist_t     *list;
        sdb_conn_node_t *node;
@@ -87,15 +89,19 @@ sdb_fe_yyerror(YYLTYPE *lval, sdb_fe_yyscan_t scanner, const char *msg);
 %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
@@ -115,6 +121,7 @@ sdb_fe_yyerror(YYLTYPE *lval, sdb_fe_yyscan_t scanner, const char *msg);
        fetch_statement
        list_statement
        lookup_statement
+       timeseries_statement
        matching_clause
        filter_clause
        condition
@@ -129,6 +136,9 @@ sdb_fe_yyerror(YYLTYPE *lval, sdb_fe_yyscan_t scanner, const char *msg);
 %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>
 
@@ -194,6 +204,8 @@ statement:
        |
        lookup_statement
        |
+       timeseries_statement
+       |
        /* empty */
                {
                        $$ = NULL;
@@ -295,6 +307,34 @@ filter_clause:
        |
        /* 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.
  */
@@ -476,9 +516,19 @@ data:
        |
        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
                {
index 4bd5655e04bd9d870ccadcafa5da15d9c7c3ba74..5af85e047ec2913747f010638d884f9256cb1943 100644 (file)
 #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
@@ -111,6 +117,12 @@ float4             ([\+\-]?[Ii][Nn][Ff]([Ii][Nn][Ii][Tt][Yy])?)
 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} |
@@ -149,6 +161,64 @@ float              ({float1}|{float2}|{float3}|{float4}|{float5})
                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)
@@ -132,6 +132,13 @@ typedef enum {
         */
        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)
@@ -97,6 +97,18 @@ START_TEST(test_parse)
                  "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 = "
@@ -290,6 +302,21 @@ START_TEST(test_parse_matcher)
                { "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 },