scenarios.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543
  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: "random_walk_table",
  94. Name: "Random Walk Table",
  95. Handler: func(query *tsdb.Query, context *tsdb.TsdbQuery) *tsdb.QueryResult {
  96. return getRandomWalkTable(query, context)
  97. },
  98. })
  99. registerScenario(&Scenario{
  100. Id: "slow_query",
  101. Name: "Slow Query",
  102. StringInput: "5s",
  103. Handler: func(query *tsdb.Query, context *tsdb.TsdbQuery) *tsdb.QueryResult {
  104. stringInput := query.Model.Get("stringInput").MustString()
  105. parsedInterval, _ := time.ParseDuration(stringInput)
  106. time.Sleep(parsedInterval)
  107. return getRandomWalk(query, context)
  108. },
  109. })
  110. registerScenario(&Scenario{
  111. Id: "no_data_points",
  112. Name: "No Data Points",
  113. Handler: func(query *tsdb.Query, context *tsdb.TsdbQuery) *tsdb.QueryResult {
  114. return tsdb.NewQueryResult()
  115. },
  116. })
  117. registerScenario(&Scenario{
  118. Id: "datapoints_outside_range",
  119. Name: "Datapoints Outside Range",
  120. Handler: func(query *tsdb.Query, context *tsdb.TsdbQuery) *tsdb.QueryResult {
  121. queryRes := tsdb.NewQueryResult()
  122. series := newSeriesForQuery(query)
  123. outsideTime := context.TimeRange.MustGetFrom().Add(-1*time.Hour).Unix() * 1000
  124. series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFrom(10), float64(outsideTime)))
  125. queryRes.Series = append(queryRes.Series, series)
  126. return queryRes
  127. },
  128. })
  129. registerScenario(&Scenario{
  130. Id: "manual_entry",
  131. Name: "Manual Entry",
  132. Handler: func(query *tsdb.Query, context *tsdb.TsdbQuery) *tsdb.QueryResult {
  133. queryRes := tsdb.NewQueryResult()
  134. points := query.Model.Get("points").MustArray()
  135. series := newSeriesForQuery(query)
  136. startTime := context.TimeRange.GetFromAsMsEpoch()
  137. endTime := context.TimeRange.GetToAsMsEpoch()
  138. for _, val := range points {
  139. pointValues := val.([]interface{})
  140. var value null.Float
  141. var time int64
  142. if valueFloat, err := strconv.ParseFloat(string(pointValues[0].(json.Number)), 64); err == nil {
  143. value = null.FloatFrom(valueFloat)
  144. }
  145. if timeInt, err := strconv.ParseInt(string(pointValues[1].(json.Number)), 10, 64); err != nil {
  146. continue
  147. } else {
  148. time = timeInt
  149. }
  150. if time >= startTime && time <= endTime {
  151. series.Points = append(series.Points, tsdb.NewTimePoint(value, float64(time)))
  152. }
  153. }
  154. queryRes.Series = append(queryRes.Series, series)
  155. return queryRes
  156. },
  157. })
  158. registerScenario(&Scenario{
  159. Id: "csv_metric_values",
  160. Name: "CSV Metric Values",
  161. StringInput: "1,20,90,30,5,0",
  162. Handler: func(query *tsdb.Query, context *tsdb.TsdbQuery) *tsdb.QueryResult {
  163. queryRes := tsdb.NewQueryResult()
  164. stringInput := query.Model.Get("stringInput").MustString()
  165. stringInput = strings.Replace(stringInput, " ", "", -1)
  166. values := []null.Float{}
  167. for _, strVal := range strings.Split(stringInput, ",") {
  168. if strVal == "null" {
  169. values = append(values, null.FloatFromPtr(nil))
  170. }
  171. if val, err := strconv.ParseFloat(strVal, 64); err == nil {
  172. values = append(values, null.FloatFrom(val))
  173. }
  174. }
  175. if len(values) == 0 {
  176. return queryRes
  177. }
  178. series := newSeriesForQuery(query)
  179. startTime := context.TimeRange.GetFromAsMsEpoch()
  180. endTime := context.TimeRange.GetToAsMsEpoch()
  181. step := (endTime - startTime) / int64(len(values)-1)
  182. for _, val := range values {
  183. series.Points = append(series.Points, tsdb.TimePoint{val, null.FloatFrom(float64(startTime))})
  184. startTime += step
  185. }
  186. queryRes.Series = append(queryRes.Series, series)
  187. return queryRes
  188. },
  189. })
  190. registerScenario(&Scenario{
  191. Id: "streaming_client",
  192. Name: "Streaming Client",
  193. Handler: func(query *tsdb.Query, context *tsdb.TsdbQuery) *tsdb.QueryResult {
  194. // Real work is in javascript client
  195. return tsdb.NewQueryResult()
  196. },
  197. })
  198. registerScenario(&Scenario{
  199. Id: "table_static",
  200. Name: "Table Static",
  201. Handler: func(query *tsdb.Query, context *tsdb.TsdbQuery) *tsdb.QueryResult {
  202. timeWalkerMs := context.TimeRange.GetFromAsMsEpoch()
  203. to := context.TimeRange.GetToAsMsEpoch()
  204. table := tsdb.Table{
  205. Columns: []tsdb.TableColumn{
  206. {Text: "Time"},
  207. {Text: "Message"},
  208. {Text: "Description"},
  209. {Text: "Value"},
  210. },
  211. Rows: []tsdb.RowValues{},
  212. }
  213. for i := int64(0); i < 10 && timeWalkerMs < to; i++ {
  214. table.Rows = append(table.Rows, tsdb.RowValues{float64(timeWalkerMs), "This is a message", "Description", 23.1})
  215. timeWalkerMs += query.IntervalMs
  216. }
  217. queryRes := tsdb.NewQueryResult()
  218. queryRes.Tables = append(queryRes.Tables, &table)
  219. return queryRes
  220. },
  221. })
  222. registerScenario(&Scenario{
  223. Id: "logs",
  224. Name: "Logs",
  225. Handler: func(query *tsdb.Query, context *tsdb.TsdbQuery) *tsdb.QueryResult {
  226. from := context.TimeRange.GetFromAsMsEpoch()
  227. to := context.TimeRange.GetToAsMsEpoch()
  228. lines := query.Model.Get("lines").MustInt64(10)
  229. includeLevelColumn := query.Model.Get("levelColumn").MustBool(false)
  230. logLevelGenerator := newRandomStringProvider([]string{
  231. "emerg",
  232. "alert",
  233. "crit",
  234. "critical",
  235. "warn",
  236. "warning",
  237. "err",
  238. "eror",
  239. "error",
  240. "info",
  241. "notice",
  242. "dbug",
  243. "debug",
  244. "trace",
  245. "",
  246. })
  247. containerIDGenerator := newRandomStringProvider([]string{
  248. "f36a9eaa6d34310686f2b851655212023a216de955cbcc764210cefa71179b1a",
  249. "5a354a630364f3742c602f315132e16def594fe68b1e4a195b2fce628e24c97a",
  250. })
  251. hostnameGenerator := newRandomStringProvider([]string{
  252. "srv-001",
  253. "srv-002",
  254. })
  255. table := tsdb.Table{
  256. Columns: []tsdb.TableColumn{
  257. {Text: "time"},
  258. {Text: "message"},
  259. {Text: "container_id"},
  260. {Text: "hostname"},
  261. },
  262. Rows: []tsdb.RowValues{},
  263. }
  264. if includeLevelColumn {
  265. table.Columns = append(table.Columns, tsdb.TableColumn{Text: "level"})
  266. }
  267. for i := int64(0); i < lines && to > from; i++ {
  268. row := tsdb.RowValues{float64(to)}
  269. logLevel := logLevelGenerator.Next()
  270. timeFormatted := time.Unix(to/1000, 0).Format(time.RFC3339)
  271. lvlString := ""
  272. if !includeLevelColumn {
  273. lvlString = fmt.Sprintf("lvl=%s ", logLevel)
  274. }
  275. 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))
  276. row = append(row, containerIDGenerator.Next())
  277. row = append(row, hostnameGenerator.Next())
  278. if includeLevelColumn {
  279. row = append(row, logLevel)
  280. }
  281. table.Rows = append(table.Rows, row)
  282. to -= query.IntervalMs
  283. }
  284. queryRes := tsdb.NewQueryResult()
  285. queryRes.Tables = append(queryRes.Tables, &table)
  286. return queryRes
  287. },
  288. })
  289. }
  290. // PredictablePulseDesc is the description for the Predictable Pulse scenerio.
  291. const PredictablePulseDesc = `Predictable Pulse returns a pulse wave where there is a datapoint every timeStepSeconds.
  292. The wave cycles at timeStepSeconds*(onCount+offCount).
  293. The cycle of the wave is based off of absolute time (from the epoch) which makes it predictable.
  294. Timestamps will line up evenly on timeStepSeconds (For example, 60 seconds means times will all end in :00 seconds).`
  295. func getPredictablePulse(query *tsdb.Query, context *tsdb.TsdbQuery) *tsdb.QueryResult {
  296. queryRes := tsdb.NewQueryResult()
  297. // Process Input
  298. var timeStep int64
  299. var onCount int64
  300. var offCount int64
  301. var onValue null.Float
  302. var offValue null.Float
  303. options := query.Model.Get("pulseWave")
  304. var err error
  305. if timeStep, err = options.Get("timeStep").Int64(); err != nil {
  306. queryRes.Error = fmt.Errorf("failed to parse timeStep value '%v' into integer: %v", options.Get("timeStep"), err)
  307. return queryRes
  308. }
  309. if onCount, err = options.Get("onCount").Int64(); err != nil {
  310. queryRes.Error = fmt.Errorf("failed to parse onCount value '%v' into integer: %v", options.Get("onCount"), err)
  311. return queryRes
  312. }
  313. if offCount, err = options.Get("offCount").Int64(); err != nil {
  314. queryRes.Error = fmt.Errorf("failed to parse offCount value '%v' into integer: %v", options.Get("offCount"), err)
  315. return queryRes
  316. }
  317. fromStringOrNumber := func(val *simplejson.Json) (null.Float, error) {
  318. switch v := val.Interface().(type) {
  319. case json.Number:
  320. fV, err := v.Float64()
  321. if err != nil {
  322. return null.Float{}, err
  323. }
  324. return null.FloatFrom(fV), nil
  325. case string:
  326. if v == "null" {
  327. return null.FloatFromPtr(nil), nil
  328. }
  329. fV, err := strconv.ParseFloat(v, 64)
  330. if err != nil {
  331. return null.Float{}, err
  332. }
  333. return null.FloatFrom(fV), nil
  334. default:
  335. return null.Float{}, fmt.Errorf("failed to extract value")
  336. }
  337. }
  338. onValue, err = fromStringOrNumber(options.Get("onValue"))
  339. if err != nil {
  340. queryRes.Error = fmt.Errorf("failed to parse onValue value '%v' into float: %v", options.Get("onValue"), err)
  341. return queryRes
  342. }
  343. offValue, err = fromStringOrNumber(options.Get("offValue"))
  344. if err != nil {
  345. queryRes.Error = fmt.Errorf("failed to parse offValue value '%v' into float: %v", options.Get("offValue"), err)
  346. return queryRes
  347. }
  348. from := context.TimeRange.GetFromAsMsEpoch()
  349. to := context.TimeRange.GetToAsMsEpoch()
  350. series := newSeriesForQuery(query)
  351. points := make(tsdb.TimeSeriesPoints, 0)
  352. timeStep = timeStep * 1000 // Seconds to Milliseconds
  353. timeCursor := from - (from % timeStep) // Truncate Start
  354. wavePeriod := timeStep * (onCount + offCount)
  355. maxPoints := 10000 // Don't return too many points
  356. onFor := func(mod int64) null.Float { // How many items in the cycle should get the on value
  357. var i int64
  358. for i = 0; i < onCount; i++ {
  359. if mod == i*timeStep {
  360. return onValue
  361. }
  362. }
  363. return offValue
  364. }
  365. for i := 0; i < maxPoints && timeCursor < to; i++ {
  366. point := tsdb.NewTimePoint(onFor(timeCursor%wavePeriod), float64(timeCursor))
  367. points = append(points, point)
  368. timeCursor += timeStep
  369. }
  370. series.Points = points
  371. queryRes.Series = append(queryRes.Series, series)
  372. return queryRes
  373. }
  374. func getRandomWalk(query *tsdb.Query, tsdbQuery *tsdb.TsdbQuery) *tsdb.QueryResult {
  375. timeWalkerMs := tsdbQuery.TimeRange.GetFromAsMsEpoch()
  376. to := tsdbQuery.TimeRange.GetToAsMsEpoch()
  377. series := newSeriesForQuery(query)
  378. points := make(tsdb.TimeSeriesPoints, 0)
  379. walker := query.Model.Get("startValue").MustFloat64(rand.Float64() * 100)
  380. for i := int64(0); i < 10000 && timeWalkerMs < to; i++ {
  381. points = append(points, tsdb.NewTimePoint(null.FloatFrom(walker), float64(timeWalkerMs)))
  382. walker += rand.Float64() - 0.5
  383. timeWalkerMs += query.IntervalMs
  384. }
  385. series.Points = points
  386. queryRes := tsdb.NewQueryResult()
  387. queryRes.Series = append(queryRes.Series, series)
  388. return queryRes
  389. }
  390. func getRandomWalkTable(query *tsdb.Query, tsdbQuery *tsdb.TsdbQuery) *tsdb.QueryResult {
  391. timeWalkerMs := tsdbQuery.TimeRange.GetFromAsMsEpoch()
  392. to := tsdbQuery.TimeRange.GetToAsMsEpoch()
  393. table := tsdb.Table{
  394. Columns: []tsdb.TableColumn{
  395. {Text: "Time"},
  396. {Text: "Value"},
  397. {Text: "Min"},
  398. {Text: "Max"},
  399. {Text: "Info"},
  400. },
  401. Rows: []tsdb.RowValues{},
  402. }
  403. withNil := query.Model.Get("withNil").MustBool(false)
  404. walker := query.Model.Get("startValue").MustFloat64(rand.Float64() * 100)
  405. spread := 2.5
  406. var info strings.Builder
  407. for i := int64(0); i < query.MaxDataPoints && timeWalkerMs < to; i++ {
  408. delta := rand.Float64() - 0.5
  409. walker += delta
  410. info.Reset()
  411. if delta > 0 {
  412. info.WriteString("up")
  413. } else {
  414. info.WriteString("down")
  415. }
  416. if math.Abs(delta) > .4 {
  417. info.WriteString(" fast")
  418. }
  419. row := tsdb.RowValues{
  420. float64(timeWalkerMs),
  421. walker,
  422. walker - ((rand.Float64() * spread) + 0.01), // Min
  423. walker + ((rand.Float64() * spread) + 0.01), // Max
  424. info.String(),
  425. }
  426. // Add some random null values
  427. if withNil && rand.Float64() > 0.8 {
  428. for i := 1; i < 4; i++ {
  429. if rand.Float64() > .2 {
  430. row[i] = nil
  431. }
  432. }
  433. }
  434. table.Rows = append(table.Rows, row)
  435. timeWalkerMs += query.IntervalMs
  436. }
  437. queryRes := tsdb.NewQueryResult()
  438. queryRes.Tables = append(queryRes.Tables, &table)
  439. return queryRes
  440. }
  441. func registerScenario(scenario *Scenario) {
  442. ScenarioRegistry[scenario.Id] = scenario
  443. }
  444. func newSeriesForQuery(query *tsdb.Query) *tsdb.TimeSeries {
  445. alias := query.Model.Get("alias").MustString("")
  446. if alias == "" {
  447. alias = query.RefId + "-series"
  448. }
  449. return &tsdb.TimeSeries{Name: alias}
  450. }