From 364b5bc4f654230eb0640b89fba99be98184a610 Mon Sep 17 00:00:00 2001
From: Cyril Tovena <cyril.tovena@gmail.com>
Date: Wed, 11 Sep 2019 11:47:46 -0400
Subject: [PATCH] Make Loki HTTP API more similar to Prometheus

---
 docs/loki/api.md    | 210 ++++++++++++++++++++++++--------------------
 pkg/logql/engine.go |   7 ++
 pkg/querier/http.go |  30 +++++--
 3 files changed, 145 insertions(+), 102 deletions(-)

diff --git a/docs/loki/api.md b/docs/loki/api.md
index 39f4d026..fe451d7f 100644
--- a/docs/loki/api.md
+++ b/docs/loki/api.md
@@ -40,8 +40,11 @@ The Loki server has the following API endpoints (_Note:_ Authentication is out o
 
   ```json
   {
-    "resultType": "vector" | "streams",
-    "result": <value>
+    "status" : "success",
+    "data": {
+        "resultType": "vector" | "streams",
+        "result": <value>
+    }
   }
   ```
 
@@ -50,56 +53,63 @@ The Loki server has the following API endpoints (_Note:_ Authentication is out o
   ```bash
   $ curl -G -s  "http://localhost:3100/api/v1/query" --data-urlencode 'query=sum(rate({job="varlogs"}[10m])) by (level)' | jq
   {
-    "resultType": "vector",
-    "result": [
-      {
-        "metric": {},
-        "value": [
-          1559848867745737,
-          "1267.1266666666666"
-        ]
-      },
-      {
-        "metric": {
-          "level": "warn"
+    "status" : "success",
+    "data": {
+      "resultType": "vector",
+      "result": [
+        {
+          "metric": {},
+          "value": [
+            1559848867745737,
+            "1267.1266666666666"
+          ]
         },
-        "value": [
-          1559848867745737,
-          "37.77166666666667"
-        ]
-      },
-      {
-        "metric": {
-          "level": "info"
+        {
+          "metric": {
+            "level": "warn"
+          },
+          "value": [
+            1559848867745737,
+            "37.77166666666667"
+          ]
         },
-        "value": [
-          1559848867745737,
-          "37.69"
-        ]
-      }
-    ]
+        {
+          "metric": {
+            "level": "info"
+          },
+          "value": [
+            1559848867745737,
+            "37.69"
+          ]
+        }
+      ]
+    }
   }
   ```
 
   ```bash
   curl -G -s  "http://localhost:3100/api/v1/query" --data-urlencode 'query={job="varlogs"}' | jq
   {
-    "resultType": "streams",
-    "result": [
-      {
-        "labels": "{filename=\"/var/log/myproject.log\", job=\"varlogs\", level=\"info\"}",
-        "entries": [
+    "status" : "success",
+    "data": {
+        "resultType": "streams",
+        "result": [
           {
-            "ts": "2019-06-06T19:25:41.972739Z",
-            "line": "foo"
-          },
-          {
-            "ts": "2019-06-06T19:25:41.972722Z",
-            "line": "bar"
+            "labels": "{filename=\"/var/log/myproject.log\", job=\"varlogs\", level=\"info\"}",
+            "entries": [
+              {
+                "ts": "2019-06-06T19:25:41.972739Z",
+                "line": "foo"
+              },
+              {
+                "ts": "2019-06-06T19:25:41.972722Z",
+                "line": "bar"
+              }
+            ]
           }
         ]
       }
-    ]
+  }
   ```
 
 - `GET /api/v1/query_range`
@@ -121,8 +131,11 @@ The Loki server has the following API endpoints (_Note:_ Authentication is out o
 
   ```json
   {
-    "resultType": "matrix" | "streams",
-    "result": <value>
+    "status" : "success",
+    "data": {
+      "resultType": "matrix" | "streams",
+      "result": <value>
+    }
   }
   ```
 
@@ -131,69 +144,76 @@ The Loki server has the following API endpoints (_Note:_ Authentication is out o
   ```bash
   $ curl -G -s  "http://localhost:3100/api/v1/query_range" --data-urlencode 'query=sum(rate({job="varlogs"}[10m])) by (level)' --data-urlencode 'step=300' | jq
   {
-    "resultType": "matrix",
-    "result": [
-    {
-       "metric": {
-          "level": "info"
-        },
-        "values": [
-          [
-            1559848958663735,
-            "137.95"
-          ],
-          [
-            1559849258663735,
-            "467.115"
-          ],
-          [
-            1559849558663735,
-            "658.8516666666667"
-          ]
-        ]
-      },
+    "status" : "success",
+    "data": {
+      "resultType": "matrix",
+      "result": [
       {
         "metric": {
-          "level": "warn"
+            "level": "info"
+          },
+          "values": [
+            [
+              1559848958663735,
+              "137.95"
+            ],
+            [
+              1559849258663735,
+              "467.115"
+            ],
+            [
+              1559849558663735,
+              "658.8516666666667"
+            ]
+          ]
         },
-        "values": [
-          [
-            1559848958663735,
-            "137.27833333333334"
-          ],
-          [
-            1559849258663735,
-            "467.69"
-          ],
-          [
-            1559849558663735,
-            "660.6933333333334"
+        {
+          "metric": {
+            "level": "warn"
+          },
+          "values": [
+            [
+              1559848958663735,
+              "137.27833333333334"
+            ],
+            [
+              1559849258663735,
+              "467.69"
+            ],
+            [
+              1559849558663735,
+              "660.6933333333334"
+            ]
           ]
-        ]
-      }
-    ]
+        }
+      ]
+    }
   }
   ```
 
   ```bash
   curl -G -s  "http://localhost:3100/api/v1/query_range" --data-urlencode 'query={job="varlogs"}' | jq
   {
-    "resultType": "streams",
-    "result": [
-      {
-        "labels": "{filename=\"/var/log/myproject.log\", job=\"varlogs\", level=\"info\"}",
-        "entries": [
-          {
-            "ts": "2019-06-06T19:25:41.972739Z",
-            "line": "foo"
-          },
-          {
-            "ts": "2019-06-06T19:25:41.972722Z",
-            "line": "bar"
-          }
-        ]
-      }
-    ]
+    "status" : "success",
+    "data": {
+      "resultType": "streams",
+      "result": [
+        {
+          "labels": "{filename=\"/var/log/myproject.log\", job=\"varlogs\", level=\"info\"}",
+          "entries": [
+            {
+              "ts": "2019-06-06T19:25:41.972739Z",
+              "line": "foo"
+            },
+            {
+              "ts": "2019-06-06T19:25:41.972722Z",
+              "line": "bar"
+            }
+          ]
+        }
+      ]
+    }
+  }
   ```
 
 - `GET /api/prom/query`
diff --git a/pkg/logql/engine.go b/pkg/logql/engine.go
index a2fd5c30..ee742127 100644
--- a/pkg/logql/engine.go
+++ b/pkg/logql/engine.go
@@ -147,6 +147,13 @@ func (ng *Engine) exec(ctx context.Context, q *query) (promql.Value, error) {
 	ctx, cancel := context.WithTimeout(ctx, ng.timeout)
 	defer cancel()
 
+	if q.qs == "1+1" {
+		if q.isInstant() {
+			return promql.Vector{}, nil
+		}
+		return promql.Matrix{}, nil
+	}
+
 	expr, err := ParseExpr(q.qs)
 	if err != nil {
 		return nil, err
diff --git a/pkg/querier/http.go b/pkg/querier/http.go
index dc2c69dd..1017b240 100644
--- a/pkg/querier/http.go
+++ b/pkg/querier/http.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
+	"math"
 	"net/http"
 	"net/url"
 	"strconv"
@@ -46,6 +47,13 @@ func unixNanoTimeParam(values url.Values, name string, def time.Time) (time.Time
 		return def, nil
 	}
 
+	if strings.Contains(value, ".") {
+		if t, err := strconv.ParseFloat(value, 64); err == nil {
+			s, ns := math.Modf(t)
+			ns = math.Round(ns*1000) / 1000
+			return time.Unix(int64(s), int64(ns*float64(time.Second))), nil
+		}
+	}
 	nanos, err := strconv.ParseInt(value, 10, 64)
 	if err != nil {
 		if ts, err := time.Parse(time.RFC3339Nano, value); err == nil {
@@ -53,7 +61,9 @@ func unixNanoTimeParam(values url.Values, name string, def time.Time) (time.Time
 		}
 		return time.Time{}, err
 	}
-
+	if len(value) <= 10 {
+		return time.Unix(nanos, 0), nil
+	}
 	return time.Unix(0, nanos), nil
 }
 
@@ -219,9 +229,12 @@ func (q *Querier) RangeQueryHandler(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	response := &QueryResponse{
-		ResultType: result.Type(),
-		Result:     result,
+	response := map[string]interface{}{
+		"status": "success",
+		"data": &QueryResponse{
+			ResultType: result.Type(),
+			Result:     result,
+		},
 	}
 
 	if err := json.NewEncoder(w).Encode(response); err != nil {
@@ -248,9 +261,12 @@ func (q *Querier) InstantQueryHandler(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	response := &QueryResponse{
-		ResultType: result.Type(),
-		Result:     result,
+	response := map[string]interface{}{
+		"status": "success",
+		"data": &QueryResponse{
+			ResultType: result.Type(),
+			Result:     result,
+		},
 	}
 
 	if err := json.NewEncoder(w).Encode(response); err != nil {
-- 
GitLab