|
|
@@ -2,6 +2,7 @@ package mssql
|
|
|
|
|
|
import (
|
|
|
"fmt"
|
|
|
+ "math/rand"
|
|
|
"strings"
|
|
|
"testing"
|
|
|
"time"
|
|
|
@@ -15,8 +16,10 @@ import (
|
|
|
)
|
|
|
|
|
|
// To run this test, remove the Skip from SkipConvey
|
|
|
-// and set up a MSSQL db named grafana_tests and a user/password grafana/Password!
|
|
|
-// and set the variable below to the IP address of the database
|
|
|
+// and set up a MSSQL db named grafanatest and a user/password grafana/Password!
|
|
|
+// Use the docker/blocks/mssql_tests/docker-compose.yaml to spin up a
|
|
|
+// preconfigured MSSQL server suitable for running these tests.
|
|
|
+// If needed, change the variable below to the IP address of the database.
|
|
|
var serverIP string = "localhost"
|
|
|
|
|
|
func TestMSSQL(t *testing.T) {
|
|
|
@@ -90,7 +93,7 @@ func TestMSSQL(t *testing.T) {
|
|
|
1.11, 2.22, 3.33,
|
|
|
'char10', 'varchar10', 'text',
|
|
|
N'☺nchar12☺', N'☺nvarchar12☺', N'☺text☺',
|
|
|
- CAST('%s' AS DATETIME), CAST('%s' AS DATETIME2), CAST('%s' AS SMALLDATETIME), CAST('%s' AS DATE), CAST('%s' AS TIME), SWITCHOFFSET(CAST('%s' AS DATETIMEOFFSET), '-07:00')
|
|
|
+ CAST('%s' AS DATETIME), CAST('%s' AS DATETIME2), CAST('%s' AS SMALLDATETIME), CAST('%s' AS DATE), CAST('%s' AS TIME), SWITCHOFFSET(CAST('%s' AS DATETIMEOFFSET), '-07:00')
|
|
|
`, d, d2, d, d, d, d2)
|
|
|
|
|
|
_, err = sess.Exec(sql)
|
|
|
@@ -146,14 +149,13 @@ func TestMSSQL(t *testing.T) {
|
|
|
})
|
|
|
})
|
|
|
|
|
|
- Convey("Given a table with metrics", func() {
|
|
|
+ Convey("Given a table with metrics that lacks data for some series ", func() {
|
|
|
sql := `
|
|
|
IF OBJECT_ID('dbo.[metric]', 'U') IS NOT NULL
|
|
|
DROP TABLE dbo.[metric]
|
|
|
|
|
|
CREATE TABLE [metric] (
|
|
|
time datetime,
|
|
|
- measurement nvarchar(100),
|
|
|
value int
|
|
|
)
|
|
|
`
|
|
|
@@ -162,39 +164,34 @@ func TestMSSQL(t *testing.T) {
|
|
|
So(err, ShouldBeNil)
|
|
|
|
|
|
type metric struct {
|
|
|
- Time time.Time
|
|
|
- Measurement string
|
|
|
- Value int64
|
|
|
+ Time time.Time
|
|
|
+ Value int64
|
|
|
}
|
|
|
|
|
|
series := []*metric{}
|
|
|
-
|
|
|
- fromStart := time.Date(2018, 3, 15, 13, 0, 0, 0, time.UTC)
|
|
|
firstRange := genTimeRangeByInterval(fromStart, 10*time.Minute, 10*time.Second)
|
|
|
secondRange := genTimeRangeByInterval(fromStart.Add(20*time.Minute), 10*time.Minute, 10*time.Second)
|
|
|
|
|
|
for _, t := range firstRange {
|
|
|
series = append(series, &metric{
|
|
|
- Time: t,
|
|
|
- Measurement: "test",
|
|
|
- Value: 15,
|
|
|
+ Time: t,
|
|
|
+ Value: 15,
|
|
|
})
|
|
|
}
|
|
|
|
|
|
for _, t := range secondRange {
|
|
|
series = append(series, &metric{
|
|
|
- Time: t,
|
|
|
- Measurement: "test",
|
|
|
- Value: 20,
|
|
|
+ Time: t,
|
|
|
+ Value: 20,
|
|
|
})
|
|
|
}
|
|
|
|
|
|
dtFormat := "2006-01-02 15:04:05.999999999"
|
|
|
for _, s := range series {
|
|
|
sql = fmt.Sprintf(`
|
|
|
- INSERT INTO metric (time, measurement, value)
|
|
|
- VALUES(CAST('%s' AS DATETIME), '%s', %d)
|
|
|
- `, s.Time.Format(dtFormat), s.Measurement, s.Value)
|
|
|
+ INSERT INTO metric (time, value)
|
|
|
+ VALUES(CAST('%s' AS DATETIME), %d)
|
|
|
+ `, s.Time.Format(dtFormat), s.Value)
|
|
|
|
|
|
_, err = sess.Exec(sql)
|
|
|
So(err, ShouldBeNil)
|
|
|
@@ -205,7 +202,7 @@ func TestMSSQL(t *testing.T) {
|
|
|
Queries: []*tsdb.Query{
|
|
|
{
|
|
|
Model: simplejson.NewFromAny(map[string]interface{}{
|
|
|
- "rawSql": "SELECT $__timeGroup(time, '5m') AS time, measurement as metric, avg(value) as value FROM metric GROUP BY $__timeGroup(time, '5m'), measurement ORDER BY 1",
|
|
|
+ "rawSql": "SELECT $__timeGroup(time, '5m') AS time, avg(value) as value FROM metric GROUP BY $__timeGroup(time, '5m') ORDER BY 1",
|
|
|
"format": "time_series",
|
|
|
}),
|
|
|
RefId: "A",
|
|
|
@@ -237,7 +234,7 @@ func TestMSSQL(t *testing.T) {
|
|
|
Queries: []*tsdb.Query{
|
|
|
{
|
|
|
Model: simplejson.NewFromAny(map[string]interface{}{
|
|
|
- "rawSql": "SELECT $__timeGroup(time, '5m', NULL) AS time, measurement as metric, avg(value) as value FROM metric GROUP BY $__timeGroup(time, '5m'), measurement ORDER BY 1",
|
|
|
+ "rawSql": "SELECT $__timeGroup(time, '5m', NULL) AS time, avg(value) as value FROM metric GROUP BY $__timeGroup(time, '5m') ORDER BY 1",
|
|
|
"format": "time_series",
|
|
|
}),
|
|
|
RefId: "A",
|
|
|
@@ -284,7 +281,7 @@ func TestMSSQL(t *testing.T) {
|
|
|
Queries: []*tsdb.Query{
|
|
|
{
|
|
|
Model: simplejson.NewFromAny(map[string]interface{}{
|
|
|
- "rawSql": "SELECT $__timeGroup(time, '5m', 1.5) AS time, measurement as metric, avg(value) as value FROM metric GROUP BY $__timeGroup(time, '5m'), measurement ORDER BY 1",
|
|
|
+ "rawSql": "SELECT $__timeGroup(time, '5m', 1.5) AS time, avg(value) as value FROM metric GROUP BY $__timeGroup(time, '5m') ORDER BY 1",
|
|
|
"format": "time_series",
|
|
|
}),
|
|
|
RefId: "A",
|
|
|
@@ -306,6 +303,202 @@ func TestMSSQL(t *testing.T) {
|
|
|
So(points[6][0].Float64, ShouldEqual, 1.5)
|
|
|
})
|
|
|
})
|
|
|
+
|
|
|
+ Convey("Given a table with metrics having multiple values and measurements", func() {
|
|
|
+ sql := `
|
|
|
+ IF OBJECT_ID('dbo.[metric_values]', 'U') IS NOT NULL
|
|
|
+ DROP TABLE dbo.[metric_values]
|
|
|
+
|
|
|
+ CREATE TABLE [metric_values] (
|
|
|
+ time datetime,
|
|
|
+ measurement nvarchar(100),
|
|
|
+ valueOne int,
|
|
|
+ valueTwo int,
|
|
|
+ )
|
|
|
+ `
|
|
|
+
|
|
|
+ _, err := sess.Exec(sql)
|
|
|
+ So(err, ShouldBeNil)
|
|
|
+
|
|
|
+ type metricValues struct {
|
|
|
+ Time time.Time
|
|
|
+ Measurement string
|
|
|
+ ValueOne int64
|
|
|
+ ValueTwo int64
|
|
|
+ }
|
|
|
+
|
|
|
+ rand.Seed(time.Now().Unix())
|
|
|
+ rnd := func(min, max int64) int64 {
|
|
|
+ return rand.Int63n(max-min) + min
|
|
|
+ }
|
|
|
+
|
|
|
+ series := []*metricValues{}
|
|
|
+ for _, t := range genTimeRangeByInterval(fromStart.Add(-30*time.Minute), 90*time.Minute, 5*time.Minute) {
|
|
|
+ series = append(series, &metricValues{
|
|
|
+ Time: t,
|
|
|
+ Measurement: "Metric A",
|
|
|
+ ValueOne: rnd(0, 100),
|
|
|
+ ValueTwo: rnd(0, 100),
|
|
|
+ })
|
|
|
+ series = append(series, &metricValues{
|
|
|
+ Time: t,
|
|
|
+ Measurement: "Metric B",
|
|
|
+ ValueOne: rnd(0, 100),
|
|
|
+ ValueTwo: rnd(0, 100),
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ dtFormat := "2006-01-02 15:04:05"
|
|
|
+ for _, s := range series {
|
|
|
+ sql = fmt.Sprintf(`
|
|
|
+ INSERT metric_values (time, measurement, valueOne, valueTwo)
|
|
|
+ VALUES(CAST('%s' AS DATETIME), '%s', %d, %d)
|
|
|
+ `, s.Time.Format(dtFormat), s.Measurement, s.ValueOne, s.ValueTwo)
|
|
|
+
|
|
|
+ _, err = sess.Exec(sql)
|
|
|
+ So(err, ShouldBeNil)
|
|
|
+ }
|
|
|
+
|
|
|
+ Convey("When doing a metric query grouping by time and select metric column should return correct series", func() {
|
|
|
+ query := &tsdb.TsdbQuery{
|
|
|
+ Queries: []*tsdb.Query{
|
|
|
+ {
|
|
|
+ Model: simplejson.NewFromAny(map[string]interface{}{
|
|
|
+ "rawSql": "SELECT $__timeEpoch(time), measurement as metric, valueOne, valueTwo FROM metric_values ORDER BY 1",
|
|
|
+ "format": "time_series",
|
|
|
+ }),
|
|
|
+ RefId: "A",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ }
|
|
|
+
|
|
|
+ resp, err := endpoint.Query(nil, nil, query)
|
|
|
+ queryResult := resp.Results["A"]
|
|
|
+ So(err, ShouldBeNil)
|
|
|
+ So(queryResult.Error, ShouldBeNil)
|
|
|
+
|
|
|
+ So(len(queryResult.Series), ShouldEqual, 4)
|
|
|
+ So(queryResult.Series[0].Name, ShouldEqual, "Metric A - valueOne")
|
|
|
+ So(queryResult.Series[1].Name, ShouldEqual, "Metric A - valueTwo")
|
|
|
+ So(queryResult.Series[2].Name, ShouldEqual, "Metric B - valueOne")
|
|
|
+ So(queryResult.Series[3].Name, ShouldEqual, "Metric B - valueTwo")
|
|
|
+ })
|
|
|
+
|
|
|
+ Convey("When doing a metric query grouping by time should return correct series", func() {
|
|
|
+ query := &tsdb.TsdbQuery{
|
|
|
+ Queries: []*tsdb.Query{
|
|
|
+ {
|
|
|
+ Model: simplejson.NewFromAny(map[string]interface{}{
|
|
|
+ "rawSql": "SELECT $__timeEpoch(time), valueOne, valueTwo FROM metric_values ORDER BY 1",
|
|
|
+ "format": "time_series",
|
|
|
+ }),
|
|
|
+ RefId: "A",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ }
|
|
|
+
|
|
|
+ resp, err := endpoint.Query(nil, nil, query)
|
|
|
+ queryResult := resp.Results["A"]
|
|
|
+ So(err, ShouldBeNil)
|
|
|
+ So(queryResult.Error, ShouldBeNil)
|
|
|
+
|
|
|
+ So(len(queryResult.Series), ShouldEqual, 2)
|
|
|
+ So(queryResult.Series[0].Name, ShouldEqual, "valueOne")
|
|
|
+ So(queryResult.Series[1].Name, ShouldEqual, "valueTwo")
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ Convey("Given a table with event data", func() {
|
|
|
+ sql := `
|
|
|
+ IF OBJECT_ID('dbo.[event]', 'U') IS NOT NULL
|
|
|
+ DROP TABLE dbo.[event]
|
|
|
+
|
|
|
+ CREATE TABLE [event] (
|
|
|
+ time_sec bigint,
|
|
|
+ description nvarchar(100),
|
|
|
+ tags nvarchar(100),
|
|
|
+ )
|
|
|
+ `
|
|
|
+
|
|
|
+ _, err := sess.Exec(sql)
|
|
|
+ So(err, ShouldBeNil)
|
|
|
+
|
|
|
+ type event struct {
|
|
|
+ TimeSec int64
|
|
|
+ Description string
|
|
|
+ Tags string
|
|
|
+ }
|
|
|
+
|
|
|
+ events := []*event{}
|
|
|
+ for _, t := range genTimeRangeByInterval(fromStart.Add(-20*time.Minute), 60*time.Minute, 25*time.Minute) {
|
|
|
+ events = append(events, &event{
|
|
|
+ TimeSec: t.Unix(),
|
|
|
+ Description: "Someone deployed something",
|
|
|
+ Tags: "deploy",
|
|
|
+ })
|
|
|
+ events = append(events, &event{
|
|
|
+ TimeSec: t.Add(5 * time.Minute).Unix(),
|
|
|
+ Description: "New support ticket registered",
|
|
|
+ Tags: "ticket",
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, e := range events {
|
|
|
+ sql = fmt.Sprintf(`
|
|
|
+ INSERT [event] (time_sec, description, tags)
|
|
|
+ VALUES(%d, '%s', '%s')
|
|
|
+ `, e.TimeSec, e.Description, e.Tags)
|
|
|
+
|
|
|
+ _, err = sess.Exec(sql)
|
|
|
+ So(err, ShouldBeNil)
|
|
|
+ }
|
|
|
+
|
|
|
+ Convey("When doing an annotation query of deploy events should return expected result", func() {
|
|
|
+ query := &tsdb.TsdbQuery{
|
|
|
+ Queries: []*tsdb.Query{
|
|
|
+ {
|
|
|
+ Model: simplejson.NewFromAny(map[string]interface{}{
|
|
|
+ "rawSql": "SELECT time_sec as time, description as [text], tags FROM [event] WHERE $__unixEpochFilter(time_sec) AND tags='deploy' ORDER BY 1 ASC",
|
|
|
+ "format": "table",
|
|
|
+ }),
|
|
|
+ RefId: "Deploys",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ TimeRange: &tsdb.TimeRange{
|
|
|
+ From: fmt.Sprintf("%v", fromStart.Add(-20*time.Minute).Unix()*1000),
|
|
|
+ To: fmt.Sprintf("%v", fromStart.Add(40*time.Minute).Unix()*1000),
|
|
|
+ },
|
|
|
+ }
|
|
|
+
|
|
|
+ resp, err := endpoint.Query(nil, nil, query)
|
|
|
+ queryResult := resp.Results["Deploys"]
|
|
|
+ So(err, ShouldBeNil)
|
|
|
+ So(len(queryResult.Tables[0].Rows), ShouldEqual, 3)
|
|
|
+ })
|
|
|
+
|
|
|
+ Convey("When doing an annotation query of ticket events should return expected result", func() {
|
|
|
+ query := &tsdb.TsdbQuery{
|
|
|
+ Queries: []*tsdb.Query{
|
|
|
+ {
|
|
|
+ Model: simplejson.NewFromAny(map[string]interface{}{
|
|
|
+ "rawSql": "SELECT time_sec as time, description as [text], tags FROM [event] WHERE $__unixEpochFilter(time_sec) AND tags='ticket' ORDER BY 1 ASC",
|
|
|
+ "format": "table",
|
|
|
+ }),
|
|
|
+ RefId: "Tickets",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ TimeRange: &tsdb.TimeRange{
|
|
|
+ From: fmt.Sprintf("%v", fromStart.Add(-20*time.Minute).Unix()*1000),
|
|
|
+ To: fmt.Sprintf("%v", fromStart.Add(40*time.Minute).Unix()*1000),
|
|
|
+ },
|
|
|
+ }
|
|
|
+
|
|
|
+ resp, err := endpoint.Query(nil, nil, query)
|
|
|
+ queryResult := resp.Results["Tickets"]
|
|
|
+ So(err, ShouldBeNil)
|
|
|
+ So(len(queryResult.Tables[0].Rows), ShouldEqual, 3)
|
|
|
+ })
|
|
|
+ })
|
|
|
})
|
|
|
}
|
|
|
|