Просмотр исходного кода

mssql: fix precision for time columns in time series query mode

Marcus Efraimsson 7 лет назад
Родитель
Сommit
9d84e6f31f
3 измененных файлов с 358 добавлено и 63 удалено
  1. 122 26
      docker/blocks/mssql_tests/dashboard.json
  2. 8 7
      pkg/tsdb/mssql/mssql.go
  3. 228 30
      pkg/tsdb/mssql/mssql_test.go

+ 122 - 26
docker/blocks/mssql_tests/dashboard.json

@@ -100,7 +100,7 @@
   "gnetId": null,
   "graphTooltip": 0,
   "id": null,
-  "iteration": 1521715844826,
+  "iteration": 1523320861623,
   "links": [],
   "panels": [
     {
@@ -443,7 +443,11 @@
           "min": null,
           "show": true
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     {
       "aliasColors": {},
@@ -522,7 +526,11 @@
           "min": null,
           "show": true
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     {
       "aliasColors": {},
@@ -601,7 +609,11 @@
           "min": null,
           "show": true
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     {
       "aliasColors": {},
@@ -680,7 +692,11 @@
           "min": null,
           "show": true
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     {
       "aliasColors": {},
@@ -759,7 +775,11 @@
           "min": null,
           "show": true
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     {
       "aliasColors": {},
@@ -838,7 +858,11 @@
           "min": null,
           "show": true
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     {
       "aliasColors": {},
@@ -927,7 +951,11 @@
           "min": null,
           "show": true
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     {
       "aliasColors": {},
@@ -1026,7 +1054,11 @@
           "min": null,
           "show": true
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     {
       "aliasColors": {},
@@ -1115,7 +1147,11 @@
           "min": null,
           "show": true
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     {
       "aliasColors": {},
@@ -1196,7 +1232,11 @@
           "min": null,
           "show": true
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     {
       "aliasColors": {},
@@ -1285,7 +1325,11 @@
           "min": null,
           "show": true
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     {
       "aliasColors": {},
@@ -1366,7 +1410,11 @@
           "min": null,
           "show": true
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     {
       "aliasColors": {},
@@ -1455,7 +1503,11 @@
           "min": null,
           "show": true
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     {
       "aliasColors": {},
@@ -1536,7 +1588,11 @@
           "min": null,
           "show": true
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     {
       "aliasColors": {},
@@ -1619,7 +1675,11 @@
           "min": null,
           "show": true
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     {
       "aliasColors": {},
@@ -1702,7 +1762,11 @@
           "min": null,
           "show": true
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     {
       "aliasColors": {},
@@ -1792,7 +1856,11 @@
           "min": null,
           "show": true
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     {
       "aliasColors": {},
@@ -1875,7 +1943,11 @@
           "min": null,
           "show": true
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     {
       "aliasColors": {},
@@ -1965,7 +2037,11 @@
           "min": null,
           "show": true
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     {
       "aliasColors": {},
@@ -2048,7 +2124,11 @@
           "min": null,
           "show": true
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     {
       "aliasColors": {},
@@ -2138,7 +2218,11 @@
           "min": null,
           "show": true
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     {
       "aliasColors": {},
@@ -2221,7 +2305,11 @@
           "min": null,
           "show": true
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     {
       "aliasColors": {},
@@ -2311,7 +2399,11 @@
           "min": null,
           "show": true
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     {
       "aliasColors": {},
@@ -2394,7 +2486,11 @@
           "min": null,
           "show": true
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     }
   ],
   "refresh": false,
@@ -2504,5 +2600,5 @@
   "timezone": "",
   "title": "Microsoft SQL Server Data Source Test",
   "uid": "GlAqcPgmz",
-  "version": 57
+  "version": 58
 }

+ 8 - 7
pkg/tsdb/mssql/mssql.go

@@ -8,8 +8,6 @@ import (
 	"strconv"
 	"strings"
 
-	"time"
-
 	"math"
 
 	_ "github.com/denisenkom/go-mssqldb"
@@ -231,15 +229,18 @@ func (e MssqlQueryEndpoint) transformToTimeSeries(query *tsdb.Query, rows *core.
 			return err
 		}
 
+		// converts column named time to unix timestamp in milliseconds to make
+		// native mysql datetime types and epoch dates work in
+		// annotation and table queries.
+		tsdb.ConvertSqlTimeColumnToEpochMs(values, timeIndex)
+
 		switch columnValue := values[timeIndex].(type) {
 		case int64:
-			timestamp = float64(columnValue * 1000)
+			timestamp = float64(columnValue)
 		case float64:
-			timestamp = columnValue * 1000
-		case time.Time:
-			timestamp = (float64(columnValue.Unix()) * 1000) + float64(columnValue.Nanosecond()/1e6) // in case someone is trying to map times beyond 2262 :D
+			timestamp = columnValue
 		default:
-			return fmt.Errorf("Invalid type for column time, must be of type timestamp or unix timestamp")
+			return fmt.Errorf("Invalid type for column time, must be of type timestamp or unix timestamp, got: %T %v", columnValue, columnValue)
 		}
 
 		if metricIndex >= 0 {

+ 228 - 30
pkg/tsdb/mssql/mssql_test.go

@@ -188,10 +188,8 @@ func TestMSSQL(t *testing.T) {
 				})
 			}
 
-			for _, s := range series {
-				_, err = sess.Insert(s)
-				So(err, ShouldBeNil)
-			}
+			_, err = sess.InsertMulti(series)
+			So(err, ShouldBeNil)
 
 			Convey("When doing a metric query using timeGroup", func() {
 				query := &tsdb.TsdbQuery{
@@ -312,10 +310,18 @@ func TestMSSQL(t *testing.T) {
 
 		Convey("Given a table with metrics having multiple values and measurements", func() {
 			type metric_values struct {
-				Time        time.Time
-				Measurement string
-				ValueOne    int64 `xorm:"integer 'valueOne'"`
-				ValueTwo    int64 `xorm:"integer 'valueTwo'"`
+				Time                time.Time
+				TimeInt64           int64    `xorm:"bigint 'timeInt64' not null"`
+				TimeInt64Nullable   *int64   `xorm:"bigint 'timeInt64Nullable' null"`
+				TimeFloat64         float64  `xorm:"float 'timeFloat64' not null"`
+				TimeFloat64Nullable *float64 `xorm:"float 'timeFloat64Nullable' null"`
+				TimeInt32           int32    `xorm:"int(11) 'timeInt32' not null"`
+				TimeInt32Nullable   *int32   `xorm:"int(11) 'timeInt32Nullable' null"`
+				TimeFloat32         float32  `xorm:"float(11) 'timeFloat32' not null"`
+				TimeFloat32Nullable *float32 `xorm:"float(11) 'timeFloat32Nullable' null"`
+				Measurement         string
+				ValueOne            int64 `xorm:"integer 'valueOne'"`
+				ValueTwo            int64 `xorm:"integer 'valueTwo'"`
 			}
 
 			if exist, err := sess.IsTableExist(metric_values{}); err != nil || exist {
@@ -330,26 +336,219 @@ func TestMSSQL(t *testing.T) {
 				return rand.Int63n(max-min) + min
 			}
 
+			var tInitial time.Time
+
 			series := []*metric_values{}
-			for _, t := range genTimeRangeByInterval(fromStart.Add(-30*time.Minute), 90*time.Minute, 5*time.Minute) {
-				series = append(series, &metric_values{
-					Time:        t,
-					Measurement: "Metric A",
-					ValueOne:    rnd(0, 100),
-					ValueTwo:    rnd(0, 100),
-				})
-				series = append(series, &metric_values{
-					Time:        t,
-					Measurement: "Metric B",
-					ValueOne:    rnd(0, 100),
-					ValueTwo:    rnd(0, 100),
-				})
+			for i, t := range genTimeRangeByInterval(fromStart.Add(-30*time.Minute), 90*time.Minute, 5*time.Minute) {
+				if i == 0 {
+					tInitial = t
+				}
+				tSeconds := t.Unix()
+				tSecondsInt32 := int32(tSeconds)
+				tSecondsFloat32 := float32(tSeconds)
+				tMilliseconds := tSeconds * 1e3
+				tMillisecondsFloat := float64(tMilliseconds)
+				first := metric_values{
+					Time:                t,
+					TimeInt64:           tMilliseconds,
+					TimeInt64Nullable:   &(tMilliseconds),
+					TimeFloat64:         tMillisecondsFloat,
+					TimeFloat64Nullable: &tMillisecondsFloat,
+					TimeInt32:           tSecondsInt32,
+					TimeInt32Nullable:   &tSecondsInt32,
+					TimeFloat32:         tSecondsFloat32,
+					TimeFloat32Nullable: &tSecondsFloat32,
+					Measurement:         "Metric A",
+					ValueOne:            rnd(0, 100),
+					ValueTwo:            rnd(0, 100),
+				}
+				second := first
+				second.Measurement = "Metric B"
+				second.ValueOne = rnd(0, 100)
+				second.ValueTwo = rnd(0, 100)
+
+				series = append(series, &first)
+				series = append(series, &second)
 			}
 
-			for _, s := range series {
-				_, err = sess.Insert(s)
+			_, err = sess.InsertMulti(series)
+			So(err, ShouldBeNil)
+
+			Convey("When doing a metric query using epoch (int64) as time column should return metric with time in milliseconds", func() {
+				query := &tsdb.TsdbQuery{
+					Queries: []*tsdb.Query{
+						{
+							Model: simplejson.NewFromAny(map[string]interface{}{
+								"rawSql": `SELECT TOP 1 timeInt64 as time, valueOne FROM metric_values ORDER BY time`,
+								"format": "time_series",
+							}),
+							RefId: "A",
+						},
+					},
+				}
+
+				resp, err := endpoint.Query(nil, nil, query)
 				So(err, ShouldBeNil)
-			}
+				queryResult := resp.Results["A"]
+				So(queryResult.Error, ShouldBeNil)
+
+				So(len(queryResult.Series), ShouldEqual, 1)
+				So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
+			})
+
+			Convey("When doing a metric query using epoch (int64 nullable) as time column should return metric with time in milliseconds", func() {
+				query := &tsdb.TsdbQuery{
+					Queries: []*tsdb.Query{
+						{
+							Model: simplejson.NewFromAny(map[string]interface{}{
+								"rawSql": `SELECT TOP 1 timeInt64Nullable as time, valueOne FROM metric_values ORDER BY time`,
+								"format": "time_series",
+							}),
+							RefId: "A",
+						},
+					},
+				}
+
+				resp, err := endpoint.Query(nil, nil, query)
+				So(err, ShouldBeNil)
+				queryResult := resp.Results["A"]
+				So(queryResult.Error, ShouldBeNil)
+
+				So(len(queryResult.Series), ShouldEqual, 1)
+				So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
+			})
+
+			Convey("When doing a metric query using epoch (float64) as time column should return metric with time in milliseconds", func() {
+				query := &tsdb.TsdbQuery{
+					Queries: []*tsdb.Query{
+						{
+							Model: simplejson.NewFromAny(map[string]interface{}{
+								"rawSql": `SELECT TOP 1 timeFloat64 as time, valueOne FROM metric_values ORDER BY time`,
+								"format": "time_series",
+							}),
+							RefId: "A",
+						},
+					},
+				}
+
+				resp, err := endpoint.Query(nil, nil, query)
+				So(err, ShouldBeNil)
+				queryResult := resp.Results["A"]
+				So(queryResult.Error, ShouldBeNil)
+
+				So(len(queryResult.Series), ShouldEqual, 1)
+				So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
+			})
+
+			Convey("When doing a metric query using epoch (float64 nullable) as time column should return metric with time in milliseconds", func() {
+				query := &tsdb.TsdbQuery{
+					Queries: []*tsdb.Query{
+						{
+							Model: simplejson.NewFromAny(map[string]interface{}{
+								"rawSql": `SELECT TOP 1 timeFloat64Nullable as time, valueOne FROM metric_values ORDER BY time`,
+								"format": "time_series",
+							}),
+							RefId: "A",
+						},
+					},
+				}
+
+				resp, err := endpoint.Query(nil, nil, query)
+				So(err, ShouldBeNil)
+				queryResult := resp.Results["A"]
+				So(queryResult.Error, ShouldBeNil)
+
+				So(len(queryResult.Series), ShouldEqual, 1)
+				So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
+			})
+
+			Convey("When doing a metric query using epoch (int32) as time column should return metric with time in milliseconds", func() {
+				query := &tsdb.TsdbQuery{
+					Queries: []*tsdb.Query{
+						{
+							Model: simplejson.NewFromAny(map[string]interface{}{
+								"rawSql": `SELECT TOP 1 timeInt32 as time, valueOne FROM metric_values ORDER BY time`,
+								"format": "time_series",
+							}),
+							RefId: "A",
+						},
+					},
+				}
+
+				resp, err := endpoint.Query(nil, nil, query)
+				So(err, ShouldBeNil)
+				queryResult := resp.Results["A"]
+				So(queryResult.Error, ShouldBeNil)
+
+				So(len(queryResult.Series), ShouldEqual, 1)
+				So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
+			})
+
+			Convey("When doing a metric query using epoch (int32 nullable) as time column should return metric with time in milliseconds", func() {
+				query := &tsdb.TsdbQuery{
+					Queries: []*tsdb.Query{
+						{
+							Model: simplejson.NewFromAny(map[string]interface{}{
+								"rawSql": `SELECT TOP 1 timeInt32Nullable as time, valueOne FROM metric_values ORDER BY time`,
+								"format": "time_series",
+							}),
+							RefId: "A",
+						},
+					},
+				}
+
+				resp, err := endpoint.Query(nil, nil, query)
+				So(err, ShouldBeNil)
+				queryResult := resp.Results["A"]
+				So(queryResult.Error, ShouldBeNil)
+
+				So(len(queryResult.Series), ShouldEqual, 1)
+				So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
+			})
+
+			Convey("When doing a metric query using epoch (float32) as time column should return metric with time in milliseconds", func() {
+				query := &tsdb.TsdbQuery{
+					Queries: []*tsdb.Query{
+						{
+							Model: simplejson.NewFromAny(map[string]interface{}{
+								"rawSql": `SELECT TOP 1 timeFloat32 as time, valueOne FROM metric_values ORDER BY time`,
+								"format": "time_series",
+							}),
+							RefId: "A",
+						},
+					},
+				}
+
+				resp, err := endpoint.Query(nil, nil, query)
+				So(err, ShouldBeNil)
+				queryResult := resp.Results["A"]
+				So(queryResult.Error, ShouldBeNil)
+
+				So(len(queryResult.Series), ShouldEqual, 1)
+				So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(float64(float32(tInitial.Unix())))*1e3)
+			})
+
+			Convey("When doing a metric query using epoch (float32 nullable) as time column should return metric with time in milliseconds", func() {
+				query := &tsdb.TsdbQuery{
+					Queries: []*tsdb.Query{
+						{
+							Model: simplejson.NewFromAny(map[string]interface{}{
+								"rawSql": `SELECT TOP 1 timeFloat32Nullable as time, valueOne FROM metric_values ORDER BY time`,
+								"format": "time_series",
+							}),
+							RefId: "A",
+						},
+					},
+				}
+
+				resp, err := endpoint.Query(nil, nil, query)
+				So(err, ShouldBeNil)
+				queryResult := resp.Results["A"]
+				So(queryResult.Error, ShouldBeNil)
+
+				So(len(queryResult.Series), ShouldEqual, 1)
+				So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(float64(float32(tInitial.Unix())))*1e3)
+			})
 
 			Convey("When doing a metric query grouping by time and select metric column should return correct series", func() {
 				query := &tsdb.TsdbQuery{
@@ -476,7 +675,6 @@ func TestMSSQL(t *testing.T) {
 					resp, err := endpoint.Query(nil, nil, query)
 					queryResult := resp.Results["A"]
 					So(err, ShouldBeNil)
-					fmt.Println("query", "sql", queryResult.Meta)
 					So(queryResult.Error, ShouldBeNil)
 
 					So(len(queryResult.Series), ShouldEqual, 4)
@@ -696,7 +894,7 @@ func TestMSSQL(t *testing.T) {
 				columns := queryResult.Tables[0].Rows[0]
 
 				//Should be in milliseconds
-				So(columns[0].(float64), ShouldEqual, float64(dt.Unix()*1000))
+				So(columns[0].(float64), ShouldEqual, float64(dt.UnixNano()/1e6))
 			})
 
 			Convey("When doing an annotation query with a time column in epoch second format should return ms", func() {
@@ -850,15 +1048,15 @@ func TestMSSQL(t *testing.T) {
 
 func InitMSSQLTestDB(t *testing.T) *xorm.Engine {
 	x, err := xorm.NewEngine(sqlutil.TestDB_Mssql.DriverName, strings.Replace(sqlutil.TestDB_Mssql.ConnStr, "localhost", serverIP, 1))
+	if err != nil {
+		t.Fatalf("Failed to init mssql db %v", err)
+	}
+
 	x.DatabaseTZ = time.UTC
 	x.TZLocation = time.UTC
 
 	// x.ShowSQL()
 
-	if err != nil {
-		t.Fatalf("Failed to init mssql db %v", err)
-	}
-
 	return x
 }