mssql_test.go 37 KB

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