@intentius/chant-lexicon-grafana 0.98.0 → 0.100.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (532) hide show
  1. package/README.md +24 -8
  2. package/dist/actions/grafana.d.ts +24 -0
  3. package/dist/actions/grafana.d.ts.map +1 -0
  4. package/dist/actions/index.d.ts +2 -0
  5. package/dist/actions/index.d.ts.map +1 -0
  6. package/dist/alerting-build.d.ts +128 -0
  7. package/dist/alerting-build.d.ts.map +1 -0
  8. package/dist/alerting.d.ts +273 -0
  9. package/dist/alerting.d.ts.map +1 -0
  10. package/dist/annotations.d.ts +61 -0
  11. package/dist/annotations.d.ts.map +1 -0
  12. package/dist/api/apply.d.ts +160 -0
  13. package/dist/api/apply.d.ts.map +1 -0
  14. package/dist/api/bind.d.ts +32 -0
  15. package/dist/api/bind.d.ts.map +1 -0
  16. package/dist/api/client.d.ts +76 -0
  17. package/dist/api/client.d.ts.map +1 -0
  18. package/dist/api/converge.d.ts +33 -0
  19. package/dist/api/converge.d.ts.map +1 -0
  20. package/dist/api/dashboards.d.ts +102 -0
  21. package/dist/api/dashboards.d.ts.map +1 -0
  22. package/dist/api/datasources.d.ts +26 -0
  23. package/dist/api/datasources.d.ts.map +1 -0
  24. package/dist/api/fake-grafana-writes.d.ts +41 -0
  25. package/dist/api/fake-grafana-writes.d.ts.map +1 -0
  26. package/dist/api/fake-grafana.d.ts +63 -0
  27. package/dist/api/fake-grafana.d.ts.map +1 -0
  28. package/dist/api/folders.d.ts +146 -0
  29. package/dist/api/folders.d.ts.map +1 -0
  30. package/dist/api/library-panels.d.ts +52 -0
  31. package/dist/api/library-panels.d.ts.map +1 -0
  32. package/dist/build.d.ts +55 -6
  33. package/dist/build.d.ts.map +1 -1
  34. package/dist/catalog.d.ts +1 -1
  35. package/dist/catalog.d.ts.map +1 -1
  36. package/dist/codegen/docs.d.ts.map +1 -1
  37. package/dist/codegen/generate.d.ts +7 -1
  38. package/dist/codegen/generate.d.ts.map +1 -1
  39. package/dist/codegen/schema-types.d.ts +3 -3
  40. package/dist/codegen/schema-types.d.ts.map +1 -1
  41. package/dist/composites/agent-dashboard.d.ts.map +1 -1
  42. package/dist/composites/catalog.d.ts.map +1 -1
  43. package/dist/composites/index.d.ts +4 -2
  44. package/dist/composites/index.d.ts.map +1 -1
  45. package/dist/composites/red-dashboard.d.ts +21 -2
  46. package/dist/composites/red-dashboard.d.ts.map +1 -1
  47. package/dist/composites/shared.d.ts +11 -3
  48. package/dist/composites/shared.d.ts.map +1 -1
  49. package/dist/composites/slo-alert-rules.d.ts +92 -0
  50. package/dist/composites/slo-alert-rules.d.ts.map +1 -0
  51. package/dist/config.d.ts +111 -0
  52. package/dist/config.d.ts.map +1 -0
  53. package/dist/contact-point-secrets.d.ts +17 -0
  54. package/dist/contact-point-secrets.d.ts.map +1 -0
  55. package/dist/dashboard.d.ts +51 -9
  56. package/dist/dashboard.d.ts.map +1 -1
  57. package/dist/datasource-refs.d.ts +99 -0
  58. package/dist/datasource-refs.d.ts.map +1 -0
  59. package/dist/datasource-settings.d.ts +834 -0
  60. package/dist/datasource-settings.d.ts.map +1 -0
  61. package/dist/datasource.d.ts +82 -11
  62. package/dist/datasource.d.ts.map +1 -1
  63. package/dist/deep-observe-hooks.d.ts +64 -0
  64. package/dist/deep-observe-hooks.d.ts.map +1 -0
  65. package/dist/deep-observe.d.ts +47 -0
  66. package/dist/deep-observe.d.ts.map +1 -0
  67. package/dist/describe-resources.d.ts +50 -0
  68. package/dist/describe-resources.d.ts.map +1 -0
  69. package/dist/detect.d.ts +32 -3
  70. package/dist/detect.d.ts.map +1 -1
  71. package/dist/export-resources.d.ts +38 -0
  72. package/dist/export-resources.d.ts.map +1 -0
  73. package/dist/folder.d.ts +68 -0
  74. package/dist/folder.d.ts.map +1 -0
  75. package/dist/import/alerting.d.ts +40 -0
  76. package/dist/import/alerting.d.ts.map +1 -0
  77. package/dist/import/edits.d.ts +47 -0
  78. package/dist/import/edits.d.ts.map +1 -0
  79. package/dist/import/embedded.d.ts +14 -0
  80. package/dist/import/embedded.d.ts.map +1 -0
  81. package/dist/import/generator.d.ts +60 -0
  82. package/dist/import/generator.d.ts.map +1 -0
  83. package/dist/import/live-export.d.ts +108 -0
  84. package/dist/import/live-export.d.ts.map +1 -0
  85. package/dist/import/mappings.d.ts +89 -0
  86. package/dist/import/mappings.d.ts.map +1 -0
  87. package/dist/import/model.d.ts +105 -0
  88. package/dist/import/model.d.ts.map +1 -0
  89. package/dist/import/normalize-alerting.d.ts +26 -0
  90. package/dist/import/normalize-alerting.d.ts.map +1 -0
  91. package/dist/import/normalize.d.ts +74 -0
  92. package/dist/import/normalize.d.ts.map +1 -0
  93. package/dist/import/parser.d.ts +125 -0
  94. package/dist/import/parser.d.ts.map +1 -0
  95. package/dist/import/report.d.ts +26 -0
  96. package/dist/import/report.d.ts.map +1 -0
  97. package/dist/import/testdata/fixtures.d.ts +41 -0
  98. package/dist/import/testdata/fixtures.d.ts.map +1 -0
  99. package/dist/import/v2.d.ts +73 -0
  100. package/dist/import/v2.d.ts.map +1 -0
  101. package/dist/index.d.ts +31 -12
  102. package/dist/index.d.ts.map +1 -1
  103. package/dist/init-templates.d.ts +27 -0
  104. package/dist/init-templates.d.ts.map +1 -0
  105. package/dist/integrity.json +22 -12
  106. package/dist/k8s.d.ts +107 -0
  107. package/dist/k8s.d.ts.map +1 -0
  108. package/dist/lint/audit-catalog.d.ts +2 -1
  109. package/dist/lint/audit-catalog.d.ts.map +1 -1
  110. package/dist/lint/post-synth/graf101.d.ts +2 -2
  111. package/dist/lint/post-synth/graf102.d.ts +1 -1
  112. package/dist/lint/post-synth/graf104.d.ts +2 -2
  113. package/dist/lint/post-synth/graf106.d.ts +2 -2
  114. package/dist/lint/post-synth/graf107.d.ts +1 -1
  115. package/dist/lint/post-synth/graf108.d.ts +8 -0
  116. package/dist/lint/post-synth/graf108.d.ts.map +1 -0
  117. package/dist/lint/post-synth/graf109.d.ts +8 -0
  118. package/dist/lint/post-synth/graf109.d.ts.map +1 -0
  119. package/dist/lint/post-synth/graf110.d.ts +8 -0
  120. package/dist/lint/post-synth/graf110.d.ts.map +1 -0
  121. package/dist/lint/post-synth/graf111.d.ts +8 -0
  122. package/dist/lint/post-synth/graf111.d.ts.map +1 -0
  123. package/dist/lint/post-synth/graf112.d.ts +8 -0
  124. package/dist/lint/post-synth/graf112.d.ts.map +1 -0
  125. package/dist/lint/post-synth/graf113.d.ts +8 -0
  126. package/dist/lint/post-synth/graf113.d.ts.map +1 -0
  127. package/dist/lint/post-synth/graf114.d.ts +8 -0
  128. package/dist/lint/post-synth/graf114.d.ts.map +1 -0
  129. package/dist/lint/post-synth/graf115.d.ts +8 -0
  130. package/dist/lint/post-synth/graf115.d.ts.map +1 -0
  131. package/dist/lint/post-synth/grafana-helpers.d.ts +7 -5
  132. package/dist/lint/post-synth/grafana-helpers.d.ts.map +1 -1
  133. package/dist/lint/post-synth/index.d.ts.map +1 -1
  134. package/dist/lint/rules/literal-secret.d.ts +7 -3
  135. package/dist/lint/rules/literal-secret.d.ts.map +1 -1
  136. package/dist/lsp/hover.d.ts.map +1 -1
  137. package/dist/manifest.json +1 -1
  138. package/dist/members.d.ts +27 -0
  139. package/dist/members.d.ts.map +1 -0
  140. package/dist/meta.json +205 -0
  141. package/dist/okf/index.md +53 -4
  142. package/dist/okf/rules/GRAF002.md +3 -2
  143. package/dist/okf/rules/GRAF101.md +3 -2
  144. package/dist/okf/rules/GRAF102.md +1 -0
  145. package/dist/okf/rules/GRAF104.md +2 -2
  146. package/dist/okf/rules/GRAF106.md +2 -2
  147. package/dist/okf/rules/GRAF108.md +11 -0
  148. package/dist/okf/rules/GRAF109.md +15 -0
  149. package/dist/okf/rules/GRAF110.md +11 -0
  150. package/dist/okf/rules/GRAF111.md +11 -0
  151. package/dist/okf/rules/GRAF112.md +16 -0
  152. package/dist/okf/rules/GRAF113.md +16 -0
  153. package/dist/okf/rules/GRAF114.md +11 -0
  154. package/dist/okf/rules/GRAF115.md +11 -0
  155. package/dist/okf/types/AdhocVariable.md +9 -0
  156. package/dist/okf/types/AlertListPanel.md +9 -0
  157. package/dist/okf/types/AlertQuery.md +9 -0
  158. package/dist/okf/types/AlertRule.md +9 -0
  159. package/dist/okf/types/AlertRuleGroup.md +9 -0
  160. package/dist/okf/types/AzureMonitorQuery.md +9 -0
  161. package/dist/okf/types/BarChartPanel.md +9 -0
  162. package/dist/okf/types/BarGaugePanel.md +9 -0
  163. package/dist/okf/types/BigQueryQuery.md +9 -0
  164. package/dist/okf/types/CanvasPanel.md +9 -0
  165. package/dist/okf/types/ClassicConditionsExpression.md +9 -0
  166. package/dist/okf/types/CloudMonitoringQuery.md +9 -0
  167. package/dist/okf/types/CloudWatchQuery.md +9 -0
  168. package/dist/okf/types/ContactPoint.md +14 -0
  169. package/dist/okf/types/Dashboard.md +2 -1
  170. package/dist/okf/types/Datasource.md +3 -2
  171. package/dist/okf/types/DatasourceProvisioning.md +9 -0
  172. package/dist/okf/types/ElasticsearchQuery.md +9 -0
  173. package/dist/okf/types/ExternalDatasource.md +15 -0
  174. package/dist/okf/types/FlameGraphPanel.md +9 -0
  175. package/dist/okf/types/Folder.md +9 -0
  176. package/dist/okf/types/GeomapPanel.md +9 -0
  177. package/dist/okf/types/GroupByVariable.md +9 -0
  178. package/dist/okf/types/HistogramPanel.md +9 -0
  179. package/dist/okf/types/MSSQLQuery.md +9 -0
  180. package/dist/okf/types/MathExpression.md +9 -0
  181. package/dist/okf/types/MuteTiming.md +13 -0
  182. package/dist/okf/types/MySQLQuery.md +9 -0
  183. package/dist/okf/types/NodeGraphPanel.md +9 -0
  184. package/dist/okf/types/NotificationPolicy.md +9 -0
  185. package/dist/okf/types/NotificationTemplate.md +9 -0
  186. package/dist/okf/types/PieChartPanel.md +9 -0
  187. package/dist/okf/types/PostgresQuery.md +9 -0
  188. package/dist/okf/types/PyroscopeQuery.md +9 -0
  189. package/dist/okf/types/ReduceExpression.md +9 -0
  190. package/dist/okf/types/ResampleExpression.md +9 -0
  191. package/dist/okf/types/SqlExpression.md +9 -0
  192. package/dist/okf/types/StateTimelinePanel.md +9 -0
  193. package/dist/okf/types/StatusHistoryPanel.md +9 -0
  194. package/dist/okf/types/SwitchVariable.md +9 -0
  195. package/dist/okf/types/ThresholdExpression.md +9 -0
  196. package/dist/okf/types/TrendPanel.md +9 -0
  197. package/dist/okf/types/XYChartPanel.md +9 -0
  198. package/dist/op/activities/grafana-apply.d.ts +86 -0
  199. package/dist/op/activities/grafana-apply.d.ts.map +1 -0
  200. package/dist/op/activities/index.d.ts +9 -0
  201. package/dist/op/activities/index.d.ts.map +1 -0
  202. package/dist/op/builders.d.ts +24 -0
  203. package/dist/op/builders.d.ts.map +1 -0
  204. package/dist/ownership.d.ts +78 -0
  205. package/dist/ownership.d.ts.map +1 -0
  206. package/dist/panel-options.d.ts +65 -0
  207. package/dist/panel-options.d.ts.map +1 -0
  208. package/dist/panels.d.ts +46 -9
  209. package/dist/panels.d.ts.map +1 -1
  210. package/dist/pin.d.ts +29 -7
  211. package/dist/pin.d.ts.map +1 -1
  212. package/dist/plugin.d.ts.map +1 -1
  213. package/dist/promql-check.d.ts +51 -0
  214. package/dist/promql-check.d.ts.map +1 -0
  215. package/dist/query-models.d.ts +95 -0
  216. package/dist/query-models.d.ts.map +1 -0
  217. package/dist/query.d.ts +116 -18
  218. package/dist/query.d.ts.map +1 -1
  219. package/dist/rules/graf101.ts +3 -3
  220. package/dist/rules/graf102.ts +1 -1
  221. package/dist/rules/graf104.ts +3 -3
  222. package/dist/rules/graf106.ts +3 -3
  223. package/dist/rules/graf107.ts +1 -1
  224. package/dist/rules/graf108.ts +17 -0
  225. package/dist/rules/graf109.ts +17 -0
  226. package/dist/rules/graf110.ts +17 -0
  227. package/dist/rules/graf111.ts +17 -0
  228. package/dist/rules/graf112.ts +17 -0
  229. package/dist/rules/graf113.ts +17 -0
  230. package/dist/rules/graf114.ts +17 -0
  231. package/dist/rules/graf115.ts +17 -0
  232. package/dist/rules/grafana-helpers.ts +27 -10
  233. package/dist/rules/literal-secret.ts +60 -6
  234. package/dist/schema/azuremonitor.gen.d.ts +354 -0
  235. package/dist/schema/azuremonitor.gen.d.ts.map +1 -0
  236. package/dist/schema/barchart.gen.d.ts +198 -0
  237. package/dist/schema/barchart.gen.d.ts.map +1 -0
  238. package/dist/schema/bargauge.gen.d.ts +97 -0
  239. package/dist/schema/bargauge.gen.d.ts.map +1 -0
  240. package/dist/schema/bigquery.gen.d.ts +93 -0
  241. package/dist/schema/bigquery.gen.d.ts.map +1 -0
  242. package/dist/schema/canvas.gen.d.ts +152 -0
  243. package/dist/schema/canvas.gen.d.ts.map +1 -0
  244. package/dist/schema/cloudwatch.gen.d.ts +394 -0
  245. package/dist/schema/cloudwatch.gen.d.ts.map +1 -0
  246. package/dist/schema/dashboard.gen.d.ts +196 -21
  247. package/dist/schema/dashboard.gen.d.ts.map +1 -1
  248. package/dist/schema/elasticsearch.gen.d.ts +444 -0
  249. package/dist/schema/elasticsearch.gen.d.ts.map +1 -0
  250. package/dist/schema/expr.gen.d.ts +606 -0
  251. package/dist/schema/expr.gen.d.ts.map +1 -0
  252. package/dist/schema/gauge.gen.d.ts.map +1 -1
  253. package/dist/schema/geomap.gen.d.ts +100 -0
  254. package/dist/schema/geomap.gen.d.ts.map +1 -0
  255. package/dist/schema/googlecloudmonitoring.gen.d.ts +312 -0
  256. package/dist/schema/googlecloudmonitoring.gen.d.ts.map +1 -0
  257. package/dist/schema/grafanapyroscope.gen.d.ts +60 -0
  258. package/dist/schema/grafanapyroscope.gen.d.ts.map +1 -0
  259. package/dist/schema/heatmap.gen.d.ts.map +1 -1
  260. package/dist/schema/histogram.gen.d.ts +132 -0
  261. package/dist/schema/histogram.gen.d.ts.map +1 -0
  262. package/dist/schema/index.d.ts +18 -0
  263. package/dist/schema/index.d.ts.map +1 -1
  264. package/dist/schema/logs.gen.d.ts +1 -0
  265. package/dist/schema/logs.gen.d.ts.map +1 -1
  266. package/dist/schema/loki.gen.d.ts.map +1 -1
  267. package/dist/schema/nodegraph.gen.d.ts +49 -0
  268. package/dist/schema/nodegraph.gen.d.ts.map +1 -0
  269. package/dist/schema/piechart.gen.d.ts +127 -0
  270. package/dist/schema/piechart.gen.d.ts.map +1 -0
  271. package/dist/schema/prometheus.gen.d.ts.map +1 -1
  272. package/dist/schema/stat.gen.d.ts.map +1 -1
  273. package/dist/schema/statetimeline.gen.d.ts +124 -0
  274. package/dist/schema/statetimeline.gen.d.ts.map +1 -0
  275. package/dist/schema/statushistory.gen.d.ts +114 -0
  276. package/dist/schema/statushistory.gen.d.ts.map +1 -0
  277. package/dist/schema/table.gen.d.ts +35 -3
  278. package/dist/schema/table.gen.d.ts.map +1 -1
  279. package/dist/schema/tempo.gen.d.ts.map +1 -1
  280. package/dist/schema/text.gen.d.ts.map +1 -1
  281. package/dist/schema/timeseries.gen.d.ts +5 -1
  282. package/dist/schema/timeseries.gen.d.ts.map +1 -1
  283. package/dist/schema/trend.gen.d.ts +180 -0
  284. package/dist/schema/trend.gen.d.ts.map +1 -0
  285. package/dist/schema/xychart.gen.d.ts +145 -0
  286. package/dist/schema/xychart.gen.d.ts.map +1 -0
  287. package/dist/schema-validate.d.ts +39 -4
  288. package/dist/schema-validate.d.ts.map +1 -1
  289. package/dist/serializer.d.ts +11 -3
  290. package/dist/serializer.d.ts.map +1 -1
  291. package/dist/skill-defs.d.ts.map +1 -1
  292. package/dist/skills/chant-grafana-alerting.md +49 -0
  293. package/dist/skills/chant-grafana-operations.md +108 -0
  294. package/dist/skills/chant-grafana-provisioning.md +3 -3
  295. package/dist/skills/chant-grafana.md +15 -8
  296. package/dist/spec/fetch-units-cli.d.ts +3 -0
  297. package/dist/spec/fetch-units-cli.d.ts.map +1 -0
  298. package/dist/spec/overlay.d.ts +43 -0
  299. package/dist/spec/overlay.d.ts.map +1 -0
  300. package/dist/spec/schemas.d.ts +21 -13
  301. package/dist/spec/schemas.d.ts.map +1 -1
  302. package/dist/spec/schemas.gen.d.ts +3 -0
  303. package/dist/spec/schemas.gen.d.ts.map +1 -0
  304. package/dist/spec/units.d.ts +35 -0
  305. package/dist/spec/units.d.ts.map +1 -0
  306. package/dist/spec/units.gen.d.ts +2 -0
  307. package/dist/spec/units.gen.d.ts.map +1 -0
  308. package/dist/transformations.d.ts +459 -0
  309. package/dist/transformations.d.ts.map +1 -0
  310. package/dist/util.d.ts +6 -0
  311. package/dist/util.d.ts.map +1 -1
  312. package/dist/validate-alerting.d.ts +65 -0
  313. package/dist/validate-alerting.d.ts.map +1 -0
  314. package/dist/validate-output.d.ts +62 -9
  315. package/dist/validate-output.d.ts.map +1 -1
  316. package/dist/validate.d.ts.map +1 -1
  317. package/dist/validation.d.ts +14 -0
  318. package/dist/validation.d.ts.map +1 -0
  319. package/dist/variables.d.ts +109 -7
  320. package/dist/variables.d.ts.map +1 -1
  321. package/package.json +12 -4
  322. package/src/actions/actions.test.ts +24 -0
  323. package/src/actions/grafana.ts +40 -0
  324. package/src/actions/index.ts +1 -0
  325. package/src/alerting-build.ts +376 -0
  326. package/src/alerting.e2e.test.ts +212 -0
  327. package/src/alerting.test.ts +272 -0
  328. package/src/alerting.ts +435 -0
  329. package/src/annotations.ts +84 -0
  330. package/src/api/apply.test.ts +256 -0
  331. package/src/api/apply.ts +394 -0
  332. package/src/api/bind.ts +58 -0
  333. package/src/api/client.ts +148 -0
  334. package/src/api/converge.ts +63 -0
  335. package/src/api/dashboards.ts +247 -0
  336. package/src/api/datasources.ts +33 -0
  337. package/src/api/fake-grafana-writes.ts +201 -0
  338. package/src/api/fake-grafana.ts +190 -0
  339. package/src/api/folders.ts +339 -0
  340. package/src/api/library-panels.ts +101 -0
  341. package/src/build.test.ts +351 -10
  342. package/src/build.ts +294 -55
  343. package/src/catalog.ts +104 -2
  344. package/src/codegen/docs.ts +50 -10
  345. package/src/codegen/generate.ts +27 -1
  346. package/src/codegen/schema-types.ts +16 -6
  347. package/src/composites/agent-dashboard.ts +6 -3
  348. package/src/composites/catalog.test.ts +2 -2
  349. package/src/composites/catalog.ts +82 -0
  350. package/src/composites/composites.test.ts +73 -9
  351. package/src/composites/index.ts +4 -2
  352. package/src/composites/queries.e2e.test.ts +374 -0
  353. package/src/composites/red-dashboard.ts +83 -12
  354. package/src/composites/shared.ts +14 -2
  355. package/src/composites/slo-alert-rules.ts +149 -0
  356. package/src/config.test.ts +61 -0
  357. package/src/config.ts +138 -0
  358. package/src/contact-point-secrets.ts +63 -0
  359. package/src/dashboard.ts +68 -9
  360. package/src/datasource-refs.test.ts +56 -0
  361. package/src/datasource-refs.ts +213 -0
  362. package/src/datasource-settings.test.ts +106 -0
  363. package/src/datasource-settings.ts +941 -0
  364. package/src/datasource.ts +123 -11
  365. package/src/deep-observe-hooks.ts +267 -0
  366. package/src/deep-observe.test.ts +311 -0
  367. package/src/deep-observe.ts +187 -0
  368. package/src/describe-resources.test.ts +271 -0
  369. package/src/describe-resources.ts +244 -0
  370. package/src/detect.ts +82 -5
  371. package/src/export-resources.test.ts +129 -0
  372. package/src/export-resources.ts +84 -0
  373. package/src/folder.ts +118 -0
  374. package/src/generated/lexicon-grafana.json +205 -0
  375. package/src/import/alerting-roundtrip.test.ts +161 -0
  376. package/src/import/alerting.ts +500 -0
  377. package/src/import/edits.ts +95 -0
  378. package/src/import/embedded.test.ts +45 -0
  379. package/src/import/embedded.ts +47 -0
  380. package/src/import/generated-types.e2e.test.ts +130 -0
  381. package/src/import/generator.test.ts +210 -0
  382. package/src/import/generator.ts +463 -0
  383. package/src/import/live-export.ts +264 -0
  384. package/src/import/mappings.ts +336 -0
  385. package/src/import/model.ts +117 -0
  386. package/src/import/normalize-alerting.ts +101 -0
  387. package/src/import/normalize.test.ts +117 -0
  388. package/src/import/normalize.ts +393 -0
  389. package/src/import/parser.test.ts +516 -0
  390. package/src/import/parser.ts +1387 -0
  391. package/src/import/report.ts +62 -0
  392. package/src/import/roundtrip.test.ts +563 -0
  393. package/src/import/testdata/fixtures.ts +137 -0
  394. package/src/import/v2.test.ts +292 -0
  395. package/src/import/v2.ts +804 -0
  396. package/src/import.e2e.test.ts +31 -84
  397. package/src/import.test.ts +9 -3
  398. package/src/index.ts +308 -14
  399. package/src/init-templates.test.ts +74 -0
  400. package/src/init-templates.ts +349 -0
  401. package/src/k8s.test.ts +98 -0
  402. package/src/k8s.ts +197 -0
  403. package/src/lint/audit-catalog.ts +68 -3
  404. package/src/lint/core-lint.test.ts +43 -0
  405. package/src/lint/post-synth/graf101.ts +3 -3
  406. package/src/lint/post-synth/graf102.ts +1 -1
  407. package/src/lint/post-synth/graf104.ts +3 -3
  408. package/src/lint/post-synth/graf106.ts +3 -3
  409. package/src/lint/post-synth/graf107.ts +1 -1
  410. package/src/lint/post-synth/graf108.ts +17 -0
  411. package/src/lint/post-synth/graf109.ts +17 -0
  412. package/src/lint/post-synth/graf110.ts +17 -0
  413. package/src/lint/post-synth/graf111.ts +17 -0
  414. package/src/lint/post-synth/graf112.ts +17 -0
  415. package/src/lint/post-synth/graf113.ts +17 -0
  416. package/src/lint/post-synth/graf114.ts +17 -0
  417. package/src/lint/post-synth/graf115.ts +17 -0
  418. package/src/lint/post-synth/grafana-helpers.ts +27 -10
  419. package/src/lint/post-synth/index.ts +16 -0
  420. package/src/lint/post-synth/post-synth.test.ts +333 -12
  421. package/src/lint/rules/literal-secret.ts +60 -6
  422. package/src/lint/rules/rules.test.ts +27 -0
  423. package/src/load-cost.test.ts +35 -0
  424. package/src/lsp/completions.test.ts +5 -0
  425. package/src/lsp/hover.test.ts +7 -0
  426. package/src/lsp/hover.ts +8 -1
  427. package/src/members.ts +68 -0
  428. package/src/observe.e2e.test.ts +255 -0
  429. package/src/op/activities/grafana-apply.e2e.test.ts +184 -0
  430. package/src/op/activities/grafana-apply.test.ts +243 -0
  431. package/src/op/activities/grafana-apply.ts +197 -0
  432. package/src/op/activities/index.ts +8 -0
  433. package/src/op/builders.test.ts +40 -0
  434. package/src/op/builders.ts +34 -0
  435. package/src/ownership.ts +122 -0
  436. package/src/panel-options.ts +66 -0
  437. package/src/panels.test.ts +53 -0
  438. package/src/panels.ts +148 -10
  439. package/src/pin.ts +69 -6
  440. package/src/plugin.test.ts +12 -2
  441. package/src/plugin.ts +75 -0
  442. package/src/promql-check.test.ts +108 -0
  443. package/src/promql-check.ts +193 -0
  444. package/src/query-models.ts +94 -0
  445. package/src/query.test.ts +166 -0
  446. package/src/query.ts +190 -20
  447. package/src/schema/azuremonitor.gen.ts +381 -0
  448. package/src/schema/barchart.gen.ts +221 -0
  449. package/src/schema/bargauge.gen.ts +111 -0
  450. package/src/schema/bigquery.gen.ts +110 -0
  451. package/src/schema/canvas.gen.ts +176 -0
  452. package/src/schema/cloudwatch.gen.ts +423 -0
  453. package/src/schema/dashboard.gen.ts +206 -22
  454. package/src/schema/elasticsearch.gen.ts +508 -0
  455. package/src/schema/expr.gen.ts +618 -0
  456. package/src/schema/gauge.gen.ts +2 -1
  457. package/src/schema/geomap.gen.ts +112 -0
  458. package/src/schema/googlecloudmonitoring.gen.ts +330 -0
  459. package/src/schema/grafanapyroscope.gen.ts +66 -0
  460. package/src/schema/heatmap.gen.ts +2 -1
  461. package/src/schema/histogram.gen.ts +151 -0
  462. package/src/schema/index.ts +18 -0
  463. package/src/schema/logs.gen.ts +3 -1
  464. package/src/schema/loki.gen.ts +2 -1
  465. package/src/schema/nodegraph.gen.ts +58 -0
  466. package/src/schema/piechart.gen.ts +146 -0
  467. package/src/schema/prometheus.gen.ts +2 -1
  468. package/src/schema/stat.gen.ts +2 -1
  469. package/src/schema/statetimeline.gen.ts +143 -0
  470. package/src/schema/statushistory.gen.ts +132 -0
  471. package/src/schema/table.gen.ts +41 -4
  472. package/src/schema/tempo.gen.ts +2 -1
  473. package/src/schema/text.gen.ts +2 -1
  474. package/src/schema/timeseries.gen.ts +7 -2
  475. package/src/schema/trend.gen.ts +208 -0
  476. package/src/schema/xychart.gen.ts +167 -0
  477. package/src/schema-overlay.test.ts +282 -0
  478. package/src/schema-validate.ts +199 -57
  479. package/src/schema.test.ts +4 -2
  480. package/src/serializer.test.ts +13 -1
  481. package/src/serializer.ts +11 -3
  482. package/src/skill-defs.ts +34 -0
  483. package/src/skills/chant-grafana-alerting.md +49 -0
  484. package/src/skills/chant-grafana-operations.md +108 -0
  485. package/src/skills/chant-grafana-provisioning.md +3 -3
  486. package/src/skills/chant-grafana.md +15 -8
  487. package/src/spec/fetch-cli.ts +3 -3
  488. package/src/spec/fetch-units-cli.ts +37 -0
  489. package/src/spec/overlay/canvas.overlay.json +142 -0
  490. package/src/spec/overlay/dashboard.overlay.json +378 -0
  491. package/src/spec/overlay/expr.overlay.json +14 -0
  492. package/src/spec/overlay/geomap.overlay.json +35 -0
  493. package/src/spec/overlay/logs.overlay.json +14 -0
  494. package/src/spec/overlay/nodegraph.overlay.json +31 -0
  495. package/src/spec/overlay/piechart.overlay.json +17 -0
  496. package/src/spec/overlay/table.overlay.json +103 -0
  497. package/src/spec/overlay/timeseries.overlay.json +21 -0
  498. package/src/spec/overlay/trend.overlay.json +17 -0
  499. package/src/spec/overlay/xychart.overlay.json +16 -0
  500. package/src/spec/overlay.ts +105 -0
  501. package/src/spec/schemas/azuremonitor.jsonschema.json +679 -0
  502. package/src/spec/schemas/barchart.jsonschema.json +419 -0
  503. package/src/spec/schemas/bargauge.jsonschema.json +224 -0
  504. package/src/spec/schemas/bigquery.jsonschema.json +290 -0
  505. package/src/spec/schemas/canvas.jsonschema.json +356 -0
  506. package/src/spec/schemas/cloudwatch.jsonschema.json +721 -0
  507. package/src/spec/schemas/dashboardv2.jsonschema.json +3387 -0
  508. package/src/spec/schemas/elasticsearch.jsonschema.json +1695 -0
  509. package/src/spec/schemas/expr.jsonschema.json +987 -0
  510. package/src/spec/schemas/geomap.jsonschema.json +220 -0
  511. package/src/spec/schemas/googlecloudmonitoring.jsonschema.json +488 -0
  512. package/src/spec/schemas/grafanapyroscope.jsonschema.json +93 -0
  513. package/src/spec/schemas/histogram.jsonschema.json +308 -0
  514. package/src/spec/schemas/nodegraph.jsonschema.json +77 -0
  515. package/src/spec/schemas/piechart.jsonschema.json +275 -0
  516. package/src/spec/schemas/statetimeline.jsonschema.json +324 -0
  517. package/src/spec/schemas/statushistory.jsonschema.json +292 -0
  518. package/src/spec/schemas/trend.jsonschema.json +449 -0
  519. package/src/spec/schemas/xychart.jsonschema.json +428 -0
  520. package/src/spec/schemas.gen.ts +40 -0
  521. package/src/spec/schemas.ts +30 -16
  522. package/src/spec/units.gen.ts +281 -0
  523. package/src/spec/units.ts +77 -0
  524. package/src/transformations.test.ts +65 -0
  525. package/src/transformations.ts +574 -0
  526. package/src/util.ts +8 -0
  527. package/src/validate-alerting.test.ts +255 -0
  528. package/src/validate-alerting.ts +430 -0
  529. package/src/validate-output.ts +427 -104
  530. package/src/validate.ts +28 -0
  531. package/src/validation.ts +27 -0
  532. package/src/variables.ts +120 -8
