scenarios.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607
  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. return queryRes
  353. }
  354. func getPredictableCSVWave(query *tsdb.Query, context *tsdb.TsdbQuery) *tsdb.QueryResult {
  355. queryRes := tsdb.NewQueryResult()
  356. // Process Input
  357. var timeStep int64
  358. options := query.Model.Get("csvWave")
  359. var err error
  360. if timeStep, err = options.Get("timeStep").Int64(); err != nil {
  361. queryRes.Error = fmt.Errorf("failed to parse timeStep value '%v' into integer: %v", options.Get("timeStep"), err)
  362. return queryRes
  363. }
  364. rawValues := options.Get("valuesCSV").MustString()
  365. rawValues = strings.TrimRight(strings.TrimSpace(rawValues), ",") // Strip Trailing Comma
  366. rawValesCSV := strings.Split(rawValues, ",")
  367. values := make([]null.Float, len(rawValesCSV))
  368. for i, rawValue := range rawValesCSV {
  369. val, err := null.FloatFromString(strings.TrimSpace(rawValue), "null")
  370. if err != nil {
  371. queryRes.Error = fmt.Errorf("failed to parse value '%v' into nullable float: err", rawValue, err)
  372. return queryRes
  373. }
  374. values[i] = val
  375. }
  376. timeStep = timeStep * 1000 // Seconds to Milliseconds
  377. valuesLen := int64(len(values))
  378. getValue := func(mod int64) (null.Float, error) {
  379. var i int64
  380. for i = 0; i < valuesLen; i++ {
  381. if mod == i*timeStep {
  382. return values[i], nil
  383. }
  384. }
  385. return null.Float{}, fmt.Errorf("did not get value at point in waveform - should not be here")
  386. }
  387. points, err := predictableSeries(context.TimeRange, timeStep, valuesLen, getValue)
  388. if err != nil {
  389. queryRes.Error = err
  390. return queryRes
  391. }
  392. series := newSeriesForQuery(query)
  393. series.Points = *points
  394. queryRes.Series = append(queryRes.Series, series)
  395. return queryRes
  396. }
  397. func predictableSeries(timeRange *tsdb.TimeRange, timeStep, length int64, getValue func(mod int64) (null.Float, error)) (*tsdb.TimeSeriesPoints, error) {
  398. points := make(tsdb.TimeSeriesPoints, 0)
  399. from := timeRange.GetFromAsMsEpoch()
  400. to := timeRange.GetToAsMsEpoch()
  401. timeCursor := from - (from % timeStep) // Truncate Start
  402. wavePeriod := timeStep * length
  403. maxPoints := 10000 // Don't return too many points
  404. for i := 0; i < maxPoints && timeCursor < to; i++ {
  405. val, err := getValue(timeCursor % wavePeriod)
  406. if err != nil {
  407. return &points, err
  408. }
  409. point := tsdb.NewTimePoint(val, float64(timeCursor))
  410. points = append(points, point)
  411. timeCursor += timeStep
  412. }
  413. return &points, nil
  414. }
  415. func getRandomWalk(query *tsdb.Query, tsdbQuery *tsdb.TsdbQuery) *tsdb.QueryResult {
  416. timeWalkerMs := tsdbQuery.TimeRange.GetFromAsMsEpoch()
  417. to := tsdbQuery.TimeRange.GetToAsMsEpoch()
  418. series := newSeriesForQuery(query)
  419. points := make(tsdb.TimeSeriesPoints, 0)
  420. walker := query.Model.Get("startValue").MustFloat64(rand.Float64() * 100)
  421. for i := int64(0); i < 10000 && timeWalkerMs < to; i++ {
  422. points = append(points, tsdb.NewTimePoint(null.FloatFrom(walker), float64(timeWalkerMs)))
  423. walker += rand.Float64() - 0.5
  424. timeWalkerMs += query.IntervalMs
  425. }
  426. series.Points = points
  427. queryRes := tsdb.NewQueryResult()
  428. queryRes.Series = append(queryRes.Series, series)
  429. return queryRes
  430. }
  431. func getRandomWalkTable(query *tsdb.Query, tsdbQuery *tsdb.TsdbQuery) *tsdb.QueryResult {
  432. timeWalkerMs := tsdbQuery.TimeRange.GetFromAsMsEpoch()
  433. to := tsdbQuery.TimeRange.GetToAsMsEpoch()
  434. table := tsdb.Table{
  435. Columns: []tsdb.TableColumn{
  436. {Text: "Time"},
  437. {Text: "Value"},
  438. {Text: "Min"},
  439. {Text: "Max"},
  440. {Text: "Info"},
  441. },
  442. Rows: []tsdb.RowValues{},
  443. }
  444. withNil := query.Model.Get("withNil").MustBool(false)
  445. walker := query.Model.Get("startValue").MustFloat64(rand.Float64() * 100)
  446. spread := 2.5
  447. var info strings.Builder
  448. for i := int64(0); i < query.MaxDataPoints && timeWalkerMs < to; i++ {
  449. delta := rand.Float64() - 0.5
  450. walker += delta
  451. info.Reset()
  452. if delta > 0 {
  453. info.WriteString("up")
  454. } else {
  455. info.WriteString("down")
  456. }
  457. if math.Abs(delta) > .4 {
  458. info.WriteString(" fast")
  459. }
  460. row := tsdb.RowValues{
  461. float64(timeWalkerMs),
  462. walker,
  463. walker - ((rand.Float64() * spread) + 0.01), // Min
  464. walker + ((rand.Float64() * spread) + 0.01), // Max
  465. info.String(),
  466. }
  467. // Add some random null values
  468. if withNil && rand.Float64() > 0.8 {
  469. for i := 1; i < 4; i++ {
  470. if rand.Float64() > .2 {
  471. row[i] = nil
  472. }
  473. }
  474. }
  475. table.Rows = append(table.Rows, row)
  476. timeWalkerMs += query.IntervalMs
  477. }
  478. queryRes := tsdb.NewQueryResult()
  479. queryRes.Tables = append(queryRes.Tables, &table)
  480. return queryRes
  481. }
  482. func registerScenario(scenario *Scenario) {
  483. ScenarioRegistry[scenario.Id] = scenario
  484. }
  485. func newSeriesForQuery(query *tsdb.Query) *tsdb.TimeSeries {
  486. alias := query.Model.Get("alias").MustString("")
  487. if alias == "" {
  488. alias = query.RefId + "-series"
  489. }
  490. return &tsdb.TimeSeries{Name: alias}
  491. }
  492. func fromStringOrNumber(val *simplejson.Json) (null.Float, error) {
  493. switch v := val.Interface().(type) {
  494. case json.Number:
  495. fV, err := v.Float64()
  496. if err != nil {
  497. return null.Float{}, err
  498. }
  499. return null.FloatFrom(fV), nil
  500. case string:
  501. return null.FloatFromString(v, "null")
  502. default:
  503. return null.Float{}, fmt.Errorf("failed to extract value")
  504. }
  505. }