{ dashboard(title, uid='', datasource='default'):: { // Stuff that isn't materialised. _nextPanel:: 1, addRow(row):: self { // automatically number panels in added rows. local n = std.length(row.panels), local nextPanel = super._nextPanel, local panels = std.makeArray(n, function(i) row.panels[i] { id: nextPanel + i }), _nextPanel: nextPanel + n, rows+: [row { panels: panels }], }, addTemplate(name, metric_name, label_name, hide=0):: self { templating+: { list+: [{ allValue: null, current: { text: 'prod', value: 'prod', }, datasource: '$datasource', hide: hide, includeAll: false, label: name, multi: false, name: name, options: [], query: 'label_values(%s, %s)' % [metric_name, label_name], refresh: 1, regex: '', sort: 2, tagValuesQuery: '', tags: [], tagsQuery: '', type: 'query', useTags: false, }], }, }, addMultiTemplate(name, metric_name, label_name, hide=0):: self { templating+: { list+: [{ allValue: '.+', current: { selected: true, text: 'All', value: '$__all', }, datasource: '$datasource', hide: hide, includeAll: true, label: name, multi: true, name: name, options: [], query: 'label_values(%s, %s)' % [metric_name, label_name], refresh: 1, regex: '', sort: 2, tagValuesQuery: '', tags: [], tagsQuery: '', type: 'query', useTags: false, }], }, }, // Stuff that is materialised. uid: uid, annotations: { list: [], }, hideControls: false, links: [], rows: [], schemaVersion: 14, style: 'dark', tags: [], editable: true, gnetId: null, graphTooltip: 0, templating: { list: [ { current: { text: datasource, value: datasource, }, hide: 0, label: null, name: 'datasource', options: [], query: 'prometheus', refresh: 1, regex: '', type: 'datasource', }, ], }, time: { from: 'now-1h', to: 'now', }, refresh: '10s', timepicker: { refresh_intervals: [ '5s', '10s', '30s', '1m', '5m', '15m', '30m', '1h', '2h', '1d', ], time_options: [ '5m', '15m', '1h', '6h', '12h', '24h', '2d', '7d', '30d', ], }, timezone: 'utc', title: title, version: 0, }, row(title):: { _panels:: [], addPanel(panel):: self { _panels+: [panel], }, panels: // Automatically distribute panels within a row. local n = std.length(self._panels); [ p { span: std.floor(12 / n) } for p in self._panels ], collapse: false, height: '250px', repeat: null, repeatIteration: null, repeatRowId: null, showTitle: true, title: title, titleSize: 'h6', }, panel(title):: { aliasColors: {}, bars: false, dashLength: 10, dashes: false, datasource: '$datasource', fill: 1, legend: { avg: false, current: false, max: false, min: false, show: true, total: false, values: false, }, lines: true, linewidth: 1, links: [], nullPointMode: 'null as zero', percentage: false, pointradius: 5, points: false, renderer: 'flot', seriesOverrides: [], spaceLength: 10, span: 6, stack: false, steppedLine: false, targets: [], thresholds: [], timeFrom: null, timeShift: null, title: title, tooltip: { shared: false, sort: 0, value_type: 'individual', }, type: 'graph', xaxis: { buckets: null, mode: 'time', name: null, show: true, values: [], }, yaxes: $.yaxes('short'), }, queryPanel(queries, legends, legendLink=null):: { local qs = if std.type(queries) == 'string' then [queries] else queries, local ls = if std.type(legends) == 'string' then [legends] else legends, local qsandls = if std.length(ls) == std.length(qs) then std.makeArray(std.length(qs), function(x) { q: qs[x], l: ls[x] }) else error 'length of queries is not equal to length of legends', targets+: [ { legendLink: legendLink, expr: ql.q, format: 'time_series', intervalFactor: 2, legendFormat: ql.l, step: 10, } for ql in qsandls ], }, statPanel(query, format='percentunit'):: { type: 'singlestat', thresholds: '70,80', format: format, targets: [ { expr: query, format: 'time_series', instant: true, intervalFactor: 2, refId: 'A', }, ], }, tablePanel(queries, labelStyles):: { local qs = if std.type(queries) == 'string' then [queries] else queries, local style(labelStyle) = if std.type(labelStyle) == 'string' then { alias: labelStyle, colorMode: null, colors: [], dateFormat: 'YYYY-MM-DD HH:mm:ss', decimals: 2, thresholds: [], type: 'string', unit: 'short', } else { alias: labelStyle.alias, colorMode: null, colors: [], dateFormat: 'YYYY-MM-DD HH:mm:ss', decimals: if std.objectHas(labelStyle, 'decimals') then labelStyle.decimals else 2, thresholds: [], type: if std.objectHas(labelStyle, 'type') then labelStyle.type else 'number', unit: if std.objectHas(labelStyle, 'unit') then labelStyle.unit else 'short', link: std.objectHas(labelStyle, 'link'), linkTargetBlank: if std.objectHas(labelStyle, 'linkTargetBlank') then labelStyle.linkTargetBlank else false, linkTooltip: if std.objectHas(labelStyle, 'linkTooltip') then labelStyle.linkTooltip else 'Drill down', linkUrl: if std.objectHas(labelStyle, 'link') then labelStyle.link else '', }, _styles:: { // By default hide time. Time: { alias: 'Time', dateFormat: 'YYYY-MM-DD HH:mm:ss', type: 'hidden', }, } + { [label]: style(labelStyles[label]) for label in std.objectFields(labelStyles) }, styles: [ self._styles[pattern] { pattern: pattern } for pattern in std.objectFields(self._styles) ] + [style('') + { pattern: '/.*/' }], transform: 'table', type: 'table', targets: [ { expr: qs[i], format: 'table', instant: true, intervalFactor: 2, legendFormat: '', step: 10, refId: std.char(65 + i), } for i in std.range(0, std.length(qs) - 1) ], }, textPanel(title, markdown):: { type: 'text', title: title, options: { content: markdown, mode: 'markdown', }, transparent: true, datasource: null, timeFrom: null, timeShift: null, fieldConfig: { defaults: { custom: {}, }, overrides: [], }, }, stack:: { stack: true, fill: 10, linewidth: 0, }, yaxes(args):: local format = if std.type(args) == 'string' then args else null; local options = if std.type(args) == 'object' then args else {}; [ { format: format, label: null, logBase: 1, max: null, min: 0, show: true, } + options, { format: 'short', label: null, logBase: 1, max: null, min: null, show: false, }, ], qpsPanel(selector, statusLabelName='status_code'):: { aliasColors: { '1xx': '#EAB839', '2xx': '#7EB26D', '3xx': '#6ED0E0', '4xx': '#EF843C', '5xx': '#E24D42', success: '#7EB26D', 'error': '#E24D42', }, targets: [ { expr: ||| sum by (status) ( label_replace(label_replace(rate(%s[$__rate_interval]), "status", "${1}xx", "%s", "([0-9]).."), "status", "${1}", "%s", "([a-z]+)")) ||| % [selector, statusLabelName, statusLabelName], format: 'time_series', intervalFactor: 2, legendFormat: '{{status}}', refId: 'A', step: 10, }, ], } + $.stack, latencyPanel(metricName, selector, multiplier='1e3'):: { nullPointMode: 'null as zero', targets: [ { expr: 'histogram_quantile(0.99, sum(rate(%s_bucket%s[$__rate_interval])) by (le)) * %s' % [metricName, selector, multiplier], format: 'time_series', intervalFactor: 2, legendFormat: '99th Percentile', refId: 'A', step: 10, }, { expr: 'histogram_quantile(0.50, sum(rate(%s_bucket%s[$__rate_interval])) by (le)) * %s' % [metricName, selector, multiplier], format: 'time_series', intervalFactor: 2, legendFormat: '50th Percentile', refId: 'B', step: 10, }, { expr: 'sum(rate(%s_sum%s[$__rate_interval])) * %s / sum(rate(%s_count%s[$__rate_interval]))' % [metricName, selector, multiplier, metricName, selector], format: 'time_series', intervalFactor: 2, legendFormat: 'Average', refId: 'C', step: 10, }, ], yaxes: $.yaxes('ms'), }, selector:: { eq(label, value):: { label: label, op: '=', value: value }, neq(label, value):: { label: label, op: '!=', value: value }, re(label, value):: { label: label, op: '=~', value: value }, nre(label, value):: { label: label, op: '!~', value: value }, }, toPrometheusSelector(selector):: local pairs = [ '%(label)s%(op)s"%(value)s"' % matcher for matcher in selector ]; '{%s}' % std.join(', ', pairs), }