@@ -0,0 +1,374 @@
1
+ /**
2
+ * The composites' PromQL against real series, through Grafana.
3
+ *
4
+ * Builds the dashboards-from-declarations example (RedDashboard,
5
+ * SloDashboard and AgentDashboard over its spanmetrics connector, its Slo
6
+ * and the GenAI preset), backfills a Prometheus with series named the way
7
+ * those declarations name them, provisions the build into each Grafana
8
+ * release in `GRAFANA_IMAGES` on the same Docker network, and runs every
9
+ * panel's target through `/api/ds/query` the way the dashboard would, with
10
+ * each variable at its "All" value.
11
+ *
12
+ * The series grow at constant rates, so the expected values are exact:
13
+ *
14
+ * - checkout: server spans at 1/s OK and 0.1/s errors, plus client spans
15
+ * erroring at 10/s. Rate 1.1, error ratio 0.1/1.1; the client errors
16
+ * are not counted.
17
+ * - cart: server spans at 2/s, no error series. Rate 2, error ratio 0.
18
+ * - worker: consumer spans at 0.5/s, counted.
19
+ * - frontend: client spans only. Absent from every panel and from the
20
+ * service picker.
21
+ * - idle: a server-span counter that doesn't move. Rate 0, and error ratio
22
+ * 0/0 = NaN, which is what a group with no traffic should give.
23
+ * - agents: two models, two tools, an idle model, token counters; the
24
+ * token totals are one instant value each.
25
+ * - SLO: the recording rules' outputs, seeded as plain series (the rules
26
+ * themselves are the prometheus lexicon's, checked there).
27
+ *
28
+ * Skipped, with the reason in the suite name, without Docker.
29
+ * On demand: `npx vitest run --project e2e lexicons/grafana/src/composites/queries.e2e.test.ts`.
30
+ */
31
+ import { mkdirSync, writeFileSync } from "node:fs";
32
+ import { dirname, join } from "node:path";
33
+ import { afterAll, beforeAll, describe, expect, it } from "vitest";
34
+ import { build } from "@intentius/chant/build";
35
+ import type { SerializerResult } from "@intentius/chant/serializer";
36
+ import { otelSerializer } from "@intentius/chant-lexicon-otel/serializer";
37
+ import { prometheusSerializer } from "@intentius/chant-lexicon-prometheus/serializer";
38
+ import { grafanaSerializer } from "../serializer";
39
+ import { DockerScope, GRAFANA_IMAGES, dockerAvailable, waitFor, type GrafanaContainer } from "../../test/e2e/containers";
40
+ import { dsQuery, lastBy, type Target } from "../../test/e2e/ds-query";
41
+ import { openMetrics, type Family } from "../../test/e2e/openmetrics";
42
+
43
+ const hasDocker = dockerAvailable();
44
+ const skipReason = hasDocker ? "" : " (skipped: Docker is not running)";
45
+ const example = join(import.meta.dirname, "..", "..", "examples", "dashboards-from-declarations", "src");
46
+
47
+ const STEP = 15;
48
+ /** The last sample, on a step boundary a minute back so no query reaches past the data. */
49
+ const END = Math.floor(Date.now() / 1000 / STEP) * STEP - 60;
50
+ const START = END - 3600;
51
+ /** Every query covers the last 30 minutes of the data. */
52
+ const FROM_MS = (END - 1800) * 1000;
53
+ const TO_MS = END * 1000;
54
+ const RANGE_S = 1800;
55
+
56
+ const SERVER = "SPAN_KIND_SERVER";
57
+ const CLIENT = "SPAN_KIND_CLIENT";
58
+ const CONSUMER = "SPAN_KIND_CONSUMER";
59
+ const OK = "STATUS_CODE_UNSET";
60
+ const ERROR = "STATUS_CODE_ERROR";
61
+
62
+ const span = (service: string, kind: string, status: string) => ({ service_name: service, span_name: `${service}-op`, span_kind: kind, status_code: status });
63
+
64
+ const agent = (model: string, tool: string, status: string, errorType = "") => ({
65
+ service_name: "agent",
66
+ gen_ai_request_model: model,
67
+ ...(tool ? { gen_ai_tool_name: tool } : {}),
68
+ status_code: status,
69
+ ...(errorType ? { error_type: errorType } : {}),
70
+ });
71
+
72
+ const FAMILIES: Family[] = [
73
+ {
74
+ type: "counter",
75
+ name: "shop_calls",
76
+ series: [
77
+ { labels: span("checkout", SERVER, OK), perSecond: 1 },
78
+ { labels: span("checkout", SERVER, ERROR), perSecond: 0.1 },
79
+ { labels: span("checkout", CLIENT, ERROR), perSecond: 10 },
80
+ { labels: span("cart", SERVER, OK), perSecond: 2 },
81
+ { labels: span("worker", CONSUMER, OK), perSecond: 0.5 },
82
+ { labels: span("frontend", CLIENT, OK), perSecond: 3 },
83
+ { labels: span("frontend", CLIENT, ERROR), perSecond: 1 },
84
+ { labels: span("idle", SERVER, OK), perSecond: 0 },
85
+ ],
86
+ },
87
+ {
88
+ type: "histogram",
89
+ name: "shop_duration_milliseconds",
90
+ buckets: [5, 10, 25, 50, 100, 250, 500, 1000],
91
+ series: [
92
+ { labels: span("checkout", SERVER, OK), perSecond: 1.1, within: 100 },
93
+ { labels: span("checkout", CLIENT, ERROR), perSecond: 10, within: 1000 },
94
+ { labels: span("cart", SERVER, OK), perSecond: 2, within: 10 },
95
+ { labels: span("worker", CONSUMER, OK), perSecond: 0.5, within: 250 },
96
+ { labels: span("frontend", CLIENT, OK), perSecond: 4, within: 500 },
97
+ ],
98
+ },
99
+ {
100
+ type: "counter",
101
+ name: "agents_calls",
102
+ series: [
103
+ { labels: agent("big", "", OK), perSecond: 1 },
104
+ { labels: agent("big", "", ERROR, "timeout"), perSecond: 0.25 },
105
+ { labels: agent("big", "search", OK), perSecond: 0.2 },
106
+ { labels: agent("big", "search", ERROR, "tool_error"), perSecond: 0.05 },
107
+ { labels: agent("small", "", OK), perSecond: 0.5 },
108
+ { labels: agent("small", "fetch", OK), perSecond: 0.1 },
109
+ { labels: agent("idle-model", "", OK), perSecond: 0 },
110
+ ],
111
+ },
112
+ {
113
+ type: "histogram",
114
+ name: "agents_duration_seconds",
115
+ buckets: [0.1, 0.5, 1, 2.5, 5],
116
+ series: [
117
+ { labels: { service_name: "agent", gen_ai_request_model: "big" }, perSecond: 1.25, within: 2.5 },
118
+ { labels: { service_name: "agent", gen_ai_request_model: "big", gen_ai_tool_name: "search" }, perSecond: 0.25, within: 2.5 },
119
+ { labels: { service_name: "agent", gen_ai_request_model: "small" }, perSecond: 0.5, within: 0.5 },
120
+ { labels: { service_name: "agent", gen_ai_request_model: "small", gen_ai_tool_name: "fetch" }, perSecond: 0.1, within: 0.5 },
121
+ ],
122
+ },
123
+ {
124
+ type: "counter",
125
+ name: "agents_tokens_input",
126
+ series: [
127
+ { labels: { gen_ai_request_model: "big" }, perSecond: 100 },
128
+ { labels: { gen_ai_request_model: "small" }, perSecond: 10 },
129
+ ],
130
+ },
131
+ {
132
+ type: "counter",
133
+ name: "agents_tokens_output",
134
+ series: [
135
+ { labels: { gen_ai_request_model: "big" }, perSecond: 20 },
136
+ { labels: { gen_ai_request_model: "small" }, perSecond: 5 },
137
+ ],
138
+ },
139
+ ...(
140
+ [
141
+ ["slo:sli_error:ratio_rate30d", 0.0005],
142
+ ["slo:objective:ratio", 0.999],
143
+ ["slo:error_budget:remaining", 0.5],
144
+ ["slo:sli_error:ratio_rate5m", 0.004],
145
+ ["slo:sli_error:ratio_rate30m", 0.003],
146
+ ["slo:sli_error:ratio_rate1h", 0.002],
147
+ ["slo:sli_error:ratio_rate2h", 0.0015],
148
+ ["slo:sli_error:ratio_rate6h", 0.001],
149
+ ["slo:sli_error:ratio_rate1d", 0.0008],
150
+ ["slo:sli_error:ratio_rate3d", 0.0006],
151
+ ] as const
152
+ ).map(([name, value]): Family => ({ type: "gauge", name, series: [{ labels: { slo: "checkout" }, value }] })),
153
+ ];
154
+
155
+ /** `histogram_quantile` inside the bucket `(lower, upper]` that holds every observation. */
156
+ const interpolated = (q: number, lower: number, upper: number) => lower + q * (upper - lower);
157
+
158
+ interface Panel {
159
+ title: string;
160
+ type: string;
161
+ targets?: Target[];
162
+ panels?: Panel[];
163
+ }
164
+
165
+ interface Built {
166
+ uid: string;
167
+ templating: { list: Array<{ name: string; query: string | { query: string }; allValue?: string }> };
168
+ panels: Panel[];
169
+ }
170
+
171
+ function panelsOf(d: Built): Panel[] {
172
+ const out: Panel[] = [];
173
+ const walk = (ps: Panel[]) => {
174
+ for (const p of ps) {
175
+ if (p.panels) walk(p.panels);
176
+ if (p.targets) out.push(p);
177
+ }
178
+ };
179
+ walk(d.panels);
180
+ return out;
181
+ }
182
+
183
+ /** Each variable at its "All" value, as the dashboard opens. */
184
+ function allValues(d: Built): Record<string, string> {
185
+ return Object.fromEntries(d.templating.list.map((v) => [v.name, v.allValue ?? ".*"]));
186
+ }
187
+
188
+ describe.skipIf(!hasDocker)(`composite queries against a seeded Prometheus, through Grafana${skipReason}`, () => {
189
+ const scope = new DockerScope("grafana-queries");
190
+ const dashboards = new Map<string, Built>();
191
+ let network = "";
192
+ let dir = "";
193
+
194
+ beforeAll(async () => {
195
+ const result = await build(example, [otelSerializer, prometheusSerializer, grafanaSerializer]);
196
+ expect(result.errors).toEqual([]);
197
+ const out = result.outputs.get("grafana") as SerializerResult;
198
+ dir = scope.tempDir();
199
+ for (const [file, content] of Object.entries(out.files ?? {})) {
200
+ mkdirSync(dirname(join(dir, file)), { recursive: true });
201
+ writeFileSync(join(dir, file), content);
202
+ if (file.endsWith(".json")) {
203
+ const d = JSON.parse(content) as Built;
204
+ dashboards.set(d.uid, d);
205
+ }
206
+ }
207
+ network = scope.network();
208
+ await scope.prometheus({ network, alias: "prometheus", openMetrics: openMetrics({ start: START, end: END, step: STEP }, FAMILIES) });
209
+ }, 300_000);
210
+
211
+ afterAll(() => scope.cleanup());
212
+
213
+ describe.each(GRAFANA_IMAGES)("%s", (image) => {
214
+ let grafana: GrafanaContainer;
215
+ const dashboard = (uid: string) => {
216
+ const d = dashboards.get(uid);
217
+ if (!d) throw new Error(`the example built no dashboard ${uid}; it built ${[...dashboards.keys()].join(", ")}`);
218
+ return d;
219
+ };
220
+ const panel = (uid: string, title: string, nth = 0) => {
221
+ const p = panelsOf(dashboard(uid)).filter((x) => x.title === title)[nth];
222
+ if (!p) throw new Error(`${uid} has no panel ${title}`);
223
+ return p;
224
+ };
225
+ const run = async (uid: string, target: Target) => {
226
+ const r = await dsQuery(grafana, target, { from: FROM_MS, to: TO_MS, vars: allValues(dashboard(uid)) });
227
+ expect(r.error, `${target.expr}\n${r.error}`).toBeUndefined();
228
+ return r;
229
+ };
230
+ const runPanel = async (uid: string, title: string, nth = 0) => run(uid, panel(uid, title, nth).targets![0]);
231
+
232
+ beforeAll(async () => {
233
+ grafana = await scope.grafana({ image, network, provisioningDir: join(dir, "provisioning"), dashboardsDir: join(dir, "dashboards") });
234
+ // The datasource reaches the seeded Prometheus before any panel runs.
235
+ await waitFor("Grafana to reach Prometheus", async () => {
236
+ const r = await grafana.api("/api/datasources/uid/prometheus/health");
237
+ return r.status === 200 && r.body?.status === "OK" ? true : undefined;
238
+ });
239
+ }, 300_000);
240
+
241
+ it("every panel query of every composite dashboard runs, and returns data", { timeout: 120_000 }, async () => {
242
+ for (const [uid, d] of dashboards) {
243
+ for (const p of panelsOf(d)) {
244
+ for (const t of p.targets!) {
245
+ const r = await run(uid, t);
246
+ expect(r.series.length, `${uid} / ${p.title} / ${t.refId}: ${t.expr}`).toBeGreaterThan(0);
247
+ }
248
+ }
249
+ }
250
+ });
251
+
252
+ describe("RedDashboard", () => {
253
+ it("rate counts server and consumer spans only; client-only services are absent", async () => {
254
+ const rate = lastBy((await runPanel("red-shop", "Rate")).series, "service_name");
255
+ expect(Object.keys(rate).sort()).toEqual(["cart", "checkout", "idle", "worker"]);
256
+ expect(rate.checkout).toBeCloseTo(1.1, 6);
257
+ expect(rate.cart).toBeCloseTo(2, 6);
258
+ expect(rate.worker).toBeCloseTo(0.5, 6);
259
+ expect(rate.idle).toBe(0);
260
+ });
261
+
262
+ it("error ratio ignores client errors, reads 0 for a service with calls and no errors, and NaN for one with no traffic", async () => {
263
+ const ratio = lastBy((await runPanel("red-shop", "Errors")).series, "service_name");
264
+ expect(Object.keys(ratio).sort()).toEqual(["cart", "checkout", "idle", "worker"]);
265
+ expect(ratio.checkout).toBeCloseTo(0.1 / 1.1, 6);
266
+ expect(ratio.cart).toBe(0);
267
+ expect(ratio.worker).toBe(0);
268
+ expect(ratio.idle).toBeNaN();
269
+ });
270
+
271
+ it("duration quantiles come from server and consumer spans only", async () => {
272
+ for (const [title, q] of [
273
+ ["Duration p50", 0.5],
274
+ ["Duration p95", 0.95],
275
+ ["Duration p99", 0.99],
276
+ ] as const) {
277
+ const d = lastBy((await runPanel("red-shop", title)).series, "service_name");
278
+ expect(Object.keys(d).sort(), title).toEqual(["cart", "checkout", "worker"]);
279
+ expect(d.checkout, title).toBeCloseTo(interpolated(q, 50, 100), 6);
280
+ expect(d.cart, title).toBeCloseTo(interpolated(q, 5, 10), 6);
281
+ expect(d.worker, title).toBeCloseTo(interpolated(q, 100, 250), 6);
282
+ }
283
+ });
284
+
285
+ it("the service picker lists the services that serve, not the client-only one", async () => {
286
+ const v = dashboard("red-shop").templating.list.find((x) => x.name === "service")!;
287
+ const query = typeof v.query === "string" ? v.query : v.query.query;
288
+ const m = /^label_values\((.*), (\w+)\)$/.exec(query);
289
+ expect(m, query).not.toBeNull();
290
+ const params = new URLSearchParams({ "match[]": m![1], start: String(FROM_MS / 1000), end: String(TO_MS / 1000) });
291
+ const r = await grafana.api(`/api/datasources/uid/prometheus/resources/api/v1/label/${m![2]}/values?${params}`);
292
+ expect(r.status).toBe(200);
293
+ expect([...r.body.data].sort()).toEqual(["cart", "checkout", "idle", "worker"]);
294
+ });
295
+ });
296
+
297
+ describe("AgentDashboard", () => {
298
+ it("calls, errors and latency per model", async () => {
299
+ const calls = lastBy((await runPanel("agents-agents", "Calls by model")).series, "gen_ai_request_model");
300
+ expect(calls.big).toBeCloseTo(1.5, 6);
301
+ expect(calls.small).toBeCloseTo(0.6, 6);
302
+ expect(calls["idle-model"]).toBe(0);
303
+ const errors = lastBy((await runPanel("agents-agents", "Errors by model")).series, "gen_ai_request_model");
304
+ expect(errors.big).toBeCloseTo(0.3 / 1.5, 6);
305
+ expect(errors.small).toBe(0);
306
+ expect(errors["idle-model"]).toBeNaN();
307
+ const latency = lastBy((await runPanel("agents-agents", "Latency p95 by model")).series, "gen_ai_request_model");
308
+ expect(latency.big).toBeCloseTo(interpolated(0.95, 1, 2.5), 6);
309
+ expect(latency.small).toBeCloseTo(interpolated(0.95, 0.1, 0.5), 6);
310
+ });
311
+
312
+ it("calls, errors and latency per tool, leaving out calls with no tool", async () => {
313
+ const calls = lastBy((await runPanel("agents-agents", "Tool calls")).series, "gen_ai_tool_name");
314
+ expect(Object.keys(calls).sort()).toEqual(["fetch", "search"]);
315
+ expect(calls.search).toBeCloseTo(0.25, 6);
316
+ expect(calls.fetch).toBeCloseTo(0.1, 6);
317
+ const errors = lastBy((await runPanel("agents-agents", "Errors by tool")).series, "gen_ai_tool_name");
318
+ expect(errors.search).toBeCloseTo(0.2, 6);
319
+ expect(errors.fetch).toBe(0);
320
+ const latency = lastBy((await runPanel("agents-agents", "Latency p95 by tool")).series, "gen_ai_tool_name");
321
+ expect(Object.keys(latency).sort()).toEqual(["fetch", "search"]);
322
+ expect(latency.search).toBeCloseTo(interpolated(0.95, 1, 2.5), 6);
323
+ expect(latency.fetch).toBeCloseTo(interpolated(0.95, 0.1, 0.5), 6);
324
+ });
325
+
326
+ it("errors by type", async () => {
327
+ const byType = lastBy((await runPanel("agents-agents", "Errors by type")).series, "error_type");
328
+ expect(Object.keys(byType).sort()).toEqual(["timeout", "tool_error"]);
329
+ expect(byType.timeout).toBeCloseTo(0.25, 6);
330
+ expect(byType.tool_error).toBeCloseTo(0.05, 6);
331
+ });
332
+
333
+ it("token rates per model", async () => {
334
+ const input = lastBy((await runPanel("agents-agents", "Input tokens by model")).series, "gen_ai_request_model");
335
+ expect(input.big).toBeCloseTo(100, 6);
336
+ expect(input.small).toBeCloseTo(10, 6);
337
+ const output = lastBy((await runPanel("agents-agents", "Output tokens by model")).series, "gen_ai_request_model");
338
+ expect(output.big).toBeCloseTo(20, 6);
339
+ expect(output.small).toBeCloseTo(5, 6);
340
+ });
341
+
342
+ it("token totals come back as one instant value over the dashboard's range", async () => {
343
+ for (const [title, perSecond] of [
344
+ ["Input tokens", 110],
345
+ ["Output tokens", 25],
346
+ ] as const) {
347
+ const r = await runPanel("agents-agents", title);
348
+ expect(r.series.length, title).toBe(1);
349
+ expect(r.series[0].values.length, title).toBe(1);
350
+ expect(r.series[0].values[0]!, title).toBeCloseTo(perSecond * RANGE_S, 3);
351
+ }
352
+ });
353
+ });
354
+
355
+ describe("SloDashboard", () => {
356
+ it("reads the recorded SLI, objective, budget and burn rates", async () => {
357
+ const last = async (title: string, nth = 0, refId = "A") => {
358
+ const p = panel("slo-checkout", title, nth);
359
+ const r = await run("slo-checkout", p.targets!.find((t) => t.refId === refId)!);
360
+ expect(r.series.length, `${title} ${refId}`).toBe(1);
361
+ return [...r.series[0].values].reverse().find((v) => v !== null)!;
362
+ };
363
+ expect(await last("SLI over 30d")).toBeCloseTo(0.9995, 9);
364
+ expect(await last("Objective")).toBeCloseTo(0.999, 9);
365
+ expect(await last("Error budget remaining")).toBeCloseTo(0.5, 9);
366
+ // No alert is firing, so `or vector(0)` gives 0.
367
+ expect(await last("Burn-rate alerts firing")).toBe(0);
368
+ expect(await last("Burn rate 1h / 5m (page)", 0, "A")).toBeCloseTo(2, 6);
369
+ expect(await last("Burn rate 1h / 5m (page)", 0, "B")).toBeCloseTo(4, 6);
370
+ expect(await last("Burn rate 3d / 6h (ticket)", 0, "A")).toBeCloseTo(0.6, 6);
371
+ });
372
+ });
373
+ });
374
+ });
@@ -6,6 +6,10 @@
6
6
  * lexicon's `spanMetricsNames()`: its namespace, its histogram unit, the
