mssql_test.go 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158
  1. package mssql
  2. import (
  3. "context"
  4. "fmt"
  5. "math/rand"
  6. "strings"
  7. "testing"
  8. "time"
  9. "github.com/go-xorm/xorm"
  10. "github.com/grafana/grafana/pkg/components/securejsondata"
  11. "github.com/grafana/grafana/pkg/components/simplejson"
  12. "github.com/grafana/grafana/pkg/models"
  13. "github.com/grafana/grafana/pkg/services/sqlstore/sqlutil"
  14. "github.com/grafana/grafana/pkg/tsdb"
  15. . "github.com/smartystreets/goconvey/convey"
  16. )
  17. // To run this test, remove the Skip from SkipConvey
  18. // The tests require a MSSQL db named grafanatest and a user/password grafana/Password!
  19. // Use the docker/blocks/mssql_tests/docker-compose.yaml to spin up a
  20. // preconfigured MSSQL server suitable for running these tests.
  21. // There is also a datasource and dashboard provisioned by devenv scripts that you can
  22. // use to verify that the generated data are vizualized as expected, see
  23. // devenv/README.md for setup instructions.
  24. // If needed, change the variable below to the IP address of the database.
  25. var serverIP = "localhost"
  26. func TestMSSQL(t *testing.T) {
  27. SkipConvey("MSSQL", t, func() {
  28. x := InitMSSQLTestDB(t)
  29. origXormEngine := tsdb.NewXormEngine
  30. tsdb.NewXormEngine = func(d, c string) (*xorm.Engine, error) {
  31. return x, nil
  32. }
  33. origInterpolate := tsdb.Interpolate
  34. tsdb.Interpolate = func(query *tsdb.Query, timeRange *tsdb.TimeRange, sql string) (string, error) {
  35. return sql, nil
  36. }
  37. endpoint, err := newMssqlQueryEndpoint(&models.DataSource{
  38. JsonData: simplejson.New(),
  39. SecureJsonData: securejsondata.SecureJsonData{},
  40. })
  41. So(err, ShouldBeNil)
  42. sess := x.NewSession()
  43. fromStart := time.Date(2018, 3, 15, 13, 0, 0, 0, time.UTC).In(time.Local)
  44. Reset(func() {
  45. sess.Close()
  46. tsdb.NewXormEngine = origXormEngine
  47. tsdb.Interpolate = origInterpolate
  48. })
  49. Convey("Given a table with different native data types", func() {
  50. sql := `
  51. IF OBJECT_ID('dbo.[mssql_types]', 'U') IS NOT NULL
  52. DROP TABLE dbo.[mssql_types]
  53. CREATE TABLE [mssql_types] (
  54. c_bit bit,
  55. c_tinyint tinyint,
  56. c_smallint smallint,
  57. c_int int,
  58. c_bigint bigint,
  59. c_money money,
  60. c_smallmoney smallmoney,
  61. c_numeric numeric(10,5),
  62. c_real real,
  63. c_decimal decimal(10,2),
  64. c_float float,
  65. c_char char(10),
  66. c_varchar varchar(10),
  67. c_text text,
  68. c_nchar nchar(12),
  69. c_nvarchar nvarchar(12),
  70. c_ntext ntext,
  71. c_datetime datetime,
  72. c_datetime2 datetime2,
  73. c_smalldatetime smalldatetime,
  74. c_date date,
  75. c_time time,
  76. c_datetimeoffset datetimeoffset
  77. )
  78. `
  79. _, err := sess.Exec(sql)
  80. So(err, ShouldBeNil)
  81. dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
  82. dtFormat := "2006-01-02 15:04:05.999999999"
  83. d := dt.Format(dtFormat)
  84. dt2 := time.Date(2018, 3, 14, 21, 20, 6, 8896406e2, time.UTC)
  85. dt2Format := "2006-01-02 15:04:05.999999999 -07:00"
  86. d2 := dt2.Format(dt2Format)
  87. sql = fmt.Sprintf(`
  88. INSERT INTO [mssql_types]
  89. SELECT
  90. 1, 5, 20020, 980300, 1420070400, '$20000.15', '£2.15', 12345.12,
  91. 1.11, 2.22, 3.33,
  92. 'char10', 'varchar10', 'text',
  93. N'☺nchar12☺', N'☺nvarchar12☺', N'☺text☺',
  94. 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')
  95. `, d, d2, d, d, d, d2)
  96. _, err = sess.Exec(sql)
  97. So(err, ShouldBeNil)
  98. Convey("When doing a table query should map MSSQL column types to Go types", func() {
  99. query := &tsdb.TsdbQuery{
  100. Queries: []*tsdb.Query{
  101. {
  102. Model: simplejson.NewFromAny(map[string]interface{}{
  103. "rawSql": "SELECT * FROM mssql_types",
  104. "format": "table",
  105. }),
  106. RefId: "A",
  107. },
  108. },
  109. }
  110. resp, err := endpoint.Query(context.Background(), nil, query)
  111. queryResult := resp.Results["A"]
  112. So(err, ShouldBeNil)
  113. column := queryResult.Tables[0].Rows[0]
  114. So(column[0].(bool), ShouldEqual, true)
  115. So(column[1].(int64), ShouldEqual, 5)
  116. So(column[2].(int64), ShouldEqual, 20020)
  117. So(column[3].(int64), ShouldEqual, 980300)
  118. So(column[4].(int64), ShouldEqual, 1420070400)
  119. So(column[5].(float64), ShouldEqual, 20000.15)
  120. So(column[6].(float64), ShouldEqual, 2.15)
  121. So(column[7].(float64), ShouldEqual, 12345.12)
  122. So(column[8].(float64), ShouldEqual, 1.1100000143051147)
  123. So(column[9].(float64), ShouldEqual, 2.22)
  124. So(column[10].(float64), ShouldEqual, 3.33)
  125. So(column[11].(string), ShouldEqual, "char10 ")
  126. So(column[12].(string), ShouldEqual, "varchar10")
  127. So(column[13].(string), ShouldEqual, "text")
  128. So(column[14].(string), ShouldEqual, "☺nchar12☺ ")
  129. So(column[15].(string), ShouldEqual, "☺nvarchar12☺")
  130. So(column[16].(string), ShouldEqual, "☺text☺")
  131. So(column[17].(time.Time), ShouldEqual, dt)
  132. So(column[18].(time.Time), ShouldEqual, dt2)
  133. So(column[19].(time.Time), ShouldEqual, dt.Truncate(time.Minute))
  134. So(column[20].(time.Time), ShouldEqual, dt.Truncate(24*time.Hour))
  135. So(column[21].(time.Time), ShouldEqual, time.Date(1, 1, 1, dt.Hour(), dt.Minute(), dt.Second(), dt.Nanosecond(), time.UTC))
  136. So(column[22].(time.Time), ShouldEqual, dt2.In(time.FixedZone("UTC", int(-7*time.Hour))))
  137. })
  138. })
  139. Convey("Given a table with metrics that lacks data for some series ", func() {
  140. sql := `
  141. IF OBJECT_ID('dbo.[metric]', 'U') IS NOT NULL
  142. DROP TABLE dbo.[metric]
  143. CREATE TABLE [metric] (
  144. time datetime,
  145. value int
  146. )
  147. `
  148. _, err := sess.Exec(sql)
  149. So(err, ShouldBeNil)
  150. type metric struct {
  151. Time time.Time
  152. Value int64
  153. }
  154. series := []*metric{}
  155. firstRange := genTimeRangeByInterval(fromStart, 10*time.Minute, 10*time.Second)
  156. secondRange := genTimeRangeByInterval(fromStart.Add(20*time.Minute), 10*time.Minute, 10*time.Second)
  157. for _, t := range firstRange {
  158. series = append(series, &metric{
  159. Time: t,
  160. Value: 15,
  161. })
  162. }
  163. for _, t := range secondRange {
  164. series = append(series, &metric{
  165. Time: t,
  166. Value: 20,
  167. })
  168. }
  169. _, err = sess.InsertMulti(series)
  170. So(err, ShouldBeNil)
  171. Convey("When doing a metric query using timeGroup", func() {
  172. query := &tsdb.TsdbQuery{
  173. Queries: []*tsdb.Query{
  174. {
  175. Model: simplejson.NewFromAny(map[string]interface{}{
  176. "rawSql": "SELECT $__timeGroup(time, '5m') AS time, avg(value) as value FROM metric GROUP BY $__timeGroup(time, '5m') ORDER BY 1",
  177. "format": "time_series",
  178. }),
  179. RefId: "A",
  180. },
  181. },
  182. }
  183. resp, err := endpoint.Query(context.Background(), nil, query)
  184. So(err, ShouldBeNil)
  185. queryResult := resp.Results["A"]
  186. So(queryResult.Error, ShouldBeNil)
  187. points := queryResult.Series[0].Points
  188. // without fill this should result in 4 buckets
  189. So(len(points), ShouldEqual, 4)
  190. dt := fromStart
  191. for i := 0; i < 2; i++ {
  192. aValue := points[i][0].Float64
  193. aTime := time.Unix(int64(points[i][1].Float64)/1000, 0)
  194. So(aValue, ShouldEqual, 15)
  195. So(aTime, ShouldEqual, dt)
  196. dt = dt.Add(5 * time.Minute)
  197. }
  198. // adjust for 10 minute gap between first and second set of points
  199. dt = dt.Add(10 * time.Minute)
  200. for i := 2; i < 4; i++ {
  201. aValue := points[i][0].Float64
  202. aTime := time.Unix(int64(points[i][1].Float64)/1000, 0)
  203. So(aValue, ShouldEqual, 20)
  204. So(aTime, ShouldEqual, dt)
  205. dt = dt.Add(5 * time.Minute)
  206. }
  207. })
  208. Convey("When doing a metric query using timeGroup with NULL fill enabled", func() {
  209. query := &tsdb.TsdbQuery{
  210. Queries: []*tsdb.Query{
  211. {
  212. Model: simplejson.NewFromAny(map[string]interface{}{
  213. "rawSql": "SELECT $__timeGroup(time, '5m', NULL) AS time, avg(value) as value FROM metric GROUP BY $__timeGroup(time, '5m') ORDER BY 1",
  214. "format": "time_series",
  215. }),
  216. RefId: "A",
  217. },
  218. },
  219. TimeRange: &tsdb.TimeRange{
  220. From: fmt.Sprintf("%v", fromStart.Unix()*1000),
  221. To: fmt.Sprintf("%v", fromStart.Add(34*time.Minute).Unix()*1000),
  222. },
  223. }
  224. resp, err := endpoint.Query(context.Background(), nil, query)
  225. So(err, ShouldBeNil)
  226. queryResult := resp.Results["A"]
  227. So(queryResult.Error, ShouldBeNil)
  228. points := queryResult.Series[0].Points
  229. So(len(points), ShouldEqual, 7)
  230. dt := fromStart
  231. for i := 0; i < 2; i++ {
  232. aValue := points[i][0].Float64
  233. aTime := time.Unix(int64(points[i][1].Float64)/1000, 0)
  234. So(aValue, ShouldEqual, 15)
  235. So(aTime, ShouldEqual, dt)
  236. dt = dt.Add(5 * time.Minute)
  237. }
  238. // check for NULL values inserted by fill
  239. So(points[2][0].Valid, ShouldBeFalse)
  240. So(points[3][0].Valid, ShouldBeFalse)
  241. // adjust for 10 minute gap between first and second set of points
  242. dt = dt.Add(10 * time.Minute)
  243. for i := 4; i < 6; i++ {
  244. aValue := points[i][0].Float64
  245. aTime := time.Unix(int64(points[i][1].Float64)/1000, 0)
  246. So(aValue, ShouldEqual, 20)
  247. So(aTime, ShouldEqual, dt)
  248. dt = dt.Add(5 * time.Minute)
  249. }
  250. So(points[6][0].Valid, ShouldBeFalse)
  251. })
  252. Convey("When doing a metric query using timeGroup and $__interval", func() {
  253. mockInterpolate := tsdb.Interpolate
  254. tsdb.Interpolate = origInterpolate
  255. Reset(func() {
  256. tsdb.Interpolate = mockInterpolate
  257. })
  258. Convey("Should replace $__interval", func() {
  259. query := &tsdb.TsdbQuery{
  260. Queries: []*tsdb.Query{
  261. {
  262. DataSource: &models.DataSource{},
  263. Model: simplejson.NewFromAny(map[string]interface{}{
  264. "rawSql": "SELECT $__timeGroup(time, $__interval) AS time, avg(value) as value FROM metric GROUP BY $__timeGroup(time, $__interval) ORDER BY 1",
  265. "format": "time_series",
  266. }),
  267. RefId: "A",
  268. },
  269. },
  270. TimeRange: &tsdb.TimeRange{
  271. From: fmt.Sprintf("%v", fromStart.Unix()*1000),
  272. To: fmt.Sprintf("%v", fromStart.Add(30*time.Minute).Unix()*1000),
  273. },
  274. }
  275. resp, err := endpoint.Query(context.Background(), nil, query)
  276. So(err, ShouldBeNil)
  277. queryResult := resp.Results["A"]
  278. So(queryResult.Error, ShouldBeNil)
  279. So(queryResult.Meta.Get("sql").MustString(), ShouldEqual, "SELECT FLOOR(DATEDIFF(second, '1970-01-01', time)/60)*60 AS time, avg(value) as value FROM metric GROUP BY FLOOR(DATEDIFF(second, '1970-01-01', time)/60)*60 ORDER BY 1")
  280. })
  281. })
  282. Convey("When doing a metric query using timeGroup with float fill enabled", func() {
  283. query := &tsdb.TsdbQuery{
  284. Queries: []*tsdb.Query{
  285. {
  286. Model: simplejson.NewFromAny(map[string]interface{}{
  287. "rawSql": "SELECT $__timeGroup(time, '5m', 1.5) AS time, avg(value) as value FROM metric GROUP BY $__timeGroup(time, '5m') ORDER BY 1",
  288. "format": "time_series",
  289. }),
  290. RefId: "A",
  291. },
  292. },
  293. TimeRange: &tsdb.TimeRange{
  294. From: fmt.Sprintf("%v", fromStart.Unix()*1000),
  295. To: fmt.Sprintf("%v", fromStart.Add(34*time.Minute).Unix()*1000),
  296. },
  297. }
  298. resp, err := endpoint.Query(context.Background(), nil, query)
  299. So(err, ShouldBeNil)
  300. queryResult := resp.Results["A"]
  301. So(queryResult.Error, ShouldBeNil)
  302. points := queryResult.Series[0].Points
  303. So(points[3][0].Float64, ShouldEqual, 1.5)
  304. })
  305. })
  306. Convey("Given a table with metrics having multiple values and measurements", func() {
  307. type metric_values struct {
  308. Time time.Time
  309. TimeInt64 int64 `xorm:"bigint 'timeInt64' not null"`
  310. TimeInt64Nullable *int64 `xorm:"bigint 'timeInt64Nullable' null"`
  311. TimeFloat64 float64 `xorm:"float 'timeFloat64' not null"`
  312. TimeFloat64Nullable *float64 `xorm:"float 'timeFloat64Nullable' null"`
  313. TimeInt32 int32 `xorm:"int(11) 'timeInt32' not null"`
  314. TimeInt32Nullable *int32 `xorm:"int(11) 'timeInt32Nullable' null"`
  315. TimeFloat32 float32 `xorm:"float(11) 'timeFloat32' not null"`
  316. TimeFloat32Nullable *float32 `xorm:"float(11) 'timeFloat32Nullable' null"`
  317. Measurement string
  318. ValueOne int64 `xorm:"integer 'valueOne'"`
  319. ValueTwo int64 `xorm:"integer 'valueTwo'"`
  320. }
  321. if exist, err := sess.IsTableExist(metric_values{}); err != nil || exist {
  322. So(err, ShouldBeNil)
  323. sess.DropTable(metric_values{})
  324. }
  325. err := sess.CreateTable(metric_values{})
  326. So(err, ShouldBeNil)
  327. rand.Seed(time.Now().Unix())
  328. rnd := func(min, max int64) int64 {
  329. return rand.Int63n(max-min) + min
  330. }
  331. var tInitial time.Time
  332. series := []*metric_values{}
  333. for i, t := range genTimeRangeByInterval(fromStart.Add(-30*time.Minute), 90*time.Minute, 5*time.Minute) {
  334. if i == 0 {
  335. tInitial = t
  336. }
  337. tSeconds := t.Unix()
  338. tSecondsInt32 := int32(tSeconds)
  339. tSecondsFloat32 := float32(tSeconds)
  340. tMilliseconds := tSeconds * 1e3
  341. tMillisecondsFloat := float64(tMilliseconds)
  342. first := metric_values{
  343. Time: t,
  344. TimeInt64: tMilliseconds,
  345. TimeInt64Nullable: &(tMilliseconds),
  346. TimeFloat64: tMillisecondsFloat,
  347. TimeFloat64Nullable: &tMillisecondsFloat,
  348. TimeInt32: tSecondsInt32,
  349. TimeInt32Nullable: &tSecondsInt32,
  350. TimeFloat32: tSecondsFloat32,
  351. TimeFloat32Nullable: &tSecondsFloat32,
  352. Measurement: "Metric A",
  353. ValueOne: rnd(0, 100),
  354. ValueTwo: rnd(0, 100),
  355. }
  356. second := first
  357. second.Measurement = "Metric B"
  358. second.ValueOne = rnd(0, 100)
  359. second.ValueTwo = rnd(0, 100)
  360. series = append(series, &first)
  361. series = append(series, &second)
  362. }
  363. _, err = sess.InsertMulti(series)
  364. So(err, ShouldBeNil)
  365. Convey("When doing a metric query using epoch (int64) as time column and value column (int64) should return metric with time in milliseconds", func() {
  366. query := &tsdb.TsdbQuery{
  367. Queries: []*tsdb.Query{
  368. {
  369. Model: simplejson.NewFromAny(map[string]interface{}{
  370. "rawSql": `SELECT TOP 1 timeInt64 as time, timeInt64 FROM metric_values ORDER BY time`,
  371. "format": "time_series",
  372. }),
  373. RefId: "A",
  374. },
  375. },
  376. }
  377. resp, err := endpoint.Query(context.Background(), nil, query)
  378. So(err, ShouldBeNil)
  379. queryResult := resp.Results["A"]
  380. So(queryResult.Error, ShouldBeNil)
  381. So(len(queryResult.Series), ShouldEqual, 1)
  382. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
  383. })
  384. Convey("When doing a metric query using epoch (int64 nullable) as time column and value column (int64 nullable) should return metric with time in milliseconds", func() {
  385. query := &tsdb.TsdbQuery{
  386. Queries: []*tsdb.Query{
  387. {
  388. Model: simplejson.NewFromAny(map[string]interface{}{
  389. "rawSql": `SELECT TOP 1 timeInt64Nullable as time, timeInt64Nullable FROM metric_values ORDER BY time`,
  390. "format": "time_series",
  391. }),
  392. RefId: "A",
  393. },
  394. },
  395. }
  396. resp, err := endpoint.Query(context.Background(), nil, query)
  397. So(err, ShouldBeNil)
  398. queryResult := resp.Results["A"]
  399. So(queryResult.Error, ShouldBeNil)
  400. So(len(queryResult.Series), ShouldEqual, 1)
  401. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
  402. })
  403. Convey("When doing a metric query using epoch (float64) as time column and value column (float64) should return metric with time in milliseconds", func() {
  404. query := &tsdb.TsdbQuery{
  405. Queries: []*tsdb.Query{
  406. {
  407. Model: simplejson.NewFromAny(map[string]interface{}{
  408. "rawSql": `SELECT TOP 1 timeFloat64 as time, timeFloat64 FROM metric_values ORDER BY time`,
  409. "format": "time_series",
  410. }),
  411. RefId: "A",
  412. },
  413. },
  414. }
  415. resp, err := endpoint.Query(context.Background(), nil, query)
  416. So(err, ShouldBeNil)
  417. queryResult := resp.Results["A"]
  418. So(queryResult.Error, ShouldBeNil)
  419. So(len(queryResult.Series), ShouldEqual, 1)
  420. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
  421. })
  422. Convey("When doing a metric query using epoch (float64 nullable) as time column and value column (float64 nullable) should return metric with time in milliseconds", func() {
  423. query := &tsdb.TsdbQuery{
  424. Queries: []*tsdb.Query{
  425. {
  426. Model: simplejson.NewFromAny(map[string]interface{}{
  427. "rawSql": `SELECT TOP 1 timeFloat64Nullable as time, timeFloat64Nullable FROM metric_values ORDER BY time`,
  428. "format": "time_series",
  429. }),
  430. RefId: "A",
  431. },
  432. },
  433. }
  434. resp, err := endpoint.Query(context.Background(), nil, query)
  435. So(err, ShouldBeNil)
  436. queryResult := resp.Results["A"]
  437. So(queryResult.Error, ShouldBeNil)
  438. So(len(queryResult.Series), ShouldEqual, 1)
  439. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
  440. })
  441. Convey("When doing a metric query using epoch (int32) as time column and value column (int32) should return metric with time in milliseconds", func() {
  442. query := &tsdb.TsdbQuery{
  443. Queries: []*tsdb.Query{
  444. {
  445. Model: simplejson.NewFromAny(map[string]interface{}{
  446. "rawSql": `SELECT TOP 1 timeInt32 as time, timeInt32 FROM metric_values ORDER BY time`,
  447. "format": "time_series",
  448. }),
  449. RefId: "A",
  450. },
  451. },
  452. }
  453. resp, err := endpoint.Query(context.Background(), nil, query)
  454. So(err, ShouldBeNil)
  455. queryResult := resp.Results["A"]
  456. So(queryResult.Error, ShouldBeNil)
  457. So(len(queryResult.Series), ShouldEqual, 1)
  458. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
  459. })
  460. Convey("When doing a metric query using epoch (int32 nullable) as time column and value column (int32 nullable) should return metric with time in milliseconds", func() {
  461. query := &tsdb.TsdbQuery{
  462. Queries: []*tsdb.Query{
  463. {
  464. Model: simplejson.NewFromAny(map[string]interface{}{
  465. "rawSql": `SELECT TOP 1 timeInt32Nullable as time, timeInt32Nullable FROM metric_values ORDER BY time`,
  466. "format": "time_series",
  467. }),
  468. RefId: "A",
  469. },
  470. },
  471. }
  472. resp, err := endpoint.Query(context.Background(), nil, query)
  473. So(err, ShouldBeNil)
  474. queryResult := resp.Results["A"]
  475. So(queryResult.Error, ShouldBeNil)
  476. So(len(queryResult.Series), ShouldEqual, 1)
  477. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
  478. })
  479. Convey("When doing a metric query using epoch (float32) as time column and value column (float32) should return metric with time in milliseconds", func() {
  480. query := &tsdb.TsdbQuery{
  481. Queries: []*tsdb.Query{
  482. {
  483. Model: simplejson.NewFromAny(map[string]interface{}{
  484. "rawSql": `SELECT TOP 1 timeFloat32 as time, timeFloat32 FROM metric_values ORDER BY time`,
  485. "format": "time_series",
  486. }),
  487. RefId: "A",
  488. },
  489. },
  490. }
  491. resp, err := endpoint.Query(context.Background(), nil, query)
  492. So(err, ShouldBeNil)
  493. queryResult := resp.Results["A"]
  494. So(queryResult.Error, ShouldBeNil)
  495. So(len(queryResult.Series), ShouldEqual, 1)
  496. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(float32(tInitial.Unix()))*1e3)
  497. })
  498. Convey("When doing a metric query using epoch (float32 nullable) as time column and value column (float32 nullable) should return metric with time in milliseconds", func() {
  499. query := &tsdb.TsdbQuery{
  500. Queries: []*tsdb.Query{
  501. {
  502. Model: simplejson.NewFromAny(map[string]interface{}{
  503. "rawSql": `SELECT TOP 1 timeFloat32Nullable as time, timeFloat32Nullable FROM metric_values ORDER BY time`,
  504. "format": "time_series",
  505. }),
  506. RefId: "A",
  507. },
  508. },
  509. }
  510. resp, err := endpoint.Query(context.Background(), nil, query)
  511. So(err, ShouldBeNil)
  512. queryResult := resp.Results["A"]
  513. So(queryResult.Error, ShouldBeNil)
  514. So(len(queryResult.Series), ShouldEqual, 1)
  515. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(float32(tInitial.Unix()))*1e3)
  516. })
  517. Convey("When doing a metric query grouping by time and select metric column should return correct series", func() {
  518. query := &tsdb.TsdbQuery{
  519. Queries: []*tsdb.Query{
  520. {
  521. Model: simplejson.NewFromAny(map[string]interface{}{
  522. "rawSql": "SELECT $__timeEpoch(time), measurement + ' - value one' as metric, valueOne FROM metric_values ORDER BY 1",
  523. "format": "time_series",
  524. }),
  525. RefId: "A",
  526. },
  527. },
  528. }
  529. resp, err := endpoint.Query(context.Background(), nil, query)
  530. So(err, ShouldBeNil)
  531. queryResult := resp.Results["A"]
  532. So(queryResult.Error, ShouldBeNil)
  533. So(len(queryResult.Series), ShouldEqual, 2)
  534. So(queryResult.Series[0].Name, ShouldEqual, "Metric A - value one")
  535. So(queryResult.Series[1].Name, ShouldEqual, "Metric B - value one")
  536. })
  537. Convey("When doing a metric query grouping by time should return correct series", func() {
  538. query := &tsdb.TsdbQuery{
  539. Queries: []*tsdb.Query{
  540. {
  541. Model: simplejson.NewFromAny(map[string]interface{}{
  542. "rawSql": "SELECT $__timeEpoch(time), valueOne, valueTwo FROM metric_values ORDER BY 1",
  543. "format": "time_series",
  544. }),
  545. RefId: "A",
  546. },
  547. },
  548. }
  549. resp, err := endpoint.Query(context.Background(), nil, query)
  550. So(err, ShouldBeNil)
  551. queryResult := resp.Results["A"]
  552. So(queryResult.Error, ShouldBeNil)
  553. So(len(queryResult.Series), ShouldEqual, 2)
  554. So(queryResult.Series[0].Name, ShouldEqual, "valueOne")
  555. So(queryResult.Series[1].Name, ShouldEqual, "valueTwo")
  556. })
  557. Convey("When doing a metric query with metric column and multiple value columns", func() {
  558. query := &tsdb.TsdbQuery{
  559. Queries: []*tsdb.Query{
  560. {
  561. Model: simplejson.NewFromAny(map[string]interface{}{
  562. "rawSql": "SELECT $__timeEpoch(time), measurement, valueOne, valueTwo FROM metric_values ORDER BY 1",
  563. "format": "time_series",
  564. }),
  565. RefId: "A",
  566. },
  567. },
  568. }
  569. resp, err := endpoint.Query(context.Background(), nil, query)
  570. So(err, ShouldBeNil)
  571. queryResult := resp.Results["A"]
  572. So(queryResult.Error, ShouldBeNil)
  573. So(len(queryResult.Series), ShouldEqual, 4)
  574. So(queryResult.Series[0].Name, ShouldEqual, "Metric A valueOne")
  575. So(queryResult.Series[1].Name, ShouldEqual, "Metric A valueTwo")
  576. So(queryResult.Series[2].Name, ShouldEqual, "Metric B valueOne")
  577. So(queryResult.Series[3].Name, ShouldEqual, "Metric B valueTwo")
  578. })
  579. Convey("When doing a query with timeFrom,timeTo,unixEpochFrom,unixEpochTo macros", func() {
  580. tsdb.Interpolate = origInterpolate
  581. query := &tsdb.TsdbQuery{
  582. TimeRange: tsdb.NewFakeTimeRange("5m", "now", fromStart),
  583. Queries: []*tsdb.Query{
  584. {
  585. DataSource: &models.DataSource{JsonData: simplejson.New()},
  586. Model: simplejson.NewFromAny(map[string]interface{}{
  587. "rawSql": `SELECT time FROM metric_values WHERE time > $__timeFrom() OR time < $__timeFrom() OR 1 < $__unixEpochFrom() OR $__unixEpochTo() > 1 ORDER BY 1`,
  588. "format": "time_series",
  589. }),
  590. RefId: "A",
  591. },
  592. },
  593. }
  594. resp, err := endpoint.Query(nil, nil, query)
  595. So(err, ShouldBeNil)
  596. queryResult := resp.Results["A"]
  597. So(queryResult.Error, ShouldBeNil)
  598. So(queryResult.Meta.Get("sql").MustString(), ShouldEqual, "SELECT time FROM metric_values WHERE time > '2018-03-15T12:55:00Z' OR time < '2018-03-15T12:55:00Z' OR 1 < 1521118500 OR 1521118800 > 1 ORDER BY 1")
  599. })
  600. Convey("Given a stored procedure that takes @from and @to in epoch time", func() {
  601. sql := `
  602. IF object_id('sp_test_epoch') IS NOT NULL
  603. DROP PROCEDURE sp_test_epoch
  604. `
  605. _, err := sess.Exec(sql)
  606. So(err, ShouldBeNil)
  607. sql = `
  608. CREATE PROCEDURE sp_test_epoch(
  609. @from int,
  610. @to int,
  611. @interval nvarchar(50) = '5m',
  612. @metric nvarchar(200) = 'ALL'
  613. ) AS
  614. BEGIN
  615. DECLARE @dInterval int
  616. SELECT @dInterval = 300
  617. IF @interval = '10m'
  618. SELECT @dInterval = 600
  619. SELECT
  620. CAST(ROUND(DATEDIFF(second, '1970-01-01', time)/CAST(@dInterval as float), 0) as bigint)*@dInterval as time,
  621. measurement as metric,
  622. avg(valueOne) as valueOne,
  623. avg(valueTwo) as valueTwo
  624. FROM
  625. metric_values
  626. WHERE
  627. time BETWEEN DATEADD(s, @from, '1970-01-01') AND DATEADD(s, @to, '1970-01-01') AND
  628. (@metric = 'ALL' OR measurement = @metric)
  629. GROUP BY
  630. CAST(ROUND(DATEDIFF(second, '1970-01-01', time)/CAST(@dInterval as float), 0) as bigint)*@dInterval,
  631. measurement
  632. ORDER BY 1
  633. END
  634. `
  635. _, err = sess.Exec(sql)
  636. So(err, ShouldBeNil)
  637. Convey("When doing a metric query using stored procedure should return correct result", func() {
  638. tsdb.Interpolate = origInterpolate
  639. query := &tsdb.TsdbQuery{
  640. Queries: []*tsdb.Query{
  641. {
  642. DataSource: &models.DataSource{JsonData: simplejson.New()},
  643. Model: simplejson.NewFromAny(map[string]interface{}{
  644. "rawSql": `DECLARE
  645. @from int = $__unixEpochFrom(),
  646. @to int = $__unixEpochTo()
  647. EXEC dbo.sp_test_epoch @from, @to`,
  648. "format": "time_series",
  649. }),
  650. RefId: "A",
  651. },
  652. },
  653. TimeRange: &tsdb.TimeRange{
  654. From: "1521117000000",
  655. To: "1521122100000",
  656. },
  657. }
  658. resp, err := endpoint.Query(context.Background(), nil, query)
  659. queryResult := resp.Results["A"]
  660. So(err, ShouldBeNil)
  661. So(queryResult.Error, ShouldBeNil)
  662. So(len(queryResult.Series), ShouldEqual, 4)
  663. So(queryResult.Series[0].Name, ShouldEqual, "Metric A valueOne")
  664. So(queryResult.Series[1].Name, ShouldEqual, "Metric A valueTwo")
  665. So(queryResult.Series[2].Name, ShouldEqual, "Metric B valueOne")
  666. So(queryResult.Series[3].Name, ShouldEqual, "Metric B valueTwo")
  667. })
  668. })
  669. Convey("Given a stored procedure that takes @from and @to in datetime", func() {
  670. sql := `
  671. IF object_id('sp_test_datetime') IS NOT NULL
  672. DROP PROCEDURE sp_test_datetime
  673. `
  674. _, err := sess.Exec(sql)
  675. So(err, ShouldBeNil)
  676. sql = `
  677. CREATE PROCEDURE sp_test_datetime(
  678. @from datetime,
  679. @to datetime,
  680. @interval nvarchar(50) = '5m',
  681. @metric nvarchar(200) = 'ALL'
  682. ) AS
  683. BEGIN
  684. DECLARE @dInterval int
  685. SELECT @dInterval = 300
  686. IF @interval = '10m'
  687. SELECT @dInterval = 600
  688. SELECT
  689. CAST(ROUND(DATEDIFF(second, '1970-01-01', time)/CAST(@dInterval as float), 0) as bigint)*@dInterval as time,
  690. measurement as metric,
  691. avg(valueOne) as valueOne,
  692. avg(valueTwo) as valueTwo
  693. FROM
  694. metric_values
  695. WHERE
  696. time BETWEEN @from AND @to AND
  697. (@metric = 'ALL' OR measurement = @metric)
  698. GROUP BY
  699. CAST(ROUND(DATEDIFF(second, '1970-01-01', time)/CAST(@dInterval as float), 0) as bigint)*@dInterval,
  700. measurement
  701. ORDER BY 1
  702. END
  703. `
  704. _, err = sess.Exec(sql)
  705. So(err, ShouldBeNil)
  706. Convey("When doing a metric query using stored procedure should return correct result", func() {
  707. tsdb.Interpolate = origInterpolate
  708. query := &tsdb.TsdbQuery{
  709. Queries: []*tsdb.Query{
  710. {
  711. DataSource: &models.DataSource{JsonData: simplejson.New()},
  712. Model: simplejson.NewFromAny(map[string]interface{}{
  713. "rawSql": `DECLARE
  714. @from int = $__unixEpochFrom(),
  715. @to int = $__unixEpochTo()
  716. EXEC dbo.sp_test_epoch @from, @to`,
  717. "format": "time_series",
  718. }),
  719. RefId: "A",
  720. },
  721. },
  722. TimeRange: &tsdb.TimeRange{
  723. From: "1521117000000",
  724. To: "1521122100000",
  725. },
  726. }
  727. resp, err := endpoint.Query(context.Background(), nil, query)
  728. queryResult := resp.Results["A"]
  729. So(err, ShouldBeNil)
  730. So(queryResult.Error, ShouldBeNil)
  731. So(len(queryResult.Series), ShouldEqual, 4)
  732. So(queryResult.Series[0].Name, ShouldEqual, "Metric A valueOne")
  733. So(queryResult.Series[1].Name, ShouldEqual, "Metric A valueTwo")
  734. So(queryResult.Series[2].Name, ShouldEqual, "Metric B valueOne")
  735. So(queryResult.Series[3].Name, ShouldEqual, "Metric B valueTwo")
  736. })
  737. })
  738. })
  739. Convey("Given a table with event data", func() {
  740. sql := `
  741. IF OBJECT_ID('dbo.[event]', 'U') IS NOT NULL
  742. DROP TABLE dbo.[event]
  743. CREATE TABLE [event] (
  744. time_sec int,
  745. description nvarchar(100),
  746. tags nvarchar(100),
  747. )
  748. `
  749. _, err := sess.Exec(sql)
  750. So(err, ShouldBeNil)
  751. type event struct {
  752. TimeSec int64
  753. Description string
  754. Tags string
  755. }
  756. events := []*event{}
  757. for _, t := range genTimeRangeByInterval(fromStart.Add(-20*time.Minute), 60*time.Minute, 25*time.Minute) {
  758. events = append(events, &event{
  759. TimeSec: t.Unix(),
  760. Description: "Someone deployed something",
  761. Tags: "deploy",
  762. })
  763. events = append(events, &event{
  764. TimeSec: t.Add(5 * time.Minute).Unix(),
  765. Description: "New support ticket registered",
  766. Tags: "ticket",
  767. })
  768. }
  769. for _, e := range events {
  770. sql = fmt.Sprintf(`
  771. INSERT [event] (time_sec, description, tags)
  772. VALUES(%d, '%s', '%s')
  773. `, e.TimeSec, e.Description, e.Tags)
  774. _, err = sess.Exec(sql)
  775. So(err, ShouldBeNil)
  776. }
  777. Convey("When doing an annotation query of deploy events should return expected result", func() {
  778. query := &tsdb.TsdbQuery{
  779. Queries: []*tsdb.Query{
  780. {
  781. Model: simplejson.NewFromAny(map[string]interface{}{
  782. "rawSql": "SELECT time_sec as time, description as [text], tags FROM [event] WHERE $__unixEpochFilter(time_sec) AND tags='deploy' ORDER BY 1 ASC",
  783. "format": "table",
  784. }),
  785. RefId: "Deploys",
  786. },
  787. },
  788. TimeRange: &tsdb.TimeRange{
  789. From: fmt.Sprintf("%v", fromStart.Add(-20*time.Minute).Unix()*1000),
  790. To: fmt.Sprintf("%v", fromStart.Add(40*time.Minute).Unix()*1000),
  791. },
  792. }
  793. resp, err := endpoint.Query(context.Background(), nil, query)
  794. queryResult := resp.Results["Deploys"]
  795. So(err, ShouldBeNil)
  796. So(len(queryResult.Tables[0].Rows), ShouldEqual, 3)
  797. })
  798. Convey("When doing an annotation query of ticket events should return expected result", func() {
  799. query := &tsdb.TsdbQuery{
  800. Queries: []*tsdb.Query{
  801. {
  802. Model: simplejson.NewFromAny(map[string]interface{}{
  803. "rawSql": "SELECT time_sec as time, description as [text], tags FROM [event] WHERE $__unixEpochFilter(time_sec) AND tags='ticket' ORDER BY 1 ASC",
  804. "format": "table",
  805. }),
  806. RefId: "Tickets",
  807. },
  808. },
  809. TimeRange: &tsdb.TimeRange{
  810. From: fmt.Sprintf("%v", fromStart.Add(-20*time.Minute).Unix()*1000),
  811. To: fmt.Sprintf("%v", fromStart.Add(40*time.Minute).Unix()*1000),
  812. },
  813. }
  814. resp, err := endpoint.Query(context.Background(), nil, query)
  815. queryResult := resp.Results["Tickets"]
  816. So(err, ShouldBeNil)
  817. So(len(queryResult.Tables[0].Rows), ShouldEqual, 3)
  818. })
  819. Convey("When doing an annotation query with a time column in datetime format", func() {
  820. dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
  821. dtFormat := "2006-01-02 15:04:05.999999999"
  822. query := &tsdb.TsdbQuery{
  823. Queries: []*tsdb.Query{
  824. {
  825. Model: simplejson.NewFromAny(map[string]interface{}{
  826. "rawSql": fmt.Sprintf(`SELECT
  827. CAST('%s' AS DATETIME) as time,
  828. 'message' as text,
  829. 'tag1,tag2' as tags
  830. `, dt.Format(dtFormat)),
  831. "format": "table",
  832. }),
  833. RefId: "A",
  834. },
  835. },
  836. }
  837. resp, err := endpoint.Query(context.Background(), nil, query)
  838. So(err, ShouldBeNil)
  839. queryResult := resp.Results["A"]
  840. So(queryResult.Error, ShouldBeNil)
  841. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  842. columns := queryResult.Tables[0].Rows[0]
  843. //Should be in milliseconds
  844. So(columns[0].(float64), ShouldEqual, float64(dt.UnixNano()/1e6))
  845. })
  846. Convey("When doing an annotation query with a time column in epoch second format should return ms", func() {
  847. dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
  848. query := &tsdb.TsdbQuery{
  849. Queries: []*tsdb.Query{
  850. {
  851. Model: simplejson.NewFromAny(map[string]interface{}{
  852. "rawSql": fmt.Sprintf(`SELECT
  853. %d as time,
  854. 'message' as text,
  855. 'tag1,tag2' as tags
  856. `, dt.Unix()),
  857. "format": "table",
  858. }),
  859. RefId: "A",
  860. },
  861. },
  862. }
  863. resp, err := endpoint.Query(context.Background(), nil, query)
  864. So(err, ShouldBeNil)
  865. queryResult := resp.Results["A"]
  866. So(queryResult.Error, ShouldBeNil)
  867. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  868. columns := queryResult.Tables[0].Rows[0]
  869. //Should be in milliseconds
  870. So(columns[0].(int64), ShouldEqual, dt.Unix()*1000)
  871. })
  872. Convey("When doing an annotation query with a time column in epoch second format (int) should return ms", func() {
  873. dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
  874. query := &tsdb.TsdbQuery{
  875. Queries: []*tsdb.Query{
  876. {
  877. Model: simplejson.NewFromAny(map[string]interface{}{
  878. "rawSql": fmt.Sprintf(`SELECT
  879. cast(%d as int) as time,
  880. 'message' as text,
  881. 'tag1,tag2' as tags
  882. `, dt.Unix()),
  883. "format": "table",
  884. }),
  885. RefId: "A",
  886. },
  887. },
  888. }
  889. resp, err := endpoint.Query(context.Background(), nil, query)
  890. So(err, ShouldBeNil)
  891. queryResult := resp.Results["A"]
  892. So(queryResult.Error, ShouldBeNil)
  893. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  894. columns := queryResult.Tables[0].Rows[0]
  895. //Should be in milliseconds
  896. So(columns[0].(int64), ShouldEqual, dt.Unix()*1000)
  897. })
  898. Convey("When doing an annotation query with a time column in epoch millisecond format should return ms", func() {
  899. dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
  900. query := &tsdb.TsdbQuery{
  901. Queries: []*tsdb.Query{
  902. {
  903. Model: simplejson.NewFromAny(map[string]interface{}{
  904. "rawSql": fmt.Sprintf(`SELECT
  905. %d as time,
  906. 'message' as text,
  907. 'tag1,tag2' as tags
  908. `, dt.Unix()*1000),
  909. "format": "table",
  910. }),
  911. RefId: "A",
  912. },
  913. },
  914. }
  915. resp, err := endpoint.Query(context.Background(), nil, query)
  916. So(err, ShouldBeNil)
  917. queryResult := resp.Results["A"]
  918. So(queryResult.Error, ShouldBeNil)
  919. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  920. columns := queryResult.Tables[0].Rows[0]
  921. //Should be in milliseconds
  922. So(columns[0].(float64), ShouldEqual, float64(dt.Unix()*1000))
  923. })
  924. Convey("When doing an annotation query with a time column holding a bigint null value should return nil", func() {
  925. query := &tsdb.TsdbQuery{
  926. Queries: []*tsdb.Query{
  927. {
  928. Model: simplejson.NewFromAny(map[string]interface{}{
  929. "rawSql": `SELECT
  930. cast(null as bigint) as time,
  931. 'message' as text,
  932. 'tag1,tag2' as tags
  933. `,
  934. "format": "table",
  935. }),
  936. RefId: "A",
  937. },
  938. },
  939. }
  940. resp, err := endpoint.Query(context.Background(), nil, query)
  941. So(err, ShouldBeNil)
  942. queryResult := resp.Results["A"]
  943. So(queryResult.Error, ShouldBeNil)
  944. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  945. columns := queryResult.Tables[0].Rows[0]
  946. //Should be in milliseconds
  947. So(columns[0], ShouldBeNil)
  948. })
  949. Convey("When doing an annotation query with a time column holding a datetime null value should return nil", func() {
  950. query := &tsdb.TsdbQuery{
  951. Queries: []*tsdb.Query{
  952. {
  953. Model: simplejson.NewFromAny(map[string]interface{}{
  954. "rawSql": `SELECT
  955. cast(null as datetime) as time,
  956. 'message' as text,
  957. 'tag1,tag2' as tags
  958. `,
  959. "format": "table",
  960. }),
  961. RefId: "A",
  962. },
  963. },
  964. }
  965. resp, err := endpoint.Query(context.Background(), nil, query)
  966. So(err, ShouldBeNil)
  967. queryResult := resp.Results["A"]
  968. So(queryResult.Error, ShouldBeNil)
  969. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  970. columns := queryResult.Tables[0].Rows[0]
  971. //Should be in milliseconds
  972. So(columns[0], ShouldBeNil)
  973. })
  974. })
  975. })
  976. }
  977. func InitMSSQLTestDB(t *testing.T) *xorm.Engine {
  978. x, err := xorm.NewEngine(sqlutil.TestDB_Mssql.DriverName, strings.Replace(sqlutil.TestDB_Mssql.ConnStr, "localhost", serverIP, 1))
  979. if err != nil {
  980. t.Fatalf("Failed to init mssql db %v", err)
  981. }
  982. x.DatabaseTZ = time.UTC
  983. x.TZLocation = time.UTC
  984. // x.ShowSQL()
  985. return x
  986. }
  987. func genTimeRangeByInterval(from time.Time, duration time.Duration, interval time.Duration) []time.Time {
  988. durationSec := int64(duration.Seconds())
  989. intervalSec := int64(interval.Seconds())
  990. timeRange := []time.Time{}
  991. for i := int64(0); i < durationSec; i += intervalSec {
  992. timeRange = append(timeRange, from)
  993. from = from.Add(time.Duration(int64(time.Second) * intervalSec))
  994. }
  995. return timeRange
  996. }