scenarios.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651
  1. package testdata
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "math"
  6. "math/rand"
  7. "strconv"
  8. "strings"
  9. "time"
  10. "github.com/grafana/grafana/pkg/components/simplejson"
  11. "github.com/grafana/grafana/pkg/components/null"
  12. "github.com/grafana/grafana/pkg/infra/log"
  13. "github.com/grafana/grafana/pkg/tsdb"
  14. )
  15. type ScenarioHandler func(query *tsdb.Query, context *tsdb.TsdbQuery) *tsdb.QueryResult
  16. type Scenario struct {
  17. Id string `json:"id"`
  18. Name string `json:"name"`
  19. StringInput string `json:"stringOption"`
  20. Description string `json:"description"`
  21. Handler ScenarioHandler `json:"-"`
  22. }
  23. var ScenarioRegistry map[string]*Scenario
  24. func init() {
  25. ScenarioRegistry = make(map[string]*Scenario)
  26. logger := log.New("tsdb.testdata")
  27. logger.Debug("Initializing TestData Scenario")
  28. registerScenario(&Scenario{
  29. Id: "exponential_heatmap_bucket_data",
  30. Name: "Exponential heatmap bucket data",
  31. Handler: func(query *tsdb.Query, context *tsdb.TsdbQuery) *tsdb.QueryResult {
  32. to := context.TimeRange.GetToAsMsEpoch()
  33. var series []*tsdb.TimeSeries
  34. start := 1
  35. factor := 2
  36. for i := 0; i < 10; i++ {
  37. timeWalkerMs := context.TimeRange.GetFromAsMsEpoch()
  38. serie := &tsdb.TimeSeries{Name: strconv.Itoa(start)}
  39. start *= factor
  40. points := make(tsdb.TimeSeriesPoints, 0)
  41. for j := int64(0); j < 100 && timeWalkerMs < to; j++ {
  42. v := float64(rand.Int63n(100))
  43. points = append(points, tsdb.NewTimePoint(null.FloatFrom(v), float64(timeWalkerMs)))
  44. timeWalkerMs += query.IntervalMs * 50
  45. }
  46. serie.Points = points
  47. series = append(series, serie)
  48. }
  49. queryRes := tsdb.NewQueryResult()
  50. queryRes.Series = append(queryRes.Series, series...)
  51. return queryRes
  52. },
  53. })
  54. registerScenario(&Scenario{
  55. Id: "linear_heatmap_bucket_data",
  56. Name: "Linear heatmap bucket data",
  57. Handler: func(query *tsdb.Query, context *tsdb.TsdbQuery) *tsdb.QueryResult {
  58. to := context.TimeRange.GetToAsMsEpoch()
  59. var series []*tsdb.TimeSeries
  60. for i := 0; i < 10; i++ {
  61. timeWalkerMs := context.TimeRange.GetFromAsMsEpoch()
  62. serie := &tsdb.TimeSeries{Name: strconv.Itoa(i * 10)}
  63. points := make(tsdb.TimeSeriesPoints, 0)
  64. for j := int64(0); j < 100 && timeWalkerMs < to; j++ {
  65. v := float64(rand.Int63n(100))
  66. points = append(points, tsdb.NewTimePoint(null.FloatFrom(v), float64(timeWalkerMs)))
  67. timeWalkerMs += query.IntervalMs * 50
  68. }
  69. serie.Points = points
  70. series = append(series, serie)
  71. }
  72. queryRes := tsdb.NewQueryResult()
  73. queryRes.Series = append(queryRes.Series, series...)
  74. return queryRes
  75. },
  76. })
  77. registerScenario(&Scenario{
  78. Id: "random_walk",
  79. Name: "Random Walk",
  80. Handler: func(query *tsdb.Query, context *tsdb.TsdbQuery) *tsdb.QueryResult {
  81. return getRandomWalk(query, context)
  82. },
  83. })
  84. registerScenario(&Scenario{
  85. Id: "predictable_pulse",
  86. Name: "Predictable Pulse",
  87. Handler: func(query *tsdb.Query, context *tsdb.TsdbQuery) *tsdb.QueryResult {
  88. return getPredictablePulse(query, context)
  89. },
  90. Description: PredictablePulseDesc,
  91. })
  92. registerScenario(&Scenario{
  93. Id: "predictable_csv_wave",
  94. Name: "Predictable CSV Wave",
  95. Handler: func(query *tsdb.Query, context *tsdb.TsdbQuery) *tsdb.QueryResult {
  96. return getPredictableCSVWave(query, context)
  97. },
  98. })
  99. registerScenario(&Scenario{
  100. Id: "random_walk_table",
  101. Name: "Random Walk Table",
  102. Handler: func(query *tsdb.Query, context *tsdb.TsdbQuery) *tsdb.QueryResult {
  103. return getRandomWalkTable(query, context)
  104. },
  105. })
  106. registerScenario(&Scenario{
  107. Id: "slow_query",
  108. Name: "Slow Query",
  109. StringInput: "5s",
  110. Handler: func(query *tsdb.Query, context *tsdb.TsdbQuery) *tsdb.QueryResult {
  111. stringInput := query.Model.Get("stringInput").MustString()
  112. parsedInterval, _ := time.ParseDuration(stringInput)
  113. time.Sleep(parsedInterval)
  114. return getRandomWalk(query, context)
  115. },
  116. })
  117. registerScenario(&Scenario{
  118. Id: "no_data_points",
  119. Name: "No Data Points",
  120. Handler: func(query *tsdb.Query, context *tsdb.TsdbQuery) *tsdb.QueryResult {
  121. return tsdb.NewQueryResult()
  122. },
  123. })
  124. registerScenario(&Scenario{
  125. Id: "datapoints_outside_range",
  126. Name: "Datapoints Outside Range",
  127. Handler: func(query *tsdb.Query, context *tsdb.TsdbQuery) *tsdb.QueryResult {
  128. queryRes := tsdb.NewQueryResult()
  129. series := newSeriesForQuery(query)
  130. outsideTime := context.TimeRange.MustGetFrom().Add(-1*time.Hour).Unix() * 1000
  131. series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFrom(10), float64(outsideTime)))
  132. queryRes.Series = append(queryRes.Series, series)
  133. return queryRes
  134. },
  135. })
  136. registerScenario(&Scenario{
  137. Id: "manual_entry",
  138. Name: "Manual Entry",
  139. Handler: func(query *tsdb.Query, context *tsdb.TsdbQuery) *tsdb.QueryResult {
  140. queryRes := tsdb.NewQueryResult()
  141. points := query.Model.Get("points").MustArray()
  142. series := newSeriesForQuery(query)
  143. startTime := context.TimeRange.GetFromAsMsEpoch()
  144. endTime := context.TimeRange.GetToAsMsEpoch()
  145. for _, val := range points {
  146. pointValues := val.([]interface{})
  147. var value null.Float
  148. var time int64
  149. if valueFloat, err := strconv.ParseFloat(string(pointValues[0].(json.Number)), 64); err == nil {
  150. value = null.FloatFrom(valueFloat)
  151. }
  152. if timeInt, err := strconv.ParseInt(string(pointValues[1].(json.Number)), 10, 64); err != nil {
  153. continue
  154. } else {
  155. time = timeInt
  156. }
  157. if time >= startTime && time <= endTime {
  158. series.Points = append(series.Points, tsdb.NewTimePoint(value, float64(time)))
  159. }
  160. }
  161. queryRes.Series = append(queryRes.Series, series)
  162. return queryRes
  163. },
  164. })
  165. registerScenario(&Scenario{
  166. Id: "csv_metric_values",
  167. Name: "CSV Metric Values",
  168. StringInput: "1,20,90,30,5,0",
  169. Handler: func(query *tsdb.Query, context *tsdb.TsdbQuery) *tsdb.QueryResult {
  170. queryRes := tsdb.NewQueryResult()
  171. stringInput := query.Model.Get("stringInput").MustString()
  172. stringInput = strings.Replace(stringInput, " ", "", -1)
  173. values := []null.Float{}
  174. for _, strVal := range strings.Split(stringInput, ",") {
  175. if strVal == "null" {
  176. values = append(values, null.FloatFromPtr(nil))
  177. }
  178. if val, err := strconv.ParseFloat(strVal, 64); err == nil {
  179. values = append(values, null.FloatFrom(val))
  180. }
  181. }
  182. if len(values) == 0 {
  183. return queryRes
  184. }
  185. series := newSeriesForQuery(query)
  186. startTime := context.TimeRange.GetFromAsMsEpoch()
  187. endTime := context.TimeRange.GetToAsMsEpoch()
  188. step := (endTime - startTime) / int64(len(values)-1)
  189. for _, val := range values {
  190. series.Points = append(series.Points, tsdb.TimePoint{val, null.FloatFrom(float64(startTime))})
  191. startTime += step
  192. }
  193. queryRes.Series = append(queryRes.Series, series)
  194. return queryRes
  195. },
  196. })
  197. registerScenario(&Scenario{
  198. Id: "streaming_client",
  199. Name: "Streaming Client",
  200. Handler: func(query *tsdb.Query, context *tsdb.TsdbQuery) *tsdb.QueryResult {
  201. // Real work is in javascript client
  202. return tsdb.NewQueryResult()
  203. },
  204. })
  205. registerScenario(&Scenario{
  206. Id: "table_static",
  207. Name: "Table Static",
  208. Handler: func(query *tsdb.Query, context *tsdb.TsdbQuery) *tsdb.QueryResult {
  209. timeWalkerMs := context.TimeRange.GetFromAsMsEpoch()
  210. to := context.TimeRange.GetToAsMsEpoch()
  211. table := tsdb.Table{
  212. Columns: []tsdb.TableColumn{
  213. {Text: "Time"},
  214. {Text: "Message"},
  215. {Text: "Description"},
  216. {Text: "Value"},
  217. },
  218. Rows: []tsdb.RowValues{},
  219. }
  220. for i := int64(0); i < 10 && timeWalkerMs < to; i++ {
  221. table.Rows = append(table.Rows, tsdb.RowValues{float64(timeWalkerMs), "This is a message", "Description", 23.1})
  222. timeWalkerMs += query.IntervalMs
  223. }
  224. queryRes := tsdb.NewQueryResult()
  225. queryRes.Tables = append(queryRes.Tables, &table)
  226. return queryRes
  227. },
  228. })
  229. registerScenario(&Scenario{
  230. Id: "logs",
  231. Name: "Logs",
  232. Handler: func(query *tsdb.Query, context *tsdb.TsdbQuery) *tsdb.QueryResult {
  233. from := context.TimeRange.GetFromAsMsEpoch()
  234. to := context.TimeRange.GetToAsMsEpoch()
  235. lines := query.Model.Get("lines").MustInt64(10)
  236. includeLevelColumn := query.Model.Get("levelColumn").MustBool(false)
  237. logLevelGenerator := newRandomStringProvider([]string{
  238. "emerg",
  239. "alert",
  240. "crit",
  241. "critical",
  242. "warn",
  243. "warning",
  244. "err",
  245. "eror",
  246. "error",
  247. "info",
  248. "notice",
  249. "dbug",
  250. "debug",
  251. "trace",
  252. "",
  253. })
  254. containerIDGenerator := newRandomStringProvider([]string{
  255. "f36a9eaa6d34310686f2b851655212023a216de955cbcc764210cefa71179b1a",
  256. "5a354a630364f3742c602f315132e16def594fe68b1e4a195b2fce628e24c97a",
  257. })
  258. hostnameGenerator := newRandomStringProvider([]string{
  259. "srv-001",
  260. "srv-002",
  261. })
  262. table := tsdb.Table{
  263. Columns: []tsdb.TableColumn{
  264. {Text: "time"},
  265. {Text: "message"},
  266. {Text: "container_id"},
  267. {Text: "hostname"},
  268. },
  269. Rows: []tsdb.RowValues{},
  270. }
  271. if includeLevelColumn {
  272. table.Columns = append(table.Columns, tsdb.TableColumn{Text: "level"})
  273. }
  274. for i := int64(0); i < lines && to > from; i++ {
  275. row := tsdb.RowValues{float64(to)}
  276. logLevel := logLevelGenerator.Next()
  277. timeFormatted := time.Unix(to/1000, 0).Format(time.RFC3339)
  278. lvlString := ""
  279. if !includeLevelColumn {
  280. lvlString = fmt.Sprintf("lvl=%s ", logLevel)
  281. }
  282. row = append(row, fmt.Sprintf("t=%s %smsg=\"Request Completed\" logger=context userId=1 orgId=1 uname=admin method=GET path=/api/datasources/proxy/152/api/prom/label status=502 remote_addr=[::1] time_ms=1 size=0 referer=\"http://localhost:3000/explore?left=%%5B%%22now-6h%%22,%%22now%%22,%%22Prometheus%%202.x%%22,%%7B%%7D,%%7B%%22ui%%22:%%5Btrue,true,true,%%22none%%22%%5D%%7D%%5D\"", timeFormatted, lvlString))
  283. row = append(row, containerIDGenerator.Next())
  284. row = append(row, hostnameGenerator.Next())
  285. if includeLevelColumn {
  286. row = append(row, logLevel)
  287. }
  288. table.Rows = append(table.Rows, row)
  289. to -= query.IntervalMs
  290. }
  291. queryRes := tsdb.NewQueryResult()
  292. queryRes.Tables = append(queryRes.Tables, &table)
  293. return queryRes
  294. },
  295. })
  296. }
  297. // PredictablePulseDesc is the description for the Predictable Pulse scenerio.
  298. const PredictablePulseDesc = `Predictable Pulse returns a pulse wave where there is a datapoint every timeStepSeconds.
  299. The wave cycles at timeStepSeconds*(onCount+offCount).
  300. The cycle of the wave is based off of absolute time (from the epoch) which makes it predictable.
  301. Timestamps will line up evenly on timeStepSeconds (For example, 60 seconds means times will all end in :00 seconds).`
  302. func getPredictablePulse(query *tsdb.Query, context *tsdb.TsdbQuery) *tsdb.QueryResult {
  303. queryRes := tsdb.NewQueryResult()
  304. // Process Input
  305. var timeStep int64
  306. var onCount int64
  307. var offCount int64
  308. var onValue null.Float
  309. var offValue null.Float
  310. options := query.Model.Get("pulseWave")
  311. var err error
  312. if timeStep, err = options.Get("timeStep").Int64(); err != nil {
  313. queryRes.Error = fmt.Errorf("failed to parse timeStep value '%v' into integer: %v", options.Get("timeStep"), err)
  314. return queryRes
  315. }
  316. if onCount, err = options.Get("onCount").Int64(); err != nil {
  317. queryRes.Error = fmt.Errorf("failed to parse onCount value '%v' into integer: %v", options.Get("onCount"), err)
  318. return queryRes
  319. }
  320. if offCount, err = options.Get("offCount").Int64(); err != nil {
  321. queryRes.Error = fmt.Errorf("failed to parse offCount value '%v' into integer: %v", options.Get("offCount"), err)
  322. return queryRes
  323. }
  324. onValue, err = fromStringOrNumber(options.Get("onValue"))
  325. if err != nil {
  326. queryRes.Error = fmt.Errorf("failed to parse onValue value '%v' into float: %v", options.Get("onValue"), err)
  327. return queryRes
  328. }
  329. offValue, err = fromStringOrNumber(options.Get("offValue"))
  330. if err != nil {
  331. queryRes.Error = fmt.Errorf("failed to parse offValue value '%v' into float: %v", options.Get("offValue"), err)
  332. return queryRes
  333. }
  334. timeStep = timeStep * 1000 // Seconds to Milliseconds
  335. onFor := func(mod int64) (null.Float, error) { // How many items in the cycle should get the on value
  336. var i int64
  337. for i = 0; i < onCount; i++ {
  338. if mod == i*timeStep {
  339. return onValue, nil
  340. }
  341. }
  342. return offValue, nil
  343. }
  344. points, err := predictableSeries(context.TimeRange, timeStep, onCount+offCount, onFor)
  345. if err != nil {
  346. queryRes.Error = err
  347. return queryRes
  348. }
  349. series := newSeriesForQuery(query)
  350. series.Points = *points
  351. queryRes.Series = append(queryRes.Series, series)
  352. attachLabels(query, queryRes)
  353. return queryRes
  354. }
  355. func getPredictableCSVWave(query *tsdb.Query, context *tsdb.TsdbQuery) *tsdb.QueryResult {
  356. queryRes := tsdb.NewQueryResult()
  357. // Process Input
  358. var timeStep int64
  359. options := query.Model.Get("csvWave")
  360. var err error
  361. if timeStep, err = options.Get("timeStep").Int64(); err != nil {
  362. queryRes.Error = fmt.Errorf("failed to parse timeStep value '%v' into integer: %v", options.Get("timeStep"), err)
  363. return queryRes
  364. }
  365. rawValues := options.Get("valuesCSV").MustString()
  366. rawValues = strings.TrimRight(strings.TrimSpace(rawValues), ",") // Strip Trailing Comma
  367. rawValesCSV := strings.Split(rawValues, ",")
  368. values := make([]null.Float, len(rawValesCSV))
  369. for i, rawValue := range rawValesCSV {
  370. val, err := null.FloatFromString(strings.TrimSpace(rawValue), "null")
  371. if err != nil {
  372. queryRes.Error = fmt.Errorf("failed to parse value '%v' into nullable float: err", rawValue, err)
  373. return queryRes
  374. }
  375. values[i] = val
  376. }
  377. timeStep = timeStep * 1000 // Seconds to Milliseconds
  378. valuesLen := int64(len(values))
  379. getValue := func(mod int64) (null.Float, error) {
  380. var i int64
  381. for i = 0; i < valuesLen; i++ {
  382. if mod == i*timeStep {
  383. return values[i], nil
  384. }
  385. }
  386. return null.Float{}, fmt.Errorf("did not get value at point in waveform - should not be here")
  387. }
  388. points, err := predictableSeries(context.TimeRange, timeStep, valuesLen, getValue)
  389. if err != nil {
  390. queryRes.Error = err
  391. return queryRes
  392. }
  393. series := newSeriesForQuery(query)
  394. series.Points = *points
  395. queryRes.Series = append(queryRes.Series, series)
  396. attachLabels(query, queryRes)
  397. return queryRes
  398. }
  399. func predictableSeries(timeRange *tsdb.TimeRange, timeStep, length int64, getValue func(mod int64) (null.Float, error)) (*tsdb.TimeSeriesPoints, error) {
  400. points := make(tsdb.TimeSeriesPoints, 0)
  401. from := timeRange.GetFromAsMsEpoch()
  402. to := timeRange.GetToAsMsEpoch()
  403. timeCursor := from - (from % timeStep) // Truncate Start
  404. wavePeriod := timeStep * length
  405. maxPoints := 10000 // Don't return too many points
  406. for i := 0; i < maxPoints && timeCursor < to; i++ {
  407. val, err := getValue(timeCursor % wavePeriod)
  408. if err != nil {
  409. return &points, err
  410. }
  411. point := tsdb.NewTimePoint(val, float64(timeCursor))
  412. points = append(points, point)
  413. timeCursor += timeStep
  414. }
  415. return &points, nil
  416. }
  417. func getRandomWalk(query *tsdb.Query, tsdbQuery *tsdb.TsdbQuery) *tsdb.QueryResult {
  418. timeWalkerMs := tsdbQuery.TimeRange.GetFromAsMsEpoch()
  419. to := tsdbQuery.TimeRange.GetToAsMsEpoch()
  420. series := newSeriesForQuery(query)
  421. points := make(tsdb.TimeSeriesPoints, 0)
  422. walker := query.Model.Get("startValue").MustFloat64(rand.Float64() * 100)
  423. for i := int64(0); i < 10000 && timeWalkerMs < to; i++ {
  424. points = append(points, tsdb.NewTimePoint(null.FloatFrom(walker), float64(timeWalkerMs)))
  425. walker += rand.Float64() - 0.5
  426. timeWalkerMs += query.IntervalMs
  427. }
  428. series.Points = points
  429. queryRes := tsdb.NewQueryResult()
  430. queryRes.Series = append(queryRes.Series, series)
  431. attachLabels(query, queryRes)
  432. return queryRes
  433. }
  434. /**
  435. * Looks for a labels request and adds them as tags
  436. *
  437. * '{job="foo", instance="bar"} => {job: "foo", instance: "bar"}`
  438. */
  439. func attachLabels(query *tsdb.Query, queryRes *tsdb.QueryResult) {
  440. labelText := query.Model.Get("labels").MustString("")
  441. if labelText == "" {
  442. return
  443. }
  444. tags := parseLabels(labelText)
  445. for _, series := range queryRes.Series {
  446. series.Tags = tags
  447. }
  448. }
  449. // generous parser:
  450. // {job="foo", instance="bar"}
  451. // job="foo", instance="bar"
  452. // job=foo, instance=bar
  453. // should all equal {job=foo, instance=bar}
  454. func parseLabels(text string) map[string]string {
  455. var tags map[string]string
  456. text = strings.Trim(text, `{}`)
  457. if len(text) < 2 {
  458. return tags
  459. }
  460. tags = make(map[string]string)
  461. for _, keyval := range strings.Split(text, ",") {
  462. idx := strings.Index(keyval, "=")
  463. key := strings.TrimSpace(keyval[:idx])
  464. val := strings.TrimSpace(keyval[idx+1:])
  465. tags[key] = val
  466. }
  467. return tags
  468. }
  469. func getRandomWalkTable(query *tsdb.Query, tsdbQuery *tsdb.TsdbQuery) *tsdb.QueryResult {
  470. timeWalkerMs := tsdbQuery.TimeRange.GetFromAsMsEpoch()
  471. to := tsdbQuery.TimeRange.GetToAsMsEpoch()
  472. table := tsdb.Table{
  473. Columns: []tsdb.TableColumn{
  474. {Text: "Time"},
  475. {Text: "Value"},
  476. {Text: "Min"},
  477. {Text: "Max"},
  478. {Text: "Info"},
  479. },
  480. Rows: []tsdb.RowValues{},
  481. }
  482. withNil := query.Model.Get("withNil").MustBool(false)
  483. walker := query.Model.Get("startValue").MustFloat64(rand.Float64() * 100)
  484. spread := 2.5
  485. var info strings.Builder
  486. for i := int64(0); i < query.MaxDataPoints && timeWalkerMs < to; i++ {
  487. delta := rand.Float64() - 0.5
  488. walker += delta
  489. info.Reset()
  490. if delta > 0 {
  491. info.WriteString("up")
  492. } else {
  493. info.WriteString("down")
  494. }
  495. if math.Abs(delta) > .4 {
  496. info.WriteString(" fast")
  497. }
  498. row := tsdb.RowValues{
  499. float64(timeWalkerMs),
  500. walker,
  501. walker - ((rand.Float64() * spread) + 0.01), // Min
  502. walker + ((rand.Float64() * spread) + 0.01), // Max
  503. info.String(),
  504. }
  505. // Add some random null values
  506. if withNil && rand.Float64() > 0.8 {
  507. for i := 1; i < 4; i++ {
  508. if rand.Float64() > .2 {
  509. row[i] = nil
  510. }
  511. }
  512. }
  513. table.Rows = append(table.Rows, row)
  514. timeWalkerMs += query.IntervalMs
  515. }
  516. queryRes := tsdb.NewQueryResult()
  517. queryRes.Tables = append(queryRes.Tables, &table)
  518. return queryRes
  519. }
  520. func registerScenario(scenario *Scenario) {
  521. ScenarioRegistry[scenario.Id] = scenario
  522. }
  523. func newSeriesForQuery(query *tsdb.Query) *tsdb.TimeSeries {
  524. alias := query.Model.Get("alias").MustString("")
  525. if alias == "" {
  526. alias = query.RefId + "-series"
  527. }
  528. return &tsdb.TimeSeries{Name: alias}
  529. }
  530. func fromStringOrNumber(val *simplejson.Json) (null.Float, error) {
  531. switch v := val.Interface().(type) {
  532. case json.Number:
  533. fV, err := v.Float64()
  534. if err != nil {
  535. return null.Float{}, err
  536. }
  537. return null.FloatFrom(fV), nil
  538. case string:
  539. return null.FloatFromString(v, "null")
  540. default:
  541. return null.Float{}, fmt.Errorf("failed to extract value")
  542. }
  543. }