7
7
  * default dimensions it keeps, and optionally the `prometheus` exporter's
8
8
  * namespace. Renaming the connector's namespace moves every query here.
9
+ *
10
+ * The queries count server and consumer spans only by default, the spans
11
+ * that handle a request or a message, so a service's outgoing calls and
12
+ * its internal spans don't inflate its rate or dilute its error ratio.
9
13
  */
10
14
 
11
15
  import { Composite, type CompositeInstance } from "@intentius/chant/composite";
@@ -21,6 +25,7 @@ import { slugUid } from "../util";
21
25
  import {
22
26
  dashboardProps,
23
27
  durationUnit,
28
+ errorRatio,
24
29
  legend,
25
30
  quantile,
26
31
  quantileName,
@@ -41,8 +46,36 @@ export interface RedDashboardProps extends DashboardOptions {
41
46
  exporter?: OTelComponent<"exporter", "prometheus", PrometheusExporterConfig> | PrometheusNaming;
42
47
  /** Duration quantiles, one panel each (default p50, p95 and p99). */
43
48
  quantiles?: number[];
49
+ /**
50
+ * The span kinds the queries count (default server and consumer spans).
51
+ * `[]` counts every kind. With the default, a connector that excludes
52
+ * `span.kind` gets no kind filter; naming kinds for such a connector is
53
+ * an error.
54
+ */
55
+ spanKinds?: SpanKind[];
44
56
  }
45
57
 
58
+ /** The `span_kind` label values the spanmetrics connector writes. */
59
+ export type SpanKind =
60
+ | "SPAN_KIND_UNSPECIFIED"
61
+ | "SPAN_KIND_INTERNAL"
62
+ | "SPAN_KIND_SERVER"
63
+ | "SPAN_KIND_CLIENT"
64
+ | "SPAN_KIND_PRODUCER"
65
+ | "SPAN_KIND_CONSUMER";
66
+
67
+ const SPAN_KINDS: readonly SpanKind[] = [
68
+ "SPAN_KIND_UNSPECIFIED",
69
+ "SPAN_KIND_INTERNAL",
70
+ "SPAN_KIND_SERVER",
71
+ "SPAN_KIND_CLIENT",
72
+ "SPAN_KIND_PRODUCER",
73
+ "SPAN_KIND_CONSUMER",
74
+ ];
75
+
76
+ /** The kinds `RedDashboard` counts unless told otherwise: the spans that serve a request or consume a message. */
77
+ export const RED_DEFAULT_SPAN_KINDS: readonly SpanKind[] = Object.freeze(["SPAN_KIND_SERVER", "SPAN_KIND_CONSUMER"]);
78
+
46
79
  export type RedDashboardMembers = { dashboard: DashboardEntity };
47
80
 
48
81
  /** What `RedDashboard(...)` returns: its dashboard, as `dashboard`. */
@@ -50,28 +83,65 @@ export type RedDashboardInstance = CompositeInstance<RedDashboardMembers> & RedD
50
83
 
51
84
  const DEFAULT_QUANTILES = [0.5, 0.95, 0.99];
52
85
 
86
+ function cap(s: string): string {
87
+ return s.charAt(0).toUpperCase() + s.slice(1);
88
+ }
89
+
53
90
  function isNames(x: unknown): x is SpanMetricsNames {
54
91
  return typeof x === "object" && x !== null && "calls" in x && "labels" in x && "errorStatus" in x;
55
92
  }
56
93
 
57
- /** The PromQL the RED dashboard runs, from the connector's names. Exposed for tests and for panels of your own. */
58
- export function redQueries(names: SpanMetricsNames, quantiles: number[] = DEFAULT_QUANTILES, serviceFilter = "$service") {
94
+ /**
95
+ * The span-kind matcher for `kinds`, or none: none for `[]`, and none for
96
+ * the default kinds when the connector excludes `span.kind`.
97
+ */
98
+ function spanKindMatchers(names: SpanMetricsNames, kinds: readonly SpanKind[] | undefined): Matcher[] {
99
+ const label = names.labels.spanKind;
100
+ if (kinds === undefined) return label ? [[label, "=~", RED_DEFAULT_SPAN_KINDS.join("|")]] : [];
101
+ for (const k of kinds) {
102
+ if (!SPAN_KINDS.includes(k)) throw new Error(`RedDashboard: unknown span kind ${JSON.stringify(k)}; use one of ${SPAN_KINDS.join(", ")}`);
103
+ }
104
+ if (kinds.length === 0) return [];
105
+ if (!label) throw new Error("RedDashboard: the connector excludes span.kind, so the metrics can't be filtered by spanKinds");
106
+ return [[label, "=~", [...new Set(kinds)].join("|")]];
107
+ }
108
+
109
+ /**
110
+ * The PromQL the RED dashboard runs, from the connector's names. Exposed for
111
+ * tests and for panels of your own. `spanKinds` defaults to server and
112
+ * consumer spans; `[]` counts every kind.
113
+ */
114
+ export function redQueries(
115
+ names: SpanMetricsNames,
116
+ quantiles: number[] = DEFAULT_QUANTILES,
117
+ serviceFilter = "$service",
118
+ spanKinds?: readonly SpanKind[],
119
+ ) {
59
120
  const svc = names.labels.service;
60
121
  const status = names.labels.statusCode;
61
122
  if (!svc) throw new Error("RedDashboard: the connector excludes service.name, so there is no service to break the metrics down by");
62
123
  if (!status) throw new Error("RedDashboard: the connector excludes status.code, so errors can't be told from successes");
63
- const bySvc: Matcher[] = [[svc, "=~", serviceFilter]];
64
- const calls = selector(names.calls.prometheus, bySvc);
65
- const errors = selector(names.calls.prometheus, [...bySvc, [status, "=", names.errorStatus]]);
66
- const buckets = names.duration ? selector(`${names.duration.prometheus}_bucket`, bySvc) : undefined;
124
+ const kind = spanKindMatchers(names, spanKinds);
125
+ const scope: Matcher[] = [[svc, "=~", serviceFilter], ...kind];
126
+ const calls = selector(names.calls.prometheus, scope);
127
+ const errors = selector(names.calls.prometheus, [...scope, [status, "=", names.errorStatus]]);
128
+ const buckets = names.duration ? selector(`${names.duration.prometheus}_bucket`, scope) : undefined;
67
129
  return {
68
130
  rate: sumRate(calls, [svc]),
69
- errorRatio: `${sumRate(errors, [svc])}\n/\n${sumRate(calls, [svc])}`,
131
+ errorRatio: errorRatio(errors, calls, [svc]),
70
132
  duration: buckets ? quantiles.map((q) => ({ quantile: q, expr: quantile(q, buckets, [svc]) })) : [],
71
- services: `label_values(${names.calls.prometheus}, ${svc})`,
133
+ services: `label_values(${selector(names.calls.prometheus, kind)}, ${svc})`,
72
134
  };
73
135
  }
74
136
 
137
+ /** "server and consumer spans", for panel descriptions. */
138
+ function kindsText(names: SpanMetricsNames, kinds: readonly SpanKind[] | undefined): string {
139
+ const counted = kinds ?? (names.labels.spanKind ? RED_DEFAULT_SPAN_KINDS : []);
140
+ if (counted.length === 0) return "spans";
141
+ const words = [...new Set(counted)].map((k) => k.replace(/^SPAN_KIND_/, "").toLowerCase());
142
+ return `${words.length > 1 ? `${words.slice(0, -1).join(", ")} and ${words[words.length - 1]}` : words[0]} spans`;
143
+ }
144
+
75
145
  /**
76
146
  * A RED dashboard (rate, errors, duration) per service, built from a
77
147
  * `spanmetrics` connector declaration.
@@ -94,7 +164,8 @@ export const RedDashboard = Composite<RedDashboardProps, RedDashboardMembers>((p
94
164
  for (const q of quantiles) {
95
165
  if (!(typeof q === "number" && q > 0 && q < 1)) throw new Error(`RedDashboard: a quantile must be between 0 and 1, got ${String(q)}`);
96
166
  }
97
- const q = redQueries(names, quantiles);
167
+ const q = redQueries(names, quantiles, "$service", props.spanKinds);
168
+ const counted = kindsText(names, props.spanKinds);
98
169
  const svc = names.labels.service!;
99
170
  const ds = props.datasource;
100
171
 
@@ -112,7 +183,7 @@ export const RedDashboard = Composite<RedDashboardProps, RedDashboardMembers>((p
112
183
 
113
184
  const rate = new TimeSeriesPanel({
114
185
  title: "Rate",
115
- description: `Spans per second by service, from ${names.calls.prometheus}.`,
186
+ description: `${cap(counted)} per second by service, from ${names.calls.prometheus}.`,
116
187
  datasource: ds,
117
188
  gridPos: { w: 12, h: 8 },
118
189
  targets: [new PromQuery({ expr: q.rate, legendFormat: legend(svc) })],
@@ -120,7 +191,7 @@ export const RedDashboard = Composite<RedDashboardProps, RedDashboardMembers>((p
120
191
  });
121
192
  const errors = new TimeSeriesPanel({
122
193
  title: "Errors",
123
- description: `Share of spans with ${names.labels.statusCode}="${names.errorStatus}", by service.`,
194
+ description: `Share of ${counted} with ${names.labels.statusCode}="${names.errorStatus}", by service.`,
124
195
  datasource: ds,
125
196
  gridPos: { w: 12, h: 8 },
126
197
  targets: [new PromQuery({ expr: q.errorRatio, legendFormat: legend(svc) })],
@@ -135,7 +206,7 @@ export const RedDashboard = Composite<RedDashboardProps, RedDashboardMembers>((p
135
206
  (d) =>
136
207
  new TimeSeriesPanel({
137
208
  title: `Duration ${quantileName(d.quantile)}`,
138
- description: `${quantileName(d.quantile)} span duration by service, from ${names.duration!.prometheus}.`,
209
+ description: `${quantileName(d.quantile)} duration of ${counted} by service, from ${names.duration!.prometheus}.`,
139
210
  datasource: ds,
140
211
  gridPos: { w: width, h: 8 },
141
212
  targets: [new PromQuery({ expr: d.expr, legendFormat: legend(svc) })],
@@ -5,6 +5,7 @@
5
5
 
6
6
  import type { DatasourceInput } from "../query";
7
7
  import type { DashboardLinkInput } from "../dashboard";
8
+ import type { FolderEntity } from "../folder";
8
9
  import type { ThresholdsConfig } from "../schema/dashboard.gen";
9
10
 
10
11
  /** Dashboard settings every composite takes; each has a default derived from what it reads. */
@@ -16,8 +17,8 @@ export interface DashboardOptions {
16
17
  uid?: string;
17
18
  description?: string;
18
19
  tags?: string[];
19
- /** The Grafana folder to provision it into. */
20
- folder?: string;
20
+ /** The Grafana folder to provision it into: a path, or a `Folder` to pin its uid. */
21
+ folder?: string | FolderEntity;
21
22
  /** Initial time range. */
22
23
  time?: { from: string; to: string };
23
24
  /** Auto-refresh interval, e.g. `30s`. */
@@ -39,6 +40,17 @@ export function sumRate(sel: string, by: string[], range = "$__rate_interval"):
39
40
  return `sum${by.length ? ` by (${by.join(", ")})` : ""} (rate(${sel}[${range}]))`;
40
41
  }
41
42
 
43
+ /**
44
+ * `errors / all` as rates summed by `by`, with the numerator padded to zero:
45
+ * `(errors or all * 0) / all`. A group with calls but no error series gets
46
+ * 0 instead of dropping out of the result, so a panel shows 0% rather than
47
+ * "No data" for a service that has had no errors.
48
+ */
49
+ export function errorRatio(errorSel: string, allSel: string, by: string[]): string {
50
+ const all = sumRate(allSel, by);
51
+ return `(\n${sumRate(errorSel, by)}\nor\n${all} * 0\n)\n/\n${all}`;
52
+ }
53
+
42
54
  /** `histogram_quantile(q, sum by (le, labels) (rate(bucket[range])))`. */
43
55
  export function quantile(q: number, bucketSel: string, by: string[]): string {
44
56
  return `histogram_quantile(${num(q)}, ${sumRate(bucketSel, ["le", ...by])})`;