mirror of
https://github.com/prometheus-community/windows_exporter.git
synced 2026-10-17 10:09:05 +02:00
dashboard: factor Grafonnet builders into tab modules (#2595)
Signed-off-by: Jan-Otto Kröpke <mail@jkroepke.de>
This commit is contained in:
+10
-3
@@ -17,9 +17,11 @@ DASHBOARD := ../../dashboard/windows-exporter-dashboard.json
|
||||
|
||||
.DEFAULT_GOAL := generate
|
||||
.DELETE_ON_ERROR:
|
||||
.PHONY: generate dashboard tools jsonnet-tools fmt lint test check-runbooks clean
|
||||
.PHONY: generate rules dashboard dashboards check-dashboards tools jsonnet-tools fmt lint test check-runbooks clean
|
||||
|
||||
generate: windows_rules.yaml windows_alerts.yaml
|
||||
generate: rules dashboard
|
||||
|
||||
rules: windows_rules.yaml windows_alerts.yaml
|
||||
|
||||
tools: jsonnet-tools
|
||||
go install github.com/cloudflare/pint/cmd/pint@$(PINT_VERSION)
|
||||
@@ -50,10 +52,15 @@ dashboard: vendor
|
||||
$(JSONNET) -J vendor -S dashboard.jsonnet > $(DASHBOARD).tmp
|
||||
mv $(DASHBOARD).tmp $(DASHBOARD)
|
||||
|
||||
dashboards: dashboard
|
||||
|
||||
check-dashboards: dashboard
|
||||
git diff --exit-code -- $(DASHBOARD)
|
||||
|
||||
fmt:
|
||||
$(JSONNETFMT) $(FMT_FLAGS) -i $(SOURCES)
|
||||
|
||||
lint: generate tests/custom_rules.yaml tests/custom_alerts.yaml vendor
|
||||
lint: rules tests/custom_rules.yaml tests/custom_alerts.yaml vendor
|
||||
@set -e; for file in $(SOURCES); do \
|
||||
$(JSONNETFMT) $(FMT_FLAGS) "$$file" | diff -u "$$file" -; \
|
||||
done
|
||||
|
||||
+34
-6
@@ -13,7 +13,7 @@ and [pint](https://github.com/cloudflare/pint) tools:
|
||||
cd contrib/mixin
|
||||
make tools
|
||||
export PATH="$(go env GOPATH)/bin:$PATH"
|
||||
make generate
|
||||
make rules
|
||||
```
|
||||
|
||||
Only the dashboard depends on Jsonnet libraries, so `jb install` is not required
|
||||
@@ -201,15 +201,35 @@ Fork users should override `runbookURLPattern` to match their published URL.
|
||||
It is a Grafana v2 dashboard with tabs and needs Grafana 13 or later. See the
|
||||
[dashboard documentation](../../dashboard/README.md) for its content.
|
||||
|
||||
The dashboard is built with [grafonnet](https://github.com/grafana/grafonnet),
|
||||
The dashboard uses additive builders with [grafonnet](https://github.com/grafana/grafonnet),
|
||||
pinned in `jsonnetfile.json`:
|
||||
|
||||
- `dashboards/windows-exporter.libsonnet` defines the tabs, panels and queries.
|
||||
- `lib/dashboard.libsonnet` wraps grafonnet panel options and queries into v2
|
||||
panels, and lays them out in tabs, rows and grids.
|
||||
- `dashboards/windows-exporter.libsonnet` composes the dashboard, with one source
|
||||
file per tab for its panels, queries, rows, and grid positions.
|
||||
- `dashboards/builders.libsonnet` uses Grafonnet's native `apps.dashboard.v2`
|
||||
builders for the dashboard and layout and wraps its Prometheus query builders
|
||||
into the v2 panel/query-group schema.
|
||||
- `dashboards/styles.libsonnet` shares the visualization defaults; individual
|
||||
panels add their differences using `withDefaults`, `withOptions`, and
|
||||
`withOverrides`. Explicit nulls are preserved and arrays are replaced whole.
|
||||
- `dashboards/variables.libsonnet` and `dashboards/annotations.libsonnet` use
|
||||
native Grafonnet v2 builders for variables and annotations.
|
||||
- `lib/manifest.libsonnet` renders JSON with the four-space indentation of the
|
||||
committed file.
|
||||
|
||||
For example, a panel can be built with:
|
||||
|
||||
```jsonnet
|
||||
local b = import 'dashboards/builders.libsonnet';
|
||||
|
||||
b.panel.new(200, 'CPU usage', 'timeseries')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows:cpu_usage:ratio{instance="$instance"}')
|
||||
+ b.query.withLegendFormat('CPU'),
|
||||
])
|
||||
+ b.panel.withDefaults({ unit: 'percentunit', min: 0, max: 1 })
|
||||
```
|
||||
|
||||
Regenerate the committed file after changing the dashboard source:
|
||||
|
||||
```sh
|
||||
@@ -217,7 +237,15 @@ make dashboard
|
||||
```
|
||||
|
||||
This runs `jb install` and writes `../../dashboard/windows-exporter-dashboard.json`.
|
||||
The dashboard uses its own variables and does not read `_config`.
|
||||
`make dashboards` is an alias. `make generate` builds both the rules and the
|
||||
dashboard, while `make rules` builds only the rules. The same commands work
|
||||
inside WSL with the Go tool binaries on `PATH`.
|
||||
|
||||
The checked-in JSON remains the reference for the rendered dashboard, including
|
||||
queries, IDs, variables, annotations, transformations, and tab/row layout.
|
||||
`make check-dashboards` regenerates it and runs `git diff --exit-code`, displaying
|
||||
any drift. CI runs that check before the mixin tests. The dashboard uses its own
|
||||
variables and does not read `_config`.
|
||||
|
||||
## Validate
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
local b = import 'builders.libsonnet';
|
||||
local annotation = b.dashboard.spec.annotations;
|
||||
|
||||
[
|
||||
annotation.withKind()
|
||||
+ annotation.spec.withBuiltIn(true)
|
||||
+ annotation.spec.withEnable(true)
|
||||
+ annotation.spec.withHide(true)
|
||||
+ annotation.spec.withIconColor('rgba(0, 211, 255, 1)')
|
||||
+ annotation.spec.withLegacyOptions({
|
||||
type: 'dashboard',
|
||||
})
|
||||
+ annotation.spec.withName('Annotations & Alerts')
|
||||
+ annotation.spec.withQuery(b.dataQuery({}, 'grafana', '-- Grafana --'))
|
||||
,
|
||||
annotation.withKind()
|
||||
+ annotation.spec.withEnable(true)
|
||||
+ annotation.spec.withHide(false)
|
||||
+ annotation.spec.withIconColor('orange')
|
||||
+ annotation.spec.withLegacyOptions({
|
||||
expr: 'windows_system_boot_time_timestamp{job=~"$job", instance="$instance"} * 1000 > $__from < $__to',
|
||||
step: '1m',
|
||||
tagKeys: 'instance',
|
||||
textFormat: '',
|
||||
titleFormat: 'Reboot',
|
||||
useValueForTime: 'on',
|
||||
})
|
||||
+ annotation.spec.withName('Reboots')
|
||||
+ annotation.spec.withQuery(b.dataQuery({}, 'prometheus', '${datasource}')),
|
||||
]
|
||||
@@ -0,0 +1,107 @@
|
||||
local g = import 'github.com/grafana/grafonnet/gen/grafonnet-v13.0.0/main.libsonnet';
|
||||
local v2 = g.apps.dashboard.v2;
|
||||
local styles = import 'styles.libsonnet';
|
||||
local grid = v2.spec.layout.GridLayoutKind;
|
||||
local rows = v2.spec.layout.RowsLayoutKind;
|
||||
local tabs = v2.spec.layout.TabsLayoutKind;
|
||||
local item = grid.spec.items;
|
||||
local row = rows.spec.rows;
|
||||
local tab = tabs.spec.tabs;
|
||||
local prometheus = g.query.prometheus;
|
||||
|
||||
// Unlike std.mergePatch, this keeps explicit nulls (notably threshold steps).
|
||||
local merge(base, patch) = base + {
|
||||
[key]: if std.objectHas(base, key) && std.isObject(base[key]) && std.isObject(patch[key])
|
||||
then merge(base[key], patch[key]) else patch[key]
|
||||
for key in std.objectFields(patch)
|
||||
};
|
||||
|
||||
{
|
||||
dashboard: v2,
|
||||
|
||||
// Grafonnet's v2 dashboard accepts elements but has no Panel/QueryGroup constructor.
|
||||
// Keep that small schema boundary here; tab sources use the same additive builders.
|
||||
panel: {
|
||||
new(id, title, visualization): {
|
||||
kind: 'Panel',
|
||||
spec: {
|
||||
id: id,
|
||||
title: title,
|
||||
description: '',
|
||||
links: [],
|
||||
data: {
|
||||
kind: 'QueryGroup',
|
||||
spec: { queries: [], queryOptions: {}, transformations: [] },
|
||||
},
|
||||
vizConfig: {
|
||||
kind: 'VizConfig',
|
||||
group: visualization,
|
||||
version: '',
|
||||
spec: {
|
||||
fieldConfig: { defaults: styles[visualization].defaults, overrides: [] },
|
||||
options: styles[visualization].options,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
withDescription(value): { spec+: { description: value } },
|
||||
withLinks(value): { spec+: { links: value } },
|
||||
withQueries(value): { spec+: { data+: { spec+: { queries: value } } } },
|
||||
withQueryOptions(value): { spec+: { data+: { spec+: { queryOptions: value } } } },
|
||||
withTransformations(value): { spec+: { data+: { spec+: { transformations: value } } } },
|
||||
withDefaults(value): {
|
||||
spec+: { vizConfig+: { spec+: { fieldConfig+: { defaults: merge(super.defaults, value) } } } },
|
||||
},
|
||||
withOptions(value): { spec+: { vizConfig+: { spec+: { options: merge(super.options, value) } } } },
|
||||
withOverrides(value): { spec+: { vizConfig+: { spec+: { fieldConfig+: { overrides: value } } } } },
|
||||
},
|
||||
|
||||
dataQuery(spec, group='prometheus', datasource='${datasource}'): {
|
||||
kind: 'DataQuery',
|
||||
group: group,
|
||||
version: 'v0',
|
||||
datasource: { name: datasource },
|
||||
spec: spec,
|
||||
},
|
||||
|
||||
query: {
|
||||
new(expr, refId='A'): {
|
||||
kind: 'PanelQuery',
|
||||
spec: {
|
||||
hidden: false,
|
||||
refId: refId,
|
||||
query: $.dataQuery(
|
||||
prometheus.withExpr(expr)
|
||||
+ prometheus.withEditorMode('code')
|
||||
+ prometheus.withInstant(false)
|
||||
+ prometheus.withRange(true)
|
||||
+ prometheus.withLegendFormat('__auto')
|
||||
),
|
||||
},
|
||||
},
|
||||
withLegendFormat(value): {
|
||||
spec+: { query+: { spec+: prometheus.withLegendFormat(value) } },
|
||||
},
|
||||
withInstant(value=true): {
|
||||
spec+: { query+: { spec+: prometheus.withInstant(value) + prometheus.withRange(!value) } },
|
||||
},
|
||||
withRange(value): { spec+: { query+: { spec+: prometheus.withRange(value) } } },
|
||||
withFormat(value): { spec+: { query+: { spec+: prometheus.withFormat(value) } } },
|
||||
},
|
||||
|
||||
grid(items): (grid.withKind() + grid.spec.withItems(items)).spec.layout,
|
||||
rows(rows_): (rows.withKind() + rows.spec.withRows(rows_)).spec.layout,
|
||||
tabs(tabs_): (tabs.withKind() + tabs.spec.withTabs(tabs_)).spec.layout,
|
||||
place(id, x, y, width, height):
|
||||
item.withKind()
|
||||
+ item.spec.withElement({ kind: 'ElementReference', name: 'panel-' + id })
|
||||
+ item.spec.withX(x)
|
||||
+ item.spec.withY(y)
|
||||
+ item.spec.withWidth(width)
|
||||
+ item.spec.withHeight(height),
|
||||
row(title, layout): row.withKind() + row.withSpec({ title: title, collapse: false, layout: layout }),
|
||||
tab(title, layout, panels): {
|
||||
elements: { ['panel-' + panel.spec.id]: panel for panel in panels },
|
||||
layout: tab.withKind() + tab.withSpec({ title: title, layout: layout }),
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
local b = import 'builders.libsonnet';
|
||||
|
||||
b.tab(
|
||||
'CPU',
|
||||
b.grid([
|
||||
b.place(24, 0, 0, 12, 10),
|
||||
b.place(25, 12, 0, 12, 10),
|
||||
b.place(26, 0, 10, 8, 8),
|
||||
b.place(27, 8, 10, 8, 8),
|
||||
b.place(28, 16, 10, 8, 8),
|
||||
b.place(29, 0, 18, 12, 8),
|
||||
b.place(30, 12, 18, 12, 8),
|
||||
]),
|
||||
[
|
||||
b.panel.new(24, 'CPU utilization by mode', 'timeseries')
|
||||
+ b.panel.withDescription('Share of total CPU time across all logical processors.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('sum by (mode) (rate(windows_cpu_time_total{job=~"$job", instance="$instance", mode!="idle"}[$__rate_interval])) / scalar(count(windows_cpu_time_total{job=~"$job", instance="$instance", mode="idle"}))', 'A')
|
||||
+ b.query.withLegendFormat('{{mode}}'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
custom: {
|
||||
fillOpacity: 40,
|
||||
stacking: {
|
||||
mode: 'normal',
|
||||
},
|
||||
},
|
||||
max: 1,
|
||||
min: 0,
|
||||
}),
|
||||
|
||||
b.panel.new(25, 'CPU utilization per core', 'state-timeline')
|
||||
+ b.panel.withDescription('Busy time of each logical processor, labelled processor group,core. Green is idle, red is fully busy.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('label_replace(clamp(1 - rate(windows_cpu_time_total{job=~"$job", instance="$instance", mode="idle"}[$__rate_interval]), 0, 1), "core", "$1,0$2", "core", "(\\\\d+),(\\\\d)")', 'A')
|
||||
+ b.query.withLegendFormat('{{core}}'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
color: {
|
||||
mode: 'continuous-GrYlRd',
|
||||
},
|
||||
thresholds: {
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
value: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
unit: 'percentunit',
|
||||
}),
|
||||
|
||||
b.panel.new(26, 'Processor queue length', 'timeseries')
|
||||
+ b.panel.withDescription('Threads that are ready to run but waiting for a CPU. A sustained value above 2 per core indicates CPU contention.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_system_processor_queue_length{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withLegendFormat('queue length'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 'short',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
legend: {
|
||||
calcs: [],
|
||||
displayMode: 'list',
|
||||
},
|
||||
}),
|
||||
|
||||
b.panel.new(27, 'Context switches and interrupts', 'timeseries')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('rate(windows_system_context_switches_total{job=~"$job", instance="$instance"}[$__rate_interval])', 'A')
|
||||
+ b.query.withLegendFormat('context switches'),
|
||||
b.query.new('sum(rate(windows_cpu_interrupts_total{job=~"$job", instance="$instance"}[$__rate_interval]))', 'B')
|
||||
+ b.query.withLegendFormat('interrupts'),
|
||||
b.query.new('sum(rate(windows_cpu_dpcs_total{job=~"$job", instance="$instance"}[$__rate_interval]))', 'C')
|
||||
+ b.query.withLegendFormat('DPCs'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 'ops',
|
||||
}),
|
||||
|
||||
b.panel.new(28, 'Processes and threads', 'timeseries')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_system_processes{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withLegendFormat('processes'),
|
||||
b.query.new('windows_system_threads{job=~"$job", instance="$instance"}', 'B')
|
||||
+ b.query.withLegendFormat('threads'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 'short',
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'threads',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.axisPlacement',
|
||||
value: 'right',
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
|
||||
b.panel.new(29, 'System calls and exceptions', 'timeseries')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('rate(windows_system_system_calls_total{job=~"$job", instance="$instance"}[$__rate_interval])', 'A')
|
||||
+ b.query.withLegendFormat('system calls'),
|
||||
b.query.new('rate(windows_system_exception_dispatches_total{job=~"$job", instance="$instance"}[$__rate_interval])', 'B')
|
||||
+ b.query.withLegendFormat('exception dispatches'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 'ops',
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'exception dispatches',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.axisPlacement',
|
||||
value: 'right',
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
|
||||
b.panel.new(30, 'CPU frequency', 'timeseries')
|
||||
+ b.panel.withDescription('Effective frequency from the APERF/MPERF counters, averaged and maximum across logical processors, against the nominal frequency. Values above nominal mean turbo boost.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('avg(1e4 * windows_cpu_core_frequency_mhz{job=~"$job", instance="$instance"} * rate(windows_cpu_processor_performance_total{job=~"$job", instance="$instance"}[$__rate_interval]) / rate(windows_cpu_processor_mperf_total{job=~"$job", instance="$instance"}[$__rate_interval]))', 'A')
|
||||
+ b.query.withLegendFormat('average'),
|
||||
b.query.new('max(1e4 * windows_cpu_core_frequency_mhz{job=~"$job", instance="$instance"} * rate(windows_cpu_processor_performance_total{job=~"$job", instance="$instance"}[$__rate_interval]) / rate(windows_cpu_processor_mperf_total{job=~"$job", instance="$instance"}[$__rate_interval]))', 'B')
|
||||
+ b.query.withLegendFormat('maximum'),
|
||||
b.query.new('avg(windows_cpu_core_frequency_mhz{job=~"$job", instance="$instance"}) * 1e6', 'C')
|
||||
+ b.query.withLegendFormat('nominal'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 'hertz',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
legend: {
|
||||
calcs: [],
|
||||
displayMode: 'list',
|
||||
},
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'nominal',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'color',
|
||||
value: {
|
||||
fixedColor: 'text',
|
||||
mode: 'fixed',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'custom.fillOpacity',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
id: 'custom.lineStyle',
|
||||
value: {
|
||||
dash: [
|
||||
10,
|
||||
10,
|
||||
],
|
||||
fill: 'dash',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,319 @@
|
||||
local b = import 'builders.libsonnet';
|
||||
|
||||
b.tab(
|
||||
'Disk',
|
||||
b.grid([
|
||||
b.place(37, 0, 0, 8, 8),
|
||||
b.place(38, 8, 0, 8, 8),
|
||||
b.place(39, 16, 0, 8, 8),
|
||||
b.place(40, 0, 8, 12, 8),
|
||||
b.place(41, 12, 8, 12, 8),
|
||||
b.place(42, 0, 16, 12, 8),
|
||||
b.place(43, 12, 16, 12, 8),
|
||||
]),
|
||||
[
|
||||
b.panel.new(37, 'Volumes', 'table')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_logical_disk_size_bytes{job=~"$job", instance="$instance", volume=~"$volume"}', 'A')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
b.query.new('windows_logical_disk_free_bytes{job=~"$job", instance="$instance", volume=~"$volume"}', 'B')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
b.query.new('100 * (1 - windows_logical_disk_free_bytes{job=~"$job", instance="$instance", volume=~"$volume"} / windows_logical_disk_size_bytes{job=~"$job", instance="$instance", volume=~"$volume"})', 'C')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
])
|
||||
+ b.panel.withTransformations([
|
||||
{
|
||||
group: 'joinByField',
|
||||
kind: 'Transformation',
|
||||
spec: {
|
||||
options: {
|
||||
byField: 'volume',
|
||||
mode: 'outer',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
group: 'organize',
|
||||
kind: 'Transformation',
|
||||
spec: {
|
||||
options: {
|
||||
excludeByName: {
|
||||
Time: true,
|
||||
'Time 1': true,
|
||||
'Time 2': true,
|
||||
'Time 3': true,
|
||||
__name__: true,
|
||||
instance: true,
|
||||
'instance 1': true,
|
||||
'instance 2': true,
|
||||
'instance 3': true,
|
||||
job: true,
|
||||
'job 1': true,
|
||||
'job 2': true,
|
||||
'job 3': true,
|
||||
port: true,
|
||||
'port 1': true,
|
||||
'port 2': true,
|
||||
'port 3': true,
|
||||
},
|
||||
includeByName: {},
|
||||
indexByName: {
|
||||
'Value #A': 1,
|
||||
'Value #B': 2,
|
||||
'Value #C': 3,
|
||||
volume: 0,
|
||||
},
|
||||
renameByName: {
|
||||
'Value #A': 'Size',
|
||||
'Value #B': 'Free',
|
||||
'Value #C': 'Used',
|
||||
volume: 'Volume',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'Value #A',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'unit',
|
||||
value: 'bytes',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'Value #B',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'unit',
|
||||
value: 'bytes',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'Value #C',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'unit',
|
||||
value: 'percent',
|
||||
},
|
||||
{
|
||||
id: 'min',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
id: 'max',
|
||||
value: 100,
|
||||
},
|
||||
{
|
||||
id: 'thresholds',
|
||||
value: {
|
||||
mode: 'absolute',
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
},
|
||||
{
|
||||
color: 'orange',
|
||||
value: 80,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
value: 90,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'color',
|
||||
value: {
|
||||
mode: 'continuous-GrYlRd',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'custom.cellOptions',
|
||||
value: {
|
||||
mode: 'basic',
|
||||
type: 'gauge',
|
||||
valueDisplayMode: 'text',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'decimals',
|
||||
value: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
|
||||
b.panel.new(38, 'Volume usage', 'timeseries')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('100 * (1 - windows_logical_disk_free_bytes{job=~"$job", instance="$instance", volume=~"$volume"} / windows_logical_disk_size_bytes{job=~"$job", instance="$instance", volume=~"$volume"})', 'A')
|
||||
+ b.query.withLegendFormat('{{volume}}'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
custom: {
|
||||
thresholdsStyle: {
|
||||
mode: 'dashed',
|
||||
},
|
||||
},
|
||||
max: 100,
|
||||
min: 0,
|
||||
thresholds: {
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
color: 'orange',
|
||||
value: 80,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
value: 90,
|
||||
},
|
||||
],
|
||||
},
|
||||
unit: 'percent',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
legend: {
|
||||
calcs: [
|
||||
'min',
|
||||
'max',
|
||||
'lastNotNull',
|
||||
],
|
||||
},
|
||||
}),
|
||||
|
||||
b.panel.new(39, 'Volume busy time', 'timeseries')
|
||||
+ b.panel.withDescription('Share of time the volume was servicing requests (1 - idle time).')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('1 - clamp_max(rate(windows_logical_disk_idle_seconds_total{job=~"$job", instance="$instance", volume=~"$volume"}[$__rate_interval]), 1)', 'A')
|
||||
+ b.query.withLegendFormat('{{volume}}'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
max: 1,
|
||||
min: 0,
|
||||
}),
|
||||
|
||||
b.panel.new(40, 'Disk throughput', 'timeseries')
|
||||
+ b.panel.withDescription('Reads are drawn below the axis.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('rate(windows_logical_disk_write_bytes_total{job=~"$job", instance="$instance", volume=~"$volume"}[$__rate_interval])', 'A')
|
||||
+ b.query.withLegendFormat('{{volume}} write'),
|
||||
b.query.new('rate(windows_logical_disk_read_bytes_total{job=~"$job", instance="$instance", volume=~"$volume"}[$__rate_interval])', 'B')
|
||||
+ b.query.withLegendFormat('{{volume}} read'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: 'Bps',
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byRegexp',
|
||||
options: '.* read$',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.transform',
|
||||
value: 'negative-Y',
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
|
||||
b.panel.new(41, 'Disk IOPS', 'timeseries')
|
||||
+ b.panel.withDescription('Reads are drawn below the axis.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('rate(windows_logical_disk_writes_total{job=~"$job", instance="$instance", volume=~"$volume"}[$__rate_interval])', 'A')
|
||||
+ b.query.withLegendFormat('{{volume}} write'),
|
||||
b.query.new('rate(windows_logical_disk_reads_total{job=~"$job", instance="$instance", volume=~"$volume"}[$__rate_interval])', 'B')
|
||||
+ b.query.withLegendFormat('{{volume}} read'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: 'iops',
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byRegexp',
|
||||
options: '.* read$',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.transform',
|
||||
value: 'negative-Y',
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
|
||||
b.panel.new(42, 'Disk latency', 'timeseries')
|
||||
+ b.panel.withDescription('Average time per read and write operation. Reads are drawn below the axis.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('rate(windows_logical_disk_write_seconds_total{job=~"$job", instance="$instance", volume=~"$volume"}[$__rate_interval]) / rate(windows_logical_disk_writes_total{job=~"$job", instance="$instance", volume=~"$volume"}[$__rate_interval])', 'A')
|
||||
+ b.query.withLegendFormat('{{volume}} write'),
|
||||
b.query.new('rate(windows_logical_disk_read_seconds_total{job=~"$job", instance="$instance", volume=~"$volume"}[$__rate_interval]) / rate(windows_logical_disk_reads_total{job=~"$job", instance="$instance", volume=~"$volume"}[$__rate_interval])', 'B')
|
||||
+ b.query.withLegendFormat('{{volume}} read'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: 's',
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byRegexp',
|
||||
options: '.* read$',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.transform',
|
||||
value: 'negative-Y',
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
|
||||
b.panel.new(43, 'Disk queue length', 'timeseries')
|
||||
+ b.panel.withDescription('Average number of read and write requests queued for the volume. Reads are drawn below the axis. A queue that stays high while latency rises indicates a disk bottleneck. The avg_*_requests_queued metrics grow like counters, so the panel takes their rate.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('rate(windows_logical_disk_avg_write_requests_queued{job=~"$job", instance="$instance", volume=~"$volume"}[$__rate_interval])', 'A')
|
||||
+ b.query.withLegendFormat('{{volume}} write'),
|
||||
b.query.new('rate(windows_logical_disk_avg_read_requests_queued{job=~"$job", instance="$instance", volume=~"$volume"}[$__rate_interval])', 'B')
|
||||
+ b.query.withLegendFormat('{{volume}} read'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: 'short',
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byRegexp',
|
||||
options: '.* read$',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.transform',
|
||||
value: 'negative-Y',
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,659 @@
|
||||
local b = import 'builders.libsonnet';
|
||||
|
||||
b.tab(
|
||||
'Exporter',
|
||||
b.rows([
|
||||
b.row('Scrape', b.grid([
|
||||
b.place(90, 0, 0, 4, 4),
|
||||
b.place(91, 4, 0, 4, 4),
|
||||
b.place(92, 8, 0, 4, 4),
|
||||
b.place(93, 12, 0, 4, 4),
|
||||
b.place(94, 16, 0, 4, 4),
|
||||
b.place(95, 20, 0, 4, 4),
|
||||
b.place(96, 0, 4, 12, 8),
|
||||
b.place(97, 12, 4, 12, 8),
|
||||
])),
|
||||
b.row('Collectors', b.grid([
|
||||
b.place(99, 0, 0, 8, 8),
|
||||
b.place(100, 8, 0, 16, 8),
|
||||
b.place(101, 0, 8, 12, 9),
|
||||
b.place(102, 12, 8, 12, 9),
|
||||
])),
|
||||
b.row('Process and Go runtime', b.grid([
|
||||
b.place(104, 0, 0, 8, 8),
|
||||
b.place(105, 8, 0, 8, 8),
|
||||
b.place(106, 16, 0, 8, 8),
|
||||
b.place(107, 0, 8, 8, 8),
|
||||
b.place(108, 8, 8, 8, 8),
|
||||
b.place(109, 16, 8, 8, 8),
|
||||
b.place(110, 0, 16, 12, 8),
|
||||
b.place(111, 12, 16, 12, 8),
|
||||
])),
|
||||
]),
|
||||
[
|
||||
b.panel.new(90, 'Scrape status', 'stat')
|
||||
+ b.panel.withDescription('Result of the last scrape by Prometheus.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('up{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withInstant(),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
mappings: [
|
||||
{
|
||||
options: {
|
||||
'0': {
|
||||
index: 0,
|
||||
text: 'down',
|
||||
},
|
||||
'1': {
|
||||
index: 1,
|
||||
text: 'up',
|
||||
},
|
||||
},
|
||||
type: 'value',
|
||||
},
|
||||
],
|
||||
thresholds: {
|
||||
steps: [
|
||||
{
|
||||
color: 'red',
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
color: 'green',
|
||||
value: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
unit: '',
|
||||
}),
|
||||
|
||||
b.panel.new(91, 'Exporter version', 'stat')
|
||||
+ b.panel.withDescription('Builds without a version show the first seven characters of the Git revision.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_exporter_build_info{job=~"$job", instance="$instance", version!=""} or on (instance) label_replace(windows_exporter_build_info{job=~"$job", instance="$instance"}, "version", "$1", "revision", "(.{7}).*")', 'A')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: '',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
colorMode: 'none',
|
||||
reduceOptions: {
|
||||
fields: '/^version$/',
|
||||
},
|
||||
textMode: 'value',
|
||||
}),
|
||||
|
||||
b.panel.new(92, 'Exporter uptime', 'stat')
|
||||
+ b.panel.withDescription('Time since the exporter process started. A short uptime on a host with a long uptime means the exporter restarted.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('time() - process_start_time_seconds{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withInstant(),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
thresholds: {
|
||||
steps: [
|
||||
{
|
||||
color: 'orange',
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
color: 'text',
|
||||
value: 3600,
|
||||
},
|
||||
],
|
||||
},
|
||||
unit: 'dtdurations',
|
||||
}),
|
||||
|
||||
b.panel.new(93, 'Failed collectors', 'stat')
|
||||
+ b.panel.withDescription('Collectors that failed at least once in the selected time range.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('count(min_over_time(windows_exporter_collector_success{job=~"$job", instance="$instance"}[$__range]) == 0) or vector(0)', 'A')
|
||||
+ b.query.withInstant(),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
thresholds: {
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
value: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
|
||||
b.panel.new(94, 'Timed-out collectors', 'stat')
|
||||
+ b.panel.withDescription('Collectors that hit the collector timeout at least once in the selected time range.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('count(max_over_time(windows_exporter_collector_timeout{job=~"$job", instance="$instance"}[$__range]) == 1) or vector(0)', 'A')
|
||||
+ b.query.withInstant(),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
thresholds: {
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
value: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
|
||||
b.panel.new(95, 'Samples per scrape', 'stat')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('scrape_samples_scraped{job=~"$job", instance="$instance"}', 'A'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: 'short',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
graphMode: 'area',
|
||||
}),
|
||||
|
||||
b.panel.new(96, 'Scrape duration', 'timeseries')
|
||||
+ b.panel.withDescription('Scrape duration measured by Prometheus and by the exporter. It must stay below the scrape timeout of the job.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('scrape_duration_seconds{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withLegendFormat('Prometheus'),
|
||||
b.query.new('windows_exporter_scrape_duration_seconds{job=~"$job", instance="$instance"}', 'B')
|
||||
+ b.query.withLegendFormat('exporter'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 's',
|
||||
}),
|
||||
|
||||
b.panel.new(97, 'HTTP requests to /metrics', 'timeseries')
|
||||
+ b.panel.withDescription('Scrape requests by HTTP status code, and errors while gathering or encoding metrics. More requests than one per scrape interval mean several jobs or Prometheus servers scrape this exporter.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('sum by (code) (rate(promhttp_metric_handler_requests_total{job=~"$job", instance="$instance"}[$__rate_interval])) > 0', 'A')
|
||||
+ b.query.withLegendFormat('HTTP {{code}}'),
|
||||
b.query.new('sum by (cause) (rate(promhttp_metric_handler_errors_total{job=~"$job", instance="$instance"}[$__rate_interval])) > 0', 'B')
|
||||
+ b.query.withLegendFormat('{{cause}} errors'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 'reqps',
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byRegexp',
|
||||
options: '.* errors',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'color',
|
||||
value: {
|
||||
fixedColor: 'red',
|
||||
mode: 'fixed',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
|
||||
b.panel.new(99, 'Collectors', 'table')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_exporter_collector_success{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
b.query.new('windows_exporter_collector_timeout{job=~"$job", instance="$instance"}', 'B')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
b.query.new('windows_exporter_collector_duration_seconds{job=~"$job", instance="$instance"}', 'C')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
])
|
||||
+ b.panel.withTransformations([
|
||||
{
|
||||
group: 'joinByField',
|
||||
kind: 'Transformation',
|
||||
spec: {
|
||||
options: {
|
||||
byField: 'collector',
|
||||
mode: 'outer',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
group: 'organize',
|
||||
kind: 'Transformation',
|
||||
spec: {
|
||||
options: {
|
||||
excludeByName: {
|
||||
Time: true,
|
||||
'Time 1': true,
|
||||
'Time 2': true,
|
||||
'Time 3': true,
|
||||
__name__: true,
|
||||
'__name__ 1': true,
|
||||
'__name__ 2': true,
|
||||
'__name__ 3': true,
|
||||
instance: true,
|
||||
'instance 1': true,
|
||||
'instance 2': true,
|
||||
'instance 3': true,
|
||||
job: true,
|
||||
'job 1': true,
|
||||
'job 2': true,
|
||||
'job 3': true,
|
||||
port: true,
|
||||
'port 1': true,
|
||||
'port 2': true,
|
||||
'port 3': true,
|
||||
},
|
||||
includeByName: {},
|
||||
indexByName: {
|
||||
'Value #A': 1,
|
||||
'Value #B': 2,
|
||||
'Value #C': 3,
|
||||
collector: 0,
|
||||
},
|
||||
renameByName: {
|
||||
'Value #A': 'Status',
|
||||
'Value #B': 'Timeout',
|
||||
'Value #C': 'Duration',
|
||||
collector: 'Collector',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
+ b.panel.withOptions({
|
||||
sortBy: [
|
||||
{
|
||||
desc: true,
|
||||
displayName: 'Duration',
|
||||
},
|
||||
],
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'Value #A',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'mappings',
|
||||
value: [
|
||||
{
|
||||
options: {
|
||||
'0': {
|
||||
color: 'red',
|
||||
index: 0,
|
||||
text: 'failed',
|
||||
},
|
||||
'1': {
|
||||
color: 'green',
|
||||
index: 1,
|
||||
text: 'ok',
|
||||
},
|
||||
},
|
||||
type: 'value',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'custom.cellOptions',
|
||||
value: {
|
||||
type: 'color-text',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'Value #B',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'mappings',
|
||||
value: [
|
||||
{
|
||||
options: {
|
||||
'0': {
|
||||
color: 'green',
|
||||
index: 0,
|
||||
text: 'no',
|
||||
},
|
||||
'1': {
|
||||
color: 'red',
|
||||
index: 1,
|
||||
text: 'yes',
|
||||
},
|
||||
},
|
||||
type: 'value',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'custom.cellOptions',
|
||||
value: {
|
||||
type: 'color-text',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'Value #C',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'unit',
|
||||
value: 's',
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
|
||||
b.panel.new(100, 'Collector duration', 'timeseries')
|
||||
+ b.panel.withDescription('Time each collector spent per scrape. Compare against the scrape timeout.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_exporter_collector_duration_seconds{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withLegendFormat('{{collector}}'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
custom: {
|
||||
fillOpacity: 40,
|
||||
stacking: {
|
||||
mode: 'normal',
|
||||
},
|
||||
},
|
||||
min: 0,
|
||||
unit: 's',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
legend: {
|
||||
sortBy: 'Mean',
|
||||
sortDesc: true,
|
||||
},
|
||||
}),
|
||||
|
||||
b.panel.new(101, 'Collector failures', 'state-timeline')
|
||||
+ b.panel.withDescription('Collectors that failed at least once in the selected time range.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_exporter_collector_success{job=~"$job", instance="$instance"} and on (collector) (min_over_time(windows_exporter_collector_success{job=~"$job", instance="$instance"}[$__range] @ end()) == 0)', 'A')
|
||||
+ b.query.withLegendFormat('{{collector}}'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
mappings: [
|
||||
{
|
||||
options: {
|
||||
'0': {
|
||||
color: 'red',
|
||||
index: 0,
|
||||
text: 'failed',
|
||||
},
|
||||
'1': {
|
||||
color: 'green',
|
||||
index: 1,
|
||||
text: 'ok',
|
||||
},
|
||||
},
|
||||
type: 'value',
|
||||
},
|
||||
],
|
||||
}),
|
||||
|
||||
b.panel.new(102, 'Collector timeouts', 'state-timeline')
|
||||
+ b.panel.withDescription('Collectors that hit the collector timeout at least once in the selected time range.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('1 - windows_exporter_collector_timeout{job=~"$job", instance="$instance"} and on (collector) (max_over_time(windows_exporter_collector_timeout{job=~"$job", instance="$instance"}[$__range] @ end()) == 1)', 'A')
|
||||
+ b.query.withLegendFormat('{{collector}}'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
mappings: [
|
||||
{
|
||||
options: {
|
||||
'0': {
|
||||
color: 'red',
|
||||
index: 0,
|
||||
text: 'timed out',
|
||||
},
|
||||
'1': {
|
||||
color: 'green',
|
||||
index: 1,
|
||||
text: 'in time',
|
||||
},
|
||||
},
|
||||
type: 'value',
|
||||
},
|
||||
],
|
||||
}),
|
||||
|
||||
b.panel.new(104, 'Exporter CPU usage', 'timeseries')
|
||||
+ b.panel.withDescription('CPU time used by the exporter process, as a share of one core. CPU usage grows with the number of scrapes, for example when two Prometheus jobs scrape the same exporter; CPU per scrape (right axis) does not.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('rate(process_cpu_seconds_total{job=~"$job", instance="$instance"}[$__rate_interval])', 'A')
|
||||
+ b.query.withLegendFormat('CPU'),
|
||||
b.query.new('rate(process_cpu_seconds_total{job=~"$job", instance="$instance"}[$__rate_interval]) / on (instance) sum by (instance) (rate(promhttp_metric_handler_requests_total{job=~"$job", instance="$instance"}[$__rate_interval]))', 'B')
|
||||
+ b.query.withLegendFormat('CPU per scrape'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
legend: {
|
||||
calcs: [],
|
||||
displayMode: 'list',
|
||||
},
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'CPU per scrape',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'unit',
|
||||
value: 's',
|
||||
},
|
||||
{
|
||||
id: 'custom.axisPlacement',
|
||||
value: 'right',
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
|
||||
b.panel.new(105, 'Exporter memory', 'timeseries')
|
||||
+ b.panel.withDescription('Working set and private bytes of the exporter process, and the Go heap in use. Steady growth points to a leak.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('process_resident_memory_bytes{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withLegendFormat('working set'),
|
||||
b.query.new('process_virtual_memory_bytes{job=~"$job", instance="$instance"}', 'B')
|
||||
+ b.query.withLegendFormat('private bytes'),
|
||||
b.query.new('go_memstats_heap_inuse_bytes{job=~"$job", instance="$instance"}', 'C')
|
||||
+ b.query.withLegendFormat('Go heap in use'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 'bytes',
|
||||
}),
|
||||
|
||||
b.panel.new(106, 'Exporter handles', 'timeseries')
|
||||
+ b.panel.withDescription('Open handles of the exporter process (process_open_fds). Steady growth points to a handle leak.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('process_open_fds{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withLegendFormat('open handles'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 'short',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
legend: {
|
||||
calcs: [],
|
||||
displayMode: 'list',
|
||||
},
|
||||
}),
|
||||
|
||||
b.panel.new(107, 'Goroutines by state', 'timeseries')
|
||||
+ b.panel.withDescription('Steady growth of goroutines points to a goroutine leak. Goroutines that stay "not in Go" sit in a system call, for example a collector waiting on WMI or PDH. The states need an exporter build with the Go scheduler metrics; the total line works with every build.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('go_sched_goroutines_running_goroutines{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withLegendFormat('running'),
|
||||
b.query.new('go_sched_goroutines_runnable_goroutines{job=~"$job", instance="$instance"}', 'B')
|
||||
+ b.query.withLegendFormat('runnable'),
|
||||
b.query.new('go_sched_goroutines_waiting_goroutines{job=~"$job", instance="$instance"}', 'C')
|
||||
+ b.query.withLegendFormat('waiting'),
|
||||
b.query.new('go_sched_goroutines_not_in_go_goroutines{job=~"$job", instance="$instance"}', 'D')
|
||||
+ b.query.withLegendFormat('not in Go'),
|
||||
b.query.new('go_goroutines{job=~"$job", instance="$instance"}', 'E')
|
||||
+ b.query.withLegendFormat('total'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
custom: {
|
||||
fillOpacity: 40,
|
||||
stacking: {
|
||||
mode: 'normal',
|
||||
},
|
||||
},
|
||||
min: 0,
|
||||
unit: 'short',
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'total',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'color',
|
||||
value: {
|
||||
fixedColor: 'text',
|
||||
mode: 'fixed',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'custom.stacking',
|
||||
value: {
|
||||
group: 'B',
|
||||
mode: 'none',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'custom.fillOpacity',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
id: 'custom.lineStyle',
|
||||
value: {
|
||||
dash: [
|
||||
10,
|
||||
10,
|
||||
],
|
||||
fill: 'dash',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
|
||||
b.panel.new(108, 'Goroutines created and threads', 'timeseries')
|
||||
+ b.panel.withDescription('Goroutines started per second, and OS threads owned by the Go runtime. Goroutines created needs an exporter build with the Go scheduler metrics.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('rate(go_sched_goroutines_created_goroutines_total{job=~"$job", instance="$instance"}[$__rate_interval])', 'A')
|
||||
+ b.query.withLegendFormat('goroutines created/s'),
|
||||
b.query.new('go_threads{job=~"$job", instance="$instance"}', 'B')
|
||||
+ b.query.withLegendFormat('OS threads'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 'short',
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'OS threads',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.axisPlacement',
|
||||
value: 'right',
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
|
||||
b.panel.new(109, 'Scheduler latency', 'timeseries')
|
||||
+ b.panel.withDescription('Time goroutines wait in the runnable state before they run. High values mean the exporter does not get enough CPU. Needs an exporter build with the Go scheduler metrics.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('histogram_quantile(0.99, sum by (le) (rate(go_sched_latencies_seconds_bucket{job=~"$job", instance="$instance"}[$__rate_interval])))', 'A')
|
||||
+ b.query.withLegendFormat('p99'),
|
||||
b.query.new('histogram_quantile(0.5, sum by (le) (rate(go_sched_latencies_seconds_bucket{job=~"$job", instance="$instance"}[$__rate_interval])))', 'B')
|
||||
+ b.query.withLegendFormat('p50'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 's',
|
||||
}),
|
||||
|
||||
b.panel.new(110, 'Garbage collection', 'timeseries')
|
||||
+ b.panel.withDescription('Average GC pause and GC runs per second.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('rate(go_gc_duration_seconds_sum{job=~"$job", instance="$instance"}[$__rate_interval]) / rate(go_gc_duration_seconds_count{job=~"$job", instance="$instance"}[$__rate_interval])', 'A')
|
||||
+ b.query.withLegendFormat('average pause'),
|
||||
b.query.new('rate(go_gc_duration_seconds_count{job=~"$job", instance="$instance"}[$__rate_interval])', 'B')
|
||||
+ b.query.withLegendFormat('GC runs'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 's',
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'GC runs',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'unit',
|
||||
value: 'ops',
|
||||
},
|
||||
{
|
||||
id: 'custom.axisPlacement',
|
||||
value: 'right',
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
|
||||
b.panel.new(111, 'Allocation rate', 'timeseries')
|
||||
+ b.panel.withDescription('Bytes allocated on the Go heap per second. Spikes line up with expensive collectors.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('rate(go_memstats_alloc_bytes_total{job=~"$job", instance="$instance"}[$__rate_interval])', 'A')
|
||||
+ b.query.withLegendFormat('allocated'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 'Bps',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
legend: {
|
||||
calcs: [],
|
||||
displayMode: 'list',
|
||||
},
|
||||
}),
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,710 @@
|
||||
local b = import 'builders.libsonnet';
|
||||
|
||||
b.tab(
|
||||
'Fleet',
|
||||
b.grid([
|
||||
b.place(2, 0, 0, 24, 10),
|
||||
b.place(3, 0, 10, 8, 8),
|
||||
b.place(4, 8, 10, 8, 8),
|
||||
b.place(5, 16, 10, 8, 8),
|
||||
b.place(6, 0, 18, 8, 8),
|
||||
b.place(7, 8, 18, 8, 8),
|
||||
b.place(8, 16, 18, 8, 8),
|
||||
]),
|
||||
[
|
||||
b.panel.new(2, 'Hosts', 'table')
|
||||
+ b.panel.withDescription('One row per scraped host. Click a hostname to open its details. Services up counts running services; Auto stopped counts services with start mode auto that are not running.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('max by (instance, hostname) (windows_os_hostname{job=~"$job", hostname=~"$hostname"})', 'A')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
b.query.new('(max by (instance, product, version) (windows_os_info{job=~"$job"})) and on (instance) windows_os_hostname{job=~"$job", hostname=~"$hostname"}', 'B')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
b.query.new('(max by (instance) (time() - windows_system_boot_time_timestamp{job=~"$job"})) and on (instance) windows_os_hostname{job=~"$job", hostname=~"$hostname"}', 'C')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
b.query.new('(max by (instance) (windows_cpu_logical_processor{job=~"$job"})) and on (instance) windows_os_hostname{job=~"$job", hostname=~"$hostname"}', 'D')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
b.query.new('(100 * (1 - avg by (instance) (clamp_max(rate(windows_cpu_time_total{job=~"$job", mode="idle"}[$__rate_interval]), 1)))) and on (instance) windows_os_hostname{job=~"$job", hostname=~"$hostname"}', 'E')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
b.query.new('(max by (instance) (windows_memory_physical_total_bytes{job=~"$job"})) and on (instance) windows_os_hostname{job=~"$job", hostname=~"$hostname"}', 'F')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
b.query.new('(max by (instance) (100 * (1 - windows_memory_physical_free_bytes{job=~"$job"} / windows_memory_physical_total_bytes{job=~"$job"}))) and on (instance) windows_os_hostname{job=~"$job", hostname=~"$hostname"}', 'G')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
b.query.new('(max by (instance) (100 * windows_memory_committed_bytes{job=~"$job"} / windows_memory_commit_limit{job=~"$job"})) and on (instance) windows_os_hostname{job=~"$job", hostname=~"$hostname"}', 'H')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
b.query.new('(max by (instance) (100 * (1 - windows_logical_disk_free_bytes{job=~"$job", volume!~"HarddiskVolume.*"} / windows_logical_disk_size_bytes{job=~"$job", volume!~"HarddiskVolume.*"}))) and on (instance) windows_os_hostname{job=~"$job", hostname=~"$hostname"}', 'I')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
b.query.new('(sum by (instance) (windows_service_state{job=~"$job", state="running"})) and on (instance) windows_os_hostname{job=~"$job", hostname=~"$hostname"}', 'J')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
b.query.new('(count by (instance) (windows_service_state{job=~"$job", state="running"} == 0 and on (instance, name) windows_service_start_mode{job=~"$job", start_mode="auto"} == 1)) and on (instance) windows_os_hostname{job=~"$job", hostname=~"$hostname"}', 'K')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
b.query.new('(max by (instance) (windows_system_processes{job=~"$job"})) and on (instance) windows_os_hostname{job=~"$job", hostname=~"$hostname"}', 'L')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
])
|
||||
+ b.panel.withTransformations([
|
||||
{
|
||||
group: 'joinByField',
|
||||
kind: 'Transformation',
|
||||
spec: {
|
||||
options: {
|
||||
byField: 'instance',
|
||||
mode: 'outer',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
group: 'organize',
|
||||
kind: 'Transformation',
|
||||
spec: {
|
||||
options: {
|
||||
excludeByName: {
|
||||
Time: true,
|
||||
'Time 1': true,
|
||||
'Time 10': true,
|
||||
'Time 11': true,
|
||||
'Time 12': true,
|
||||
'Time 2': true,
|
||||
'Time 3': true,
|
||||
'Time 4': true,
|
||||
'Time 5': true,
|
||||
'Time 6': true,
|
||||
'Time 7': true,
|
||||
'Time 8': true,
|
||||
'Time 9': true,
|
||||
'Value #A': true,
|
||||
'Value #B': true,
|
||||
},
|
||||
includeByName: {},
|
||||
indexByName: {
|
||||
'Value #C': 4,
|
||||
'Value #D': 5,
|
||||
'Value #E': 6,
|
||||
'Value #F': 7,
|
||||
'Value #G': 8,
|
||||
'Value #H': 9,
|
||||
'Value #I': 10,
|
||||
'Value #J': 11,
|
||||
'Value #K': 12,
|
||||
'Value #L': 13,
|
||||
hostname: 0,
|
||||
instance: 1,
|
||||
product: 2,
|
||||
version: 3,
|
||||
},
|
||||
renameByName: {
|
||||
'Value #C': 'Uptime',
|
||||
'Value #D': 'CPUs',
|
||||
'Value #E': 'CPU',
|
||||
'Value #F': 'Memory',
|
||||
'Value #G': 'Memory used',
|
||||
'Value #H': 'Commit used',
|
||||
'Value #I': 'Fullest volume',
|
||||
'Value #J': 'Services up',
|
||||
'Value #K': 'Auto stopped',
|
||||
'Value #L': 'Processes',
|
||||
hostname: 'Hostname',
|
||||
instance: 'Instance',
|
||||
product: 'OS',
|
||||
version: 'Version',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
+ b.panel.withOptions({
|
||||
footer: {
|
||||
fields: [
|
||||
'CPUs',
|
||||
'Memory',
|
||||
'Processes',
|
||||
],
|
||||
show: true,
|
||||
},
|
||||
sortBy: [
|
||||
{
|
||||
desc: false,
|
||||
displayName: 'Hostname',
|
||||
},
|
||||
],
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'hostname',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'links',
|
||||
value: [
|
||||
{
|
||||
title: 'Show details for ${__data.fields.hostname}',
|
||||
url: '/d/${__dashboard.uid}?${__url_time_range}&${datasource:queryparam}&${job:queryparam}&var-hostname=$__all&var-instance=${__data.fields.instance}&dtab=Overview',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'custom.width',
|
||||
value: 180,
|
||||
},
|
||||
{
|
||||
id: 'custom.filterable',
|
||||
value: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'Value #C',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'unit',
|
||||
value: 'dtdurations',
|
||||
},
|
||||
{
|
||||
id: 'custom.width',
|
||||
value: 90,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'instance',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.width',
|
||||
value: 140,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'product',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.width',
|
||||
value: 160,
|
||||
},
|
||||
{
|
||||
id: 'custom.filterable',
|
||||
value: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'version',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.width',
|
||||
value: 100,
|
||||
},
|
||||
{
|
||||
id: 'custom.filterable',
|
||||
value: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'Value #D',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'unit',
|
||||
value: 'none',
|
||||
},
|
||||
{
|
||||
id: 'custom.width',
|
||||
value: 60,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'Value #E',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'unit',
|
||||
value: 'percent',
|
||||
},
|
||||
{
|
||||
id: 'min',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
id: 'max',
|
||||
value: 100,
|
||||
},
|
||||
{
|
||||
id: 'thresholds',
|
||||
value: {
|
||||
mode: 'absolute',
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
},
|
||||
{
|
||||
color: 'orange',
|
||||
value: 80,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
value: 90,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'color',
|
||||
value: {
|
||||
mode: 'continuous-GrYlRd',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'custom.cellOptions',
|
||||
value: {
|
||||
mode: 'basic',
|
||||
type: 'gauge',
|
||||
valueDisplayMode: 'text',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'decimals',
|
||||
value: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'Value #F',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'unit',
|
||||
value: 'bytes',
|
||||
},
|
||||
{
|
||||
id: 'custom.width',
|
||||
value: 90,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'Value #G',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'unit',
|
||||
value: 'percent',
|
||||
},
|
||||
{
|
||||
id: 'min',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
id: 'max',
|
||||
value: 100,
|
||||
},
|
||||
{
|
||||
id: 'thresholds',
|
||||
value: {
|
||||
mode: 'absolute',
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
},
|
||||
{
|
||||
color: 'orange',
|
||||
value: 80,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
value: 90,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'color',
|
||||
value: {
|
||||
mode: 'continuous-GrYlRd',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'custom.cellOptions',
|
||||
value: {
|
||||
mode: 'basic',
|
||||
type: 'gauge',
|
||||
valueDisplayMode: 'text',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'decimals',
|
||||
value: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'Value #H',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'unit',
|
||||
value: 'percent',
|
||||
},
|
||||
{
|
||||
id: 'min',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
id: 'max',
|
||||
value: 100,
|
||||
},
|
||||
{
|
||||
id: 'thresholds',
|
||||
value: {
|
||||
mode: 'absolute',
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
},
|
||||
{
|
||||
color: 'orange',
|
||||
value: 80,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
value: 90,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'color',
|
||||
value: {
|
||||
mode: 'continuous-GrYlRd',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'custom.cellOptions',
|
||||
value: {
|
||||
mode: 'basic',
|
||||
type: 'gauge',
|
||||
valueDisplayMode: 'text',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'decimals',
|
||||
value: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'Value #I',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'unit',
|
||||
value: 'percent',
|
||||
},
|
||||
{
|
||||
id: 'min',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
id: 'max',
|
||||
value: 100,
|
||||
},
|
||||
{
|
||||
id: 'thresholds',
|
||||
value: {
|
||||
mode: 'absolute',
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
},
|
||||
{
|
||||
color: 'orange',
|
||||
value: 80,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
value: 90,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'color',
|
||||
value: {
|
||||
mode: 'continuous-GrYlRd',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'custom.cellOptions',
|
||||
value: {
|
||||
mode: 'basic',
|
||||
type: 'gauge',
|
||||
valueDisplayMode: 'text',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'decimals',
|
||||
value: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'Value #J',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'unit',
|
||||
value: 'none',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'Value #K',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'unit',
|
||||
value: 'none',
|
||||
},
|
||||
{
|
||||
id: 'thresholds',
|
||||
value: {
|
||||
mode: 'absolute',
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
},
|
||||
{
|
||||
color: 'orange',
|
||||
value: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'custom.cellOptions',
|
||||
value: {
|
||||
type: 'color-text',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'noValue',
|
||||
value: '0',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'Value #L',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'unit',
|
||||
value: 'none',
|
||||
},
|
||||
{
|
||||
id: 'custom.width',
|
||||
value: 90,
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
|
||||
b.panel.new(3, 'CPU utilization', 'timeseries')
|
||||
+ b.panel.withDescription('Shows the 25 highest hosts at each point in time.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('topk(25, (100 * (1 - avg by (instance) (clamp_max(rate(windows_cpu_time_total{job=~"$job", mode="idle"}[$__rate_interval]), 1)))) * on (instance) group_left (hostname) max by (instance, hostname) (windows_os_hostname{job=~"$job", hostname=~"$hostname"}))', 'A')
|
||||
+ b.query.withLegendFormat('{{hostname}}'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
max: 100,
|
||||
min: 0,
|
||||
unit: 'percent',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
legend: {
|
||||
calcs: [],
|
||||
displayMode: 'list',
|
||||
},
|
||||
}),
|
||||
|
||||
b.panel.new(4, 'Memory utilization', 'timeseries')
|
||||
+ b.panel.withDescription('Shows the 25 highest hosts at each point in time.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('topk(25, (max by (instance) (100 * (1 - windows_memory_physical_free_bytes{job=~"$job"} / windows_memory_physical_total_bytes{job=~"$job"}))) * on (instance) group_left (hostname) max by (instance, hostname) (windows_os_hostname{job=~"$job", hostname=~"$hostname"}))', 'A')
|
||||
+ b.query.withLegendFormat('{{hostname}}'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
max: 100,
|
||||
min: 0,
|
||||
unit: 'percent',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
legend: {
|
||||
calcs: [],
|
||||
displayMode: 'list',
|
||||
},
|
||||
}),
|
||||
|
||||
b.panel.new(5, 'Fullest volume', 'timeseries')
|
||||
+ b.panel.withDescription('Usage of the fullest volume per host. Shows the 25 highest hosts at each point in time.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('topk(25, (max by (instance) (100 * (1 - windows_logical_disk_free_bytes{job=~"$job", volume!~"HarddiskVolume.*"} / windows_logical_disk_size_bytes{job=~"$job", volume!~"HarddiskVolume.*"}))) * on (instance) group_left (hostname) max by (instance, hostname) (windows_os_hostname{job=~"$job", hostname=~"$hostname"}))', 'A')
|
||||
+ b.query.withLegendFormat('{{hostname}}'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
custom: {
|
||||
thresholdsStyle: {
|
||||
mode: 'dashed',
|
||||
},
|
||||
},
|
||||
max: 100,
|
||||
min: 0,
|
||||
thresholds: {
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
color: 'orange',
|
||||
value: 80,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
value: 90,
|
||||
},
|
||||
],
|
||||
},
|
||||
unit: 'percent',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
legend: {
|
||||
calcs: [],
|
||||
displayMode: 'list',
|
||||
},
|
||||
}),
|
||||
|
||||
b.panel.new(6, 'Network throughput', 'timeseries')
|
||||
+ b.panel.withDescription('Sum over all interfaces. Received is drawn below the axis. Shows the 25 highest hosts at each point in time.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('topk(25, (sum by (instance) (rate(windows_net_bytes_sent_total{job=~"$job"}[$__rate_interval]) * 8)) * on (instance) group_left (hostname) max by (instance, hostname) (windows_os_hostname{job=~"$job", hostname=~"$hostname"}))', 'A')
|
||||
+ b.query.withLegendFormat('{{hostname}} sent'),
|
||||
b.query.new('topk(25, (sum by (instance) (rate(windows_net_bytes_received_total{job=~"$job"}[$__rate_interval]) * 8)) * on (instance) group_left (hostname) max by (instance, hostname) (windows_os_hostname{job=~"$job", hostname=~"$hostname"}))', 'B')
|
||||
+ b.query.withLegendFormat('{{hostname}} received'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: 'bps',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
legend: {
|
||||
calcs: [],
|
||||
displayMode: 'list',
|
||||
},
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byRegexp',
|
||||
options: '.* received$',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.transform',
|
||||
value: 'negative-Y',
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
|
||||
b.panel.new(7, 'Disk throughput', 'timeseries')
|
||||
+ b.panel.withDescription('Sum over all volumes. Read is drawn below the axis. Shows the 25 highest hosts at each point in time.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('topk(25, (sum by (instance) (rate(windows_logical_disk_write_bytes_total{job=~"$job", volume!~"HarddiskVolume.*"}[$__rate_interval]))) * on (instance) group_left (hostname) max by (instance, hostname) (windows_os_hostname{job=~"$job", hostname=~"$hostname"}))', 'A')
|
||||
+ b.query.withLegendFormat('{{hostname}} write'),
|
||||
b.query.new('topk(25, (sum by (instance) (rate(windows_logical_disk_read_bytes_total{job=~"$job", volume!~"HarddiskVolume.*"}[$__rate_interval]))) * on (instance) group_left (hostname) max by (instance, hostname) (windows_os_hostname{job=~"$job", hostname=~"$hostname"}))', 'B')
|
||||
+ b.query.withLegendFormat('{{hostname}} read'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: 'Bps',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
legend: {
|
||||
calcs: [],
|
||||
displayMode: 'list',
|
||||
},
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byRegexp',
|
||||
options: '.* read$',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.transform',
|
||||
value: 'negative-Y',
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
|
||||
b.panel.new(8, 'Network errors and discards', 'timeseries')
|
||||
+ b.panel.withDescription('Sum over all interfaces of received and outbound errors and discards. Shows the 25 highest hosts at each point in time.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('topk(25, (sum by (instance) (rate(windows_net_packets_received_errors_total{job=~"$job"}[$__rate_interval]) + rate(windows_net_packets_received_discarded_total{job=~"$job"}[$__rate_interval]) + rate(windows_net_packets_outbound_errors_total{job=~"$job"}[$__rate_interval]) + rate(windows_net_packets_outbound_discarded_total{job=~"$job"}[$__rate_interval]))) * on (instance) group_left (hostname) max by (instance, hostname) (windows_os_hostname{job=~"$job", hostname=~"$hostname"}))', 'A')
|
||||
+ b.query.withLegendFormat('{{hostname}}'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 'pps',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
legend: {
|
||||
calcs: [],
|
||||
displayMode: 'list',
|
||||
},
|
||||
}),
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,279 @@
|
||||
local b = import 'builders.libsonnet';
|
||||
|
||||
b.tab(
|
||||
'GPU',
|
||||
b.grid([
|
||||
b.place(57, 0, 0, 24, 5),
|
||||
b.place(58, 0, 5, 12, 8),
|
||||
b.place(59, 12, 5, 12, 8),
|
||||
b.place(60, 0, 13, 12, 8),
|
||||
b.place(61, 12, 13, 12, 8),
|
||||
]),
|
||||
[
|
||||
b.panel.new(57, 'GPUs', 'table')
|
||||
+ b.panel.withDescription('Needs the gpu collector, which is not enabled by default. Utilization is the busiest engine of the GPU.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('max by (luid, name) (windows_gpu_info{job=~"$job", instance="$instance"})', 'A')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
b.query.new('clamp_max(max by (luid) (sum by (luid, eng, engtype) (rate(windows_gpu_engine_time_seconds{job=~"$job", instance="$instance"}[$__rate_interval]))), 1)', 'B')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
b.query.new('max by (luid) (windows_gpu_adapter_memory_dedicated_bytes{job=~"$job", instance="$instance"})', 'C')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
b.query.new('max by (luid) (windows_gpu_dedicated_video_memory_size_bytes{job=~"$job", instance="$instance"})', 'D')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
b.query.new('max by (luid) (windows_gpu_adapter_memory_shared_bytes{job=~"$job", instance="$instance"})', 'E')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
b.query.new('max by (luid) (windows_gpu_shared_system_memory_size_bytes{job=~"$job", instance="$instance"})', 'F')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
])
|
||||
+ b.panel.withTransformations([
|
||||
{
|
||||
group: 'joinByField',
|
||||
kind: 'Transformation',
|
||||
spec: {
|
||||
options: {
|
||||
byField: 'luid',
|
||||
mode: 'outer',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
group: 'organize',
|
||||
kind: 'Transformation',
|
||||
spec: {
|
||||
options: {
|
||||
excludeByName: {
|
||||
Time: true,
|
||||
'Time 1': true,
|
||||
'Time 2': true,
|
||||
'Time 3': true,
|
||||
'Time 4': true,
|
||||
'Time 5': true,
|
||||
'Time 6': true,
|
||||
'Value #A': true,
|
||||
luid: true,
|
||||
},
|
||||
includeByName: {},
|
||||
indexByName: {
|
||||
'Value #B': 1,
|
||||
'Value #C': 2,
|
||||
'Value #D': 3,
|
||||
'Value #E': 4,
|
||||
'Value #F': 5,
|
||||
name: 0,
|
||||
},
|
||||
renameByName: {
|
||||
'Value #B': 'Utilization',
|
||||
'Value #C': 'Dedicated memory used',
|
||||
'Value #D': 'Dedicated memory',
|
||||
'Value #E': 'Shared memory used',
|
||||
'Value #F': 'Shared memory',
|
||||
name: 'GPU',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
+ b.panel.withOptions({
|
||||
sortBy: [],
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'Value #B',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'unit',
|
||||
value: 'percentunit',
|
||||
},
|
||||
{
|
||||
id: 'min',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
id: 'max',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
id: 'thresholds',
|
||||
value: {
|
||||
mode: 'absolute',
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
},
|
||||
{
|
||||
color: 'orange',
|
||||
value: 80,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
value: 90,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'color',
|
||||
value: {
|
||||
mode: 'continuous-GrYlRd',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'custom.cellOptions',
|
||||
value: {
|
||||
mode: 'basic',
|
||||
type: 'gauge',
|
||||
valueDisplayMode: 'text',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'decimals',
|
||||
value: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'Value #C',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'unit',
|
||||
value: 'bytes',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'Value #D',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'unit',
|
||||
value: 'bytes',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'Value #E',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'unit',
|
||||
value: 'bytes',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'Value #F',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'unit',
|
||||
value: 'bytes',
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
|
||||
b.panel.new(58, 'GPU utilization by engine type', 'timeseries')
|
||||
+ b.panel.withDescription('Busiest engine of each engine type, as in Task Manager.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('clamp_max(max by (luid, engtype) (sum by (luid, eng, engtype) (rate(windows_gpu_engine_time_seconds{job=~"$job", instance="$instance"}[$__rate_interval]))), 1) * on (luid) group_left (name) max by (luid, name) (windows_gpu_info{job=~"$job", instance="$instance"})', 'A')
|
||||
+ b.query.withLegendFormat('{{name}} {{engtype}}'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
max: 1,
|
||||
min: 0,
|
||||
}),
|
||||
|
||||
b.panel.new(59, 'GPU memory', 'timeseries')
|
||||
+ b.panel.withDescription('Dedicated (video) and shared (system) memory in use, against the size of each pool.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('max by (luid) (windows_gpu_adapter_memory_dedicated_bytes{job=~"$job", instance="$instance"}) * on (luid) group_left (name) max by (luid, name) (windows_gpu_info{job=~"$job", instance="$instance"})', 'A')
|
||||
+ b.query.withLegendFormat('{{name}} dedicated used'),
|
||||
b.query.new('max by (luid) (windows_gpu_dedicated_video_memory_size_bytes{job=~"$job", instance="$instance"}) * on (luid) group_left (name) max by (luid, name) (windows_gpu_info{job=~"$job", instance="$instance"})', 'B')
|
||||
+ b.query.withLegendFormat('{{name}} dedicated size'),
|
||||
b.query.new('max by (luid) (windows_gpu_adapter_memory_shared_bytes{job=~"$job", instance="$instance"}) * on (luid) group_left (name) max by (luid, name) (windows_gpu_info{job=~"$job", instance="$instance"})', 'C')
|
||||
+ b.query.withLegendFormat('{{name}} shared used'),
|
||||
b.query.new('max by (luid) (windows_gpu_shared_system_memory_size_bytes{job=~"$job", instance="$instance"}) * on (luid) group_left (name) max by (luid, name) (windows_gpu_info{job=~"$job", instance="$instance"})', 'D')
|
||||
+ b.query.withLegendFormat('{{name}} shared size'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 'bytes',
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byRegexp',
|
||||
options: '.* size',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.fillOpacity',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
id: 'custom.lineStyle',
|
||||
value: {
|
||||
dash: [
|
||||
10,
|
||||
10,
|
||||
],
|
||||
fill: 'dash',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
|
||||
b.panel.new(60, 'Top 10 processes by GPU utilization', 'timeseries')
|
||||
+ b.panel.withDescription('Busiest GPU engine used by each process. Process names need the process collector; otherwise only the PID is shown.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('topk(10, (max by (process_id) (sum by (process_id, luid, eng) (rate(windows_gpu_engine_time_seconds{job=~"$job", instance="$instance"}[$__rate_interval]))) * on (process_id) group_left (process) max by (process_id, process) (windows_process_info{job=~"$job", instance="$instance"})) or on (process_id) max by (process_id) (sum by (process_id, luid, eng) (rate(windows_gpu_engine_time_seconds{job=~"$job", instance="$instance"}[$__rate_interval]))))', 'A')
|
||||
+ b.query.withLegendFormat('{{process}} ({{process_id}})'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
legend: {
|
||||
sortBy: 'Mean',
|
||||
sortDesc: true,
|
||||
},
|
||||
}),
|
||||
|
||||
b.panel.new(61, 'Top 10 processes by dedicated GPU memory', 'timeseries')
|
||||
+ b.panel.withDescription('Process names need the process collector; otherwise only the PID is shown.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('topk(10, (sum by (process_id) (windows_gpu_process_memory_dedicated_bytes{job=~"$job", instance="$instance"}) * on (process_id) group_left (process) max by (process_id, process) (windows_process_info{job=~"$job", instance="$instance"})) or on (process_id) sum by (process_id) (windows_gpu_process_memory_dedicated_bytes{job=~"$job", instance="$instance"}))', 'A')
|
||||
+ b.query.withLegendFormat('{{process}} ({{process_id}})'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 'bytes',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
legend: {
|
||||
sortBy: 'Mean',
|
||||
sortDesc: true,
|
||||
},
|
||||
}),
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,380 @@
|
||||
local b = import 'builders.libsonnet';
|
||||
|
||||
b.tab(
|
||||
'Hyper-V',
|
||||
b.rows([
|
||||
b.row('Host', b.grid([
|
||||
b.place(63, 0, 0, 4, 4),
|
||||
b.place(64, 4, 0, 4, 4),
|
||||
b.place(65, 8, 0, 4, 4),
|
||||
b.place(66, 12, 0, 4, 4),
|
||||
b.place(67, 16, 0, 4, 4),
|
||||
b.place(68, 20, 0, 4, 4),
|
||||
b.place(69, 0, 4, 12, 8),
|
||||
b.place(70, 12, 4, 12, 8),
|
||||
])),
|
||||
b.row('Memory', b.grid([
|
||||
b.place(72, 0, 0, 8, 8),
|
||||
b.place(73, 8, 0, 8, 8),
|
||||
b.place(74, 16, 0, 8, 8),
|
||||
])),
|
||||
b.row('Storage', b.grid([
|
||||
b.place(76, 0, 0, 8, 8),
|
||||
b.place(77, 8, 0, 8, 8),
|
||||
b.place(78, 16, 0, 8, 8),
|
||||
])),
|
||||
b.row('Network', b.grid([
|
||||
b.place(80, 0, 0, 8, 8),
|
||||
b.place(81, 8, 0, 8, 8),
|
||||
b.place(82, 16, 0, 8, 8),
|
||||
])),
|
||||
]),
|
||||
[
|
||||
b.panel.new(63, 'Virtual machines', 'stat')
|
||||
+ b.panel.withDescription('Needs the hyperv collector, which is not enabled by default.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('sum(windows_hyperv_virtual_machine_health_total_count{job=~"$job", instance="$instance"})', 'A')
|
||||
+ b.query.withInstant(),
|
||||
]),
|
||||
|
||||
b.panel.new(64, 'VMs in critical health', 'stat')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('sum(windows_hyperv_virtual_machine_health_total_count{job=~"$job", instance="$instance", state="critical"})', 'A')
|
||||
+ b.query.withInstant(),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
thresholds: {
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
value: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
|
||||
b.panel.new(65, 'Hyper-V WMI', 'stat')
|
||||
+ b.panel.withDescription('Whether the Hyper-V WMI namespace answers. When it stops answering, Hyper-V Manager and Failover Cluster Manager usually cannot manage the host.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_hyperv_wmi_health{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withInstant(),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
mappings: [
|
||||
{
|
||||
options: {
|
||||
'0': {
|
||||
index: 0,
|
||||
text: 'not responding',
|
||||
},
|
||||
'1': {
|
||||
index: 1,
|
||||
text: 'ok',
|
||||
},
|
||||
},
|
||||
type: 'value',
|
||||
},
|
||||
],
|
||||
thresholds: {
|
||||
steps: [
|
||||
{
|
||||
color: 'red',
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
color: 'green',
|
||||
value: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
unit: '',
|
||||
}),
|
||||
|
||||
b.panel.new(66, 'Logical processors', 'stat')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_hyperv_host_logical_processor_count{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withInstant(),
|
||||
]),
|
||||
|
||||
b.panel.new(67, 'Virtual processors', 'stat')
|
||||
+ b.panel.withDescription('Virtual processors assigned to all VMs.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_hyperv_total_vm_processor_count{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withInstant(),
|
||||
]),
|
||||
|
||||
b.panel.new(68, 'vCPU per logical CPU', 'stat')
|
||||
+ b.panel.withDescription('Virtual processors assigned to VMs per logical processor of the host.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_hyperv_total_vm_processor_count{job=~"$job", instance="$instance"} / windows_hyperv_host_logical_processor_count{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withInstant(),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
decimals: 2,
|
||||
}),
|
||||
|
||||
b.panel.new(69, 'Host CPU time by state', 'timeseries')
|
||||
+ b.panel.withDescription('Share of all logical processors spent running guest code (VMs and the root partition) and in the hypervisor. With Hyper-V enabled, the CPU tab only sees the root partition.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('sum by (state) (rate(windows_hyperv_hypervisor_logical_processor_time_total{job=~"$job", instance="$instance", state!="idle"}[$__rate_interval])) / scalar(count(windows_hyperv_hypervisor_logical_processor_time_total{job=~"$job", instance="$instance", state="idle"}))', 'A')
|
||||
+ b.query.withLegendFormat('{{state}}'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
custom: {
|
||||
fillOpacity: 40,
|
||||
stacking: {
|
||||
mode: 'normal',
|
||||
},
|
||||
},
|
||||
max: 1,
|
||||
min: 0,
|
||||
}),
|
||||
|
||||
b.panel.new(70, 'CPU usage per VM', 'timeseries')
|
||||
+ b.panel.withDescription('Virtual processor run time of each VM as a share of all logical processors of the host.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('sum by (vm) (rate(windows_hyperv_hypervisor_virtual_processor_run_time_total{job=~"$job", instance="$instance"}[$__rate_interval])) / scalar(max(windows_hyperv_host_logical_processor_count{job=~"$job", instance="$instance"}))', 'A')
|
||||
+ b.query.withLegendFormat('{{vm}}'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
custom: {
|
||||
fillOpacity: 40,
|
||||
stacking: {
|
||||
mode: 'normal',
|
||||
},
|
||||
},
|
||||
min: 0,
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
legend: {
|
||||
sortBy: 'Mean',
|
||||
sortDesc: true,
|
||||
},
|
||||
}),
|
||||
|
||||
b.panel.new(72, 'Memory assigned to VMs', 'timeseries')
|
||||
+ b.panel.withDescription('Physical memory currently assigned to each VM with dynamic memory.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_hyperv_dynamic_memory_vm_physical_bytes{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withLegendFormat('{{vm}}'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
custom: {
|
||||
fillOpacity: 40,
|
||||
stacking: {
|
||||
mode: 'normal',
|
||||
},
|
||||
},
|
||||
min: 0,
|
||||
unit: 'bytes',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
legend: {
|
||||
sortBy: 'Mean',
|
||||
sortDesc: true,
|
||||
},
|
||||
}),
|
||||
|
||||
b.panel.new(73, 'VM memory pressure', 'timeseries')
|
||||
+ b.panel.withDescription('Memory a VM needs as a share of the memory it has. Above 100% the VM needs more memory than it is assigned.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_hyperv_dynamic_memory_vm_pressure_current_ratio{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withLegendFormat('{{vm}}'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
custom: {
|
||||
thresholdsStyle: {
|
||||
mode: 'dashed',
|
||||
},
|
||||
},
|
||||
min: 0,
|
||||
thresholds: {
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
value: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
|
||||
b.panel.new(74, 'Memory available for VMs', 'timeseries')
|
||||
+ b.panel.withDescription('Memory the dynamic memory balancer can still hand out to VMs.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_hyperv_dynamic_memory_balancer_available_memory_bytes{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withLegendFormat('{{balancer}}'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 'bytes',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
legend: {
|
||||
calcs: [],
|
||||
displayMode: 'list',
|
||||
},
|
||||
}),
|
||||
|
||||
b.panel.new(76, 'Virtual disk throughput', 'timeseries')
|
||||
+ b.panel.withDescription('Reads are drawn below the axis.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('rate(windows_hyperv_virtual_storage_device_bytes_written{job=~"$job", instance="$instance"}[$__rate_interval])', 'A')
|
||||
+ b.query.withLegendFormat('{{device}} write'),
|
||||
b.query.new('rate(windows_hyperv_virtual_storage_device_bytes_read{job=~"$job", instance="$instance"}[$__rate_interval])', 'B')
|
||||
+ b.query.withLegendFormat('{{device}} read'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: 'Bps',
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byRegexp',
|
||||
options: '.* read$',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.transform',
|
||||
value: 'negative-Y',
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
|
||||
b.panel.new(77, 'Virtual disk IOPS', 'timeseries')
|
||||
+ b.panel.withDescription('Reads are drawn below the axis.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('rate(windows_hyperv_virtual_storage_device_operations_written_total{job=~"$job", instance="$instance"}[$__rate_interval])', 'A')
|
||||
+ b.query.withLegendFormat('{{device}} write'),
|
||||
b.query.new('rate(windows_hyperv_virtual_storage_device_operations_read_total{job=~"$job", instance="$instance"}[$__rate_interval])', 'B')
|
||||
+ b.query.withLegendFormat('{{device}} read'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: 'iops',
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byRegexp',
|
||||
options: '.* read$',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.transform',
|
||||
value: 'negative-Y',
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
|
||||
b.panel.new(78, 'Virtual disk latency and errors', 'timeseries')
|
||||
+ b.panel.withDescription('Average I/O latency of each virtual disk. windows_hyperv_virtual_storage_device_latency_seconds holds the raw, growing counter in 100 ns ticks, so the panel divides its rate by the rate of I/O operations. Errors use the right axis.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('rate(windows_hyperv_virtual_storage_device_latency_seconds{job=~"$job", instance="$instance"}[$__rate_interval]) / (rate(windows_hyperv_virtual_storage_device_operations_read_total{job=~"$job", instance="$instance"}[$__rate_interval]) + rate(windows_hyperv_virtual_storage_device_operations_written_total{job=~"$job", instance="$instance"}[$__rate_interval])) / 1e7', 'A')
|
||||
+ b.query.withLegendFormat('{{device}} latency'),
|
||||
b.query.new('rate(windows_hyperv_virtual_storage_device_error_count_total{job=~"$job", instance="$instance"}[$__rate_interval]) > 0', 'B')
|
||||
+ b.query.withLegendFormat('{{device}} errors'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 's',
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byRegexp',
|
||||
options: '.* errors',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'unit',
|
||||
value: 'ops',
|
||||
},
|
||||
{
|
||||
id: 'custom.axisPlacement',
|
||||
value: 'right',
|
||||
},
|
||||
{
|
||||
id: 'color',
|
||||
value: {
|
||||
fixedColor: 'red',
|
||||
mode: 'fixed',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
|
||||
b.panel.new(80, 'Virtual switch throughput', 'timeseries')
|
||||
+ b.panel.withDescription('Received traffic is drawn below the axis.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('rate(windows_hyperv_vswitch_bytes_sent_total{job=~"$job", instance="$instance"}[$__rate_interval]) * 8', 'A')
|
||||
+ b.query.withLegendFormat('{{vswitch}} sent'),
|
||||
b.query.new('rate(windows_hyperv_vswitch_bytes_received_total{job=~"$job", instance="$instance"}[$__rate_interval]) * 8', 'B')
|
||||
+ b.query.withLegendFormat('{{vswitch}} received'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: 'bps',
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byRegexp',
|
||||
options: '.* received$',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.transform',
|
||||
value: 'negative-Y',
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
|
||||
b.panel.new(81, 'Virtual switch dropped packets', 'timeseries')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('rate(windows_hyperv_vswitch_dropped_packets_incoming_total{job=~"$job", instance="$instance"}[$__rate_interval])', 'A')
|
||||
+ b.query.withLegendFormat('{{vswitch}} incoming'),
|
||||
b.query.new('rate(windows_hyperv_vswitch_dropped_packets_outcoming_total{job=~"$job", instance="$instance"}[$__rate_interval])', 'B')
|
||||
+ b.query.withLegendFormat('{{vswitch}} outgoing'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 'pps',
|
||||
}),
|
||||
|
||||
b.panel.new(82, 'VM network adapter throughput', 'timeseries')
|
||||
+ b.panel.withDescription('Received traffic is drawn below the axis.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('rate(windows_hyperv_virtual_network_adapter_sent_bytes_total{job=~"$job", instance="$instance"}[$__rate_interval]) * 8', 'A')
|
||||
+ b.query.withLegendFormat('{{adapter}} sent'),
|
||||
b.query.new('rate(windows_hyperv_virtual_network_adapter_received_bytes_total{job=~"$job", instance="$instance"}[$__rate_interval]) * 8', 'B')
|
||||
+ b.query.withLegendFormat('{{adapter}} received'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: 'bps',
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byRegexp',
|
||||
options: '.* received$',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.transform',
|
||||
value: 'negative-Y',
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,135 @@
|
||||
local b = import 'builders.libsonnet';
|
||||
|
||||
b.tab(
|
||||
'Memory',
|
||||
b.grid([
|
||||
b.place(32, 0, 0, 12, 8),
|
||||
b.place(33, 12, 0, 12, 8),
|
||||
b.place(34, 0, 8, 12, 8),
|
||||
b.place(35, 12, 8, 12, 8),
|
||||
]),
|
||||
[
|
||||
b.panel.new(32, 'Physical memory', 'timeseries')
|
||||
+ b.panel.withDescription('Available memory is the standby (cache), free and zero page lists.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_memory_physical_total_bytes{job=~"$job", instance="$instance"} - windows_memory_physical_free_bytes{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withLegendFormat('used'),
|
||||
b.query.new('windows_memory_physical_free_bytes{job=~"$job", instance="$instance"}', 'B')
|
||||
+ b.query.withLegendFormat('available'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
custom: {
|
||||
fillOpacity: 40,
|
||||
stacking: {
|
||||
mode: 'normal',
|
||||
},
|
||||
},
|
||||
min: 0,
|
||||
unit: 'bytes',
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'used',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'color',
|
||||
value: {
|
||||
fixedColor: 'orange',
|
||||
mode: 'fixed',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'available',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'color',
|
||||
value: {
|
||||
fixedColor: 'green',
|
||||
mode: 'fixed',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
|
||||
b.panel.new(33, 'Commit charge', 'timeseries')
|
||||
+ b.panel.withDescription('Committed virtual memory against the commit limit (physical memory + page files).')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_memory_committed_bytes{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withLegendFormat('committed'),
|
||||
b.query.new('windows_memory_commit_limit{job=~"$job", instance="$instance"}', 'B')
|
||||
+ b.query.withLegendFormat('limit'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 'bytes',
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'limit',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'color',
|
||||
value: {
|
||||
fixedColor: 'red',
|
||||
mode: 'fixed',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'custom.fillOpacity',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
id: 'custom.lineStyle',
|
||||
value: {
|
||||
dash: [
|
||||
10,
|
||||
10,
|
||||
],
|
||||
fill: 'dash',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
|
||||
b.panel.new(34, 'Paging', 'timeseries')
|
||||
+ b.panel.withDescription('Hard page faults read from or written to disk. Sustained page reads indicate memory pressure.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('rate(windows_memory_swap_page_reads_total{job=~"$job", instance="$instance"}[$__rate_interval])', 'A')
|
||||
+ b.query.withLegendFormat('page reads'),
|
||||
b.query.new('rate(windows_memory_swap_page_writes_total{job=~"$job", instance="$instance"}[$__rate_interval])', 'B')
|
||||
+ b.query.withLegendFormat('page writes'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 'ops',
|
||||
}),
|
||||
|
||||
b.panel.new(35, 'Kernel pools and cache', 'timeseries')
|
||||
+ b.panel.withDescription('A steadily growing nonpaged pool usually means a driver leaks memory.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_memory_pool_nonpaged_bytes{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withLegendFormat('nonpaged pool'),
|
||||
b.query.new('windows_memory_pool_paged_bytes{job=~"$job", instance="$instance"}', 'B')
|
||||
+ b.query.withLegendFormat('paged pool'),
|
||||
b.query.new('windows_memory_cache_bytes{job=~"$job", instance="$instance"}', 'C')
|
||||
+ b.query.withLegendFormat('system cache'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 'bytes',
|
||||
}),
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,108 @@
|
||||
local b = import 'builders.libsonnet';
|
||||
|
||||
b.tab(
|
||||
'Network',
|
||||
b.grid([
|
||||
b.place(45, 0, 0, 12, 8),
|
||||
b.place(46, 12, 0, 12, 8),
|
||||
b.place(47, 0, 8, 12, 8),
|
||||
b.place(48, 12, 8, 12, 8),
|
||||
]),
|
||||
[
|
||||
b.panel.new(45, 'Network throughput', 'timeseries')
|
||||
+ b.panel.withDescription('Received traffic is drawn below the axis.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('rate(windows_net_bytes_sent_total{job=~"$job", instance="$instance", nic=~"$nic"}[$__rate_interval]) * 8', 'A')
|
||||
+ b.query.withLegendFormat('{{nic}} sent'),
|
||||
b.query.new('rate(windows_net_bytes_received_total{job=~"$job", instance="$instance", nic=~"$nic"}[$__rate_interval]) * 8', 'B')
|
||||
+ b.query.withLegendFormat('{{nic}} received'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: 'bps',
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byRegexp',
|
||||
options: '.* received$',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.transform',
|
||||
value: 'negative-Y',
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
|
||||
b.panel.new(46, 'Network utilization', 'timeseries')
|
||||
+ b.panel.withDescription('Sent plus received traffic relative to the link speed reported by the adapter. Interfaces without a link speed are not shown.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('rate(windows_net_bytes_total{job=~"$job", instance="$instance", nic=~"$nic"}[$__rate_interval]) / (windows_net_current_bandwidth_bytes{job=~"$job", instance="$instance", nic=~"$nic"} > 0)', 'A')
|
||||
+ b.query.withLegendFormat('{{nic}}'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
thresholds: {
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
color: 'orange',
|
||||
value: 80,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
value: 90,
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
|
||||
b.panel.new(47, 'Packets', 'timeseries')
|
||||
+ b.panel.withDescription('Received packets are drawn below the axis.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('rate(windows_net_packets_sent_total{job=~"$job", instance="$instance", nic=~"$nic"}[$__rate_interval])', 'A')
|
||||
+ b.query.withLegendFormat('{{nic}} sent'),
|
||||
b.query.new('rate(windows_net_packets_received_total{job=~"$job", instance="$instance", nic=~"$nic"}[$__rate_interval])', 'B')
|
||||
+ b.query.withLegendFormat('{{nic}} received'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: 'pps',
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byRegexp',
|
||||
options: '.* received$',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.transform',
|
||||
value: 'negative-Y',
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
|
||||
b.panel.new(48, 'Network errors and discards', 'timeseries')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('rate(windows_net_packets_received_errors_total{job=~"$job", instance="$instance", nic=~"$nic"}[$__rate_interval])', 'A')
|
||||
+ b.query.withLegendFormat('{{nic}} received errors'),
|
||||
b.query.new('rate(windows_net_packets_received_discarded_total{job=~"$job", instance="$instance", nic=~"$nic"}[$__rate_interval])', 'B')
|
||||
+ b.query.withLegendFormat('{{nic}} received discarded'),
|
||||
b.query.new('rate(windows_net_packets_outbound_errors_total{job=~"$job", instance="$instance", nic=~"$nic"}[$__rate_interval])', 'C')
|
||||
+ b.query.withLegendFormat('{{nic}} outbound errors'),
|
||||
b.query.new('rate(windows_net_packets_outbound_discarded_total{job=~"$job", instance="$instance", nic=~"$nic"}[$__rate_interval])', 'D')
|
||||
+ b.query.withLegendFormat('{{nic}} outbound discarded'),
|
||||
b.query.new('rate(windows_net_packets_received_unknown_total{job=~"$job", instance="$instance", nic=~"$nic"}[$__rate_interval])', 'E')
|
||||
+ b.query.withLegendFormat('{{nic}} received unknown protocol'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 'pps',
|
||||
}),
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,490 @@
|
||||
local b = import 'builders.libsonnet';
|
||||
|
||||
b.tab(
|
||||
'Overview',
|
||||
b.grid([
|
||||
b.place(10, 0, 0, 4, 4),
|
||||
b.place(11, 4, 0, 3, 4),
|
||||
b.place(12, 7, 0, 2, 4),
|
||||
b.place(13, 9, 0, 2, 4),
|
||||
b.place(14, 11, 0, 3, 4),
|
||||
b.place(15, 14, 0, 3, 4),
|
||||
b.place(16, 17, 0, 3, 4),
|
||||
b.place(17, 20, 0, 4, 4),
|
||||
b.place(18, 0, 4, 12, 8),
|
||||
b.place(19, 12, 4, 12, 8),
|
||||
b.place(20, 0, 12, 8, 8),
|
||||
b.place(21, 8, 12, 8, 8),
|
||||
b.place(22, 16, 12, 8, 8),
|
||||
]),
|
||||
[
|
||||
b.panel.new(10, 'Operating system', 'stat')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_os_info{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: '',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
colorMode: 'none',
|
||||
reduceOptions: {
|
||||
fields: '/^product$/',
|
||||
},
|
||||
textMode: 'value',
|
||||
}),
|
||||
|
||||
b.panel.new(11, 'Uptime', 'stat')
|
||||
+ b.panel.withDescription('Time since the last boot.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('time() - windows_system_boot_time_timestamp{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withInstant(),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
thresholds: {
|
||||
steps: [
|
||||
{
|
||||
color: 'orange',
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
color: 'text',
|
||||
value: 3600,
|
||||
},
|
||||
],
|
||||
},
|
||||
unit: 'dtdurations',
|
||||
}),
|
||||
|
||||
b.panel.new(12, 'CPUs', 'stat')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_cpu_logical_processor{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withInstant(),
|
||||
]),
|
||||
|
||||
b.panel.new(13, 'RAM', 'stat')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_memory_physical_total_bytes{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withInstant(),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
decimals: 1,
|
||||
unit: 'bytes',
|
||||
}),
|
||||
|
||||
b.panel.new(14, 'CPU busy', 'stat')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('100 * (1 - avg by (instance) (clamp_max(rate(windows_cpu_time_total{job=~"$job", instance="$instance", mode="idle"}[$__rate_interval]), 1)))', 'A'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
decimals: 1,
|
||||
max: 100,
|
||||
min: 0,
|
||||
thresholds: {
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
color: 'orange',
|
||||
value: 80,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
value: 90,
|
||||
},
|
||||
],
|
||||
},
|
||||
unit: 'percent',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
graphMode: 'area',
|
||||
}),
|
||||
|
||||
b.panel.new(15, 'Memory used', 'stat')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('100 * (1 - windows_memory_physical_free_bytes{job=~"$job", instance="$instance"} / windows_memory_physical_total_bytes{job=~"$job", instance="$instance"})', 'A'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
decimals: 1,
|
||||
max: 100,
|
||||
min: 0,
|
||||
thresholds: {
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
color: 'orange',
|
||||
value: 80,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
value: 90,
|
||||
},
|
||||
],
|
||||
},
|
||||
unit: 'percent',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
graphMode: 'area',
|
||||
}),
|
||||
|
||||
b.panel.new(16, 'Commit used', 'stat')
|
||||
+ b.panel.withDescription('Committed virtual memory as a share of the commit limit (physical memory + page files). Allocations fail at 100%.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('100 * windows_memory_committed_bytes{job=~"$job", instance="$instance"} / windows_memory_commit_limit{job=~"$job", instance="$instance"}', 'A'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
decimals: 1,
|
||||
max: 100,
|
||||
min: 0,
|
||||
thresholds: {
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
color: 'orange',
|
||||
value: 80,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
value: 90,
|
||||
},
|
||||
],
|
||||
},
|
||||
unit: 'percent',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
graphMode: 'area',
|
||||
}),
|
||||
|
||||
b.panel.new(17, 'Stopped auto services', 'stat')
|
||||
+ b.panel.withDescription('Services with start mode "auto" that are not running. See the Services tab for names.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('count(windows_service_state{job=~"$job", instance="$instance", state="running"} == 0 and on (name) windows_service_start_mode{job=~"$job", instance="$instance", start_mode="auto"} == 1) or vector(0)', 'A')
|
||||
+ b.query.withInstant(),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
thresholds: {
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
color: 'orange',
|
||||
value: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
|
||||
b.panel.new(18, 'CPU utilization', 'timeseries')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('100 * (1 - avg by (instance) (clamp_max(rate(windows_cpu_time_total{job=~"$job", instance="$instance", mode="idle"}[$__rate_interval]), 1)))', 'A')
|
||||
+ b.query.withLegendFormat('CPU busy'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
max: 100,
|
||||
min: 0,
|
||||
thresholds: {
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
color: 'orange',
|
||||
value: 80,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
value: 90,
|
||||
},
|
||||
],
|
||||
},
|
||||
unit: 'percent',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
legend: {
|
||||
calcs: [],
|
||||
displayMode: 'list',
|
||||
},
|
||||
}),
|
||||
|
||||
b.panel.new(19, 'Physical memory', 'timeseries')
|
||||
+ b.panel.withDescription('Available memory is the standby (cache), free and zero page lists.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_memory_physical_total_bytes{job=~"$job", instance="$instance"} - windows_memory_physical_free_bytes{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withLegendFormat('used'),
|
||||
b.query.new('windows_memory_physical_free_bytes{job=~"$job", instance="$instance"}', 'B')
|
||||
+ b.query.withLegendFormat('available'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
custom: {
|
||||
fillOpacity: 40,
|
||||
stacking: {
|
||||
mode: 'normal',
|
||||
},
|
||||
},
|
||||
min: 0,
|
||||
unit: 'bytes',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
legend: {
|
||||
calcs: [],
|
||||
displayMode: 'list',
|
||||
},
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'used',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'color',
|
||||
value: {
|
||||
fixedColor: 'orange',
|
||||
mode: 'fixed',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'available',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'color',
|
||||
value: {
|
||||
fixedColor: 'green',
|
||||
mode: 'fixed',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
|
||||
b.panel.new(20, 'Volumes', 'table')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_logical_disk_size_bytes{job=~"$job", instance="$instance", volume=~"$volume"}', 'A')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
b.query.new('windows_logical_disk_free_bytes{job=~"$job", instance="$instance", volume=~"$volume"}', 'B')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
b.query.new('100 * (1 - windows_logical_disk_free_bytes{job=~"$job", instance="$instance", volume=~"$volume"} / windows_logical_disk_size_bytes{job=~"$job", instance="$instance", volume=~"$volume"})', 'C')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
])
|
||||
+ b.panel.withTransformations([
|
||||
{
|
||||
group: 'joinByField',
|
||||
kind: 'Transformation',
|
||||
spec: {
|
||||
options: {
|
||||
byField: 'volume',
|
||||
mode: 'outer',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
group: 'organize',
|
||||
kind: 'Transformation',
|
||||
spec: {
|
||||
options: {
|
||||
excludeByName: {
|
||||
Time: true,
|
||||
'Time 1': true,
|
||||
'Time 2': true,
|
||||
'Time 3': true,
|
||||
__name__: true,
|
||||
instance: true,
|
||||
'instance 1': true,
|
||||
'instance 2': true,
|
||||
'instance 3': true,
|
||||
job: true,
|
||||
'job 1': true,
|
||||
'job 2': true,
|
||||
'job 3': true,
|
||||
port: true,
|
||||
'port 1': true,
|
||||
'port 2': true,
|
||||
'port 3': true,
|
||||
},
|
||||
includeByName: {},
|
||||
indexByName: {
|
||||
'Value #A': 1,
|
||||
'Value #B': 2,
|
||||
'Value #C': 3,
|
||||
volume: 0,
|
||||
},
|
||||
renameByName: {
|
||||
'Value #A': 'Size',
|
||||
'Value #B': 'Free',
|
||||
'Value #C': 'Used',
|
||||
volume: 'Volume',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'Value #A',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'unit',
|
||||
value: 'bytes',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'Value #B',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'unit',
|
||||
value: 'bytes',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'Value #C',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'unit',
|
||||
value: 'percent',
|
||||
},
|
||||
{
|
||||
id: 'min',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
id: 'max',
|
||||
value: 100,
|
||||
},
|
||||
{
|
||||
id: 'thresholds',
|
||||
value: {
|
||||
mode: 'absolute',
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
},
|
||||
{
|
||||
color: 'orange',
|
||||
value: 80,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
value: 90,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'color',
|
||||
value: {
|
||||
mode: 'continuous-GrYlRd',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'custom.cellOptions',
|
||||
value: {
|
||||
mode: 'basic',
|
||||
type: 'gauge',
|
||||
valueDisplayMode: 'text',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'decimals',
|
||||
value: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
|
||||
b.panel.new(21, 'Disk throughput', 'timeseries')
|
||||
+ b.panel.withDescription('Sum over the selected volumes. Reads are drawn below the axis.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('sum(rate(windows_logical_disk_write_bytes_total{job=~"$job", instance="$instance", volume=~"$volume"}[$__rate_interval]))', 'A')
|
||||
+ b.query.withLegendFormat('write'),
|
||||
b.query.new('sum(rate(windows_logical_disk_read_bytes_total{job=~"$job", instance="$instance", volume=~"$volume"}[$__rate_interval]))', 'B')
|
||||
+ b.query.withLegendFormat('read'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: 'Bps',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
legend: {
|
||||
calcs: [],
|
||||
displayMode: 'list',
|
||||
},
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byRegexp',
|
||||
options: 'read',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.transform',
|
||||
value: 'negative-Y',
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
|
||||
b.panel.new(22, 'Network throughput', 'timeseries')
|
||||
+ b.panel.withDescription('Sum over the selected interfaces. Received traffic is drawn below the axis.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('sum(rate(windows_net_bytes_sent_total{job=~"$job", instance="$instance", nic=~"$nic"}[$__rate_interval])) * 8', 'A')
|
||||
+ b.query.withLegendFormat('sent'),
|
||||
b.query.new('sum(rate(windows_net_bytes_received_total{job=~"$job", instance="$instance", nic=~"$nic"}[$__rate_interval])) * 8', 'B')
|
||||
+ b.query.withLegendFormat('received'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: 'bps',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
legend: {
|
||||
calcs: [],
|
||||
displayMode: 'list',
|
||||
},
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byRegexp',
|
||||
options: 'received',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.transform',
|
||||
value: 'negative-Y',
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,134 @@
|
||||
local b = import 'builders.libsonnet';
|
||||
|
||||
b.tab(
|
||||
'Services',
|
||||
b.grid([
|
||||
b.place(50, 0, 0, 6, 4),
|
||||
b.place(51, 6, 0, 6, 4),
|
||||
b.place(52, 12, 0, 6, 4),
|
||||
b.place(53, 18, 0, 6, 4),
|
||||
b.place(54, 0, 4, 12, 10),
|
||||
b.place(55, 12, 4, 12, 10),
|
||||
]),
|
||||
[
|
||||
b.panel.new(50, 'Running services', 'stat')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('sum(windows_service_state{job=~"$job", instance="$instance", state="running"})', 'A')
|
||||
+ b.query.withInstant(),
|
||||
]),
|
||||
|
||||
b.panel.new(51, 'Stopped auto services', 'stat')
|
||||
+ b.panel.withDescription('Services with start mode "auto" that are not running.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('count(windows_service_state{job=~"$job", instance="$instance", state="running"} == 0 and on (name) windows_service_start_mode{job=~"$job", instance="$instance", start_mode="auto"} == 1) or vector(0)', 'A')
|
||||
+ b.query.withInstant(),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
thresholds: {
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
color: 'orange',
|
||||
value: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
|
||||
b.panel.new(52, 'Pending services', 'stat')
|
||||
+ b.panel.withDescription('Services in a start, stop, pause or continue pending state. A service that stays pending is hung.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('sum(windows_service_state{job=~"$job", instance="$instance", state=~".* pending"}) or vector(0)', 'A')
|
||||
+ b.query.withInstant(),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
thresholds: {
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
color: 'orange',
|
||||
value: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
|
||||
b.panel.new(53, 'Disabled services', 'stat')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('sum(windows_service_start_mode{job=~"$job", instance="$instance", start_mode="disabled"})', 'A')
|
||||
+ b.query.withInstant(),
|
||||
]),
|
||||
|
||||
b.panel.new(54, 'Automatic services not running', 'table')
|
||||
+ b.panel.withDescription('Services configured to start automatically that are not in the running state. Delayed-start and trigger-start services may show up here until they start.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('max by (name, state) (windows_service_state{job=~"$job", instance="$instance"} == 1) and on (name) (windows_service_state{job=~"$job", instance="$instance", state="running"} == 0) and on (name) (windows_service_start_mode{job=~"$job", instance="$instance", start_mode="auto"} == 1)', 'A')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
])
|
||||
+ b.panel.withTransformations([
|
||||
{
|
||||
group: 'organize',
|
||||
kind: 'Transformation',
|
||||
spec: {
|
||||
options: {
|
||||
excludeByName: {
|
||||
Time: true,
|
||||
'Time 1': true,
|
||||
Value: true,
|
||||
},
|
||||
includeByName: {},
|
||||
indexByName: {
|
||||
name: 0,
|
||||
state: 1,
|
||||
},
|
||||
renameByName: {
|
||||
name: 'Service',
|
||||
state: 'State',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
+ b.panel.withOptions({
|
||||
sortBy: [
|
||||
{
|
||||
desc: false,
|
||||
displayName: 'Service',
|
||||
},
|
||||
],
|
||||
}),
|
||||
|
||||
b.panel.new(55, 'Service state changes', 'state-timeline')
|
||||
+ b.panel.withDescription('Services that started or stopped in the selected time range.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_service_state{job=~"$job", instance="$instance", state="running"} and on (name) (changes(windows_service_state{job=~"$job", instance="$instance", state="running"}[$__range] @ end()) > 0)', 'A')
|
||||
+ b.query.withLegendFormat('{{name}}'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
mappings: [
|
||||
{
|
||||
options: {
|
||||
'0': {
|
||||
color: 'red',
|
||||
index: 0,
|
||||
text: 'not running',
|
||||
},
|
||||
'1': {
|
||||
color: 'green',
|
||||
index: 1,
|
||||
text: 'running',
|
||||
},
|
||||
},
|
||||
type: 'value',
|
||||
},
|
||||
],
|
||||
}),
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,197 @@
|
||||
// Shared visualization defaults from the reference dashboard. Arrays are replaced whole.
|
||||
{
|
||||
timeseries: {
|
||||
defaults: {
|
||||
color: {
|
||||
mode: 'palette-classic',
|
||||
},
|
||||
custom: {
|
||||
axisBorderShow: false,
|
||||
axisCenteredZero: false,
|
||||
axisColorMode: 'text',
|
||||
axisLabel: '',
|
||||
axisPlacement: 'auto',
|
||||
barAlignment: 0,
|
||||
drawStyle: 'line',
|
||||
fillOpacity: 10,
|
||||
gradientMode: 'opacity',
|
||||
hideFrom: {
|
||||
legend: false,
|
||||
tooltip: false,
|
||||
viz: false,
|
||||
},
|
||||
insertNulls: false,
|
||||
lineInterpolation: 'linear',
|
||||
lineWidth: 1,
|
||||
pointSize: 4,
|
||||
scaleDistribution: {
|
||||
type: 'linear',
|
||||
},
|
||||
showPoints: 'never',
|
||||
spanNulls: false,
|
||||
stacking: {
|
||||
group: 'A',
|
||||
mode: 'none',
|
||||
},
|
||||
thresholdsStyle: {
|
||||
mode: 'off',
|
||||
},
|
||||
},
|
||||
thresholds: {
|
||||
mode: 'absolute',
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
value: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
unit: 'percentunit',
|
||||
},
|
||||
options: {
|
||||
legend: {
|
||||
calcs: [
|
||||
'mean',
|
||||
'max',
|
||||
'lastNotNull',
|
||||
],
|
||||
displayMode: 'table',
|
||||
placement: 'bottom',
|
||||
showLegend: true,
|
||||
},
|
||||
tooltip: {
|
||||
hideZeros: false,
|
||||
mode: 'multi',
|
||||
sort: 'desc',
|
||||
},
|
||||
},
|
||||
},
|
||||
stat: {
|
||||
defaults: {
|
||||
color: {
|
||||
mode: 'thresholds',
|
||||
},
|
||||
thresholds: {
|
||||
mode: 'absolute',
|
||||
steps: [
|
||||
{
|
||||
color: 'text',
|
||||
value: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
unit: 'none',
|
||||
},
|
||||
options: {
|
||||
colorMode: 'value',
|
||||
graphMode: 'none',
|
||||
justifyMode: 'auto',
|
||||
orientation: 'auto',
|
||||
percentChangeColorMode: 'standard',
|
||||
reduceOptions: {
|
||||
calcs: [
|
||||
'lastNotNull',
|
||||
],
|
||||
fields: '',
|
||||
values: false,
|
||||
},
|
||||
showPercentChange: false,
|
||||
textMode: 'auto',
|
||||
wideLayout: true,
|
||||
},
|
||||
},
|
||||
table: {
|
||||
defaults: {
|
||||
color: {
|
||||
mode: 'thresholds',
|
||||
},
|
||||
custom: {
|
||||
align: 'auto',
|
||||
cellOptions: {
|
||||
type: 'auto',
|
||||
},
|
||||
filterable: false,
|
||||
inspect: false,
|
||||
minWidth: 60,
|
||||
},
|
||||
thresholds: {
|
||||
mode: 'absolute',
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
value: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
options: {
|
||||
cellHeight: 'sm',
|
||||
enablePagination: false,
|
||||
footer: {
|
||||
countRows: false,
|
||||
fields: '',
|
||||
reducer: [
|
||||
'sum',
|
||||
],
|
||||
show: false,
|
||||
},
|
||||
showHeader: true,
|
||||
sortBy: [
|
||||
{
|
||||
desc: true,
|
||||
displayName: 'Used',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
'state-timeline': {
|
||||
defaults: {
|
||||
color: {
|
||||
mode: 'thresholds',
|
||||
},
|
||||
custom: {
|
||||
fillOpacity: 90,
|
||||
hideFrom: {
|
||||
legend: false,
|
||||
tooltip: false,
|
||||
viz: false,
|
||||
},
|
||||
insertNulls: false,
|
||||
lineWidth: 0,
|
||||
spanNulls: false,
|
||||
},
|
||||
max: 1,
|
||||
min: 0,
|
||||
thresholds: {
|
||||
mode: 'absolute',
|
||||
steps: [
|
||||
{
|
||||
color: 'red',
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
color: 'green',
|
||||
value: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
unit: 'none',
|
||||
},
|
||||
options: {
|
||||
alignValue: 'left',
|
||||
legend: {
|
||||
displayMode: 'list',
|
||||
placement: 'bottom',
|
||||
showLegend: false,
|
||||
},
|
||||
mergeValues: false,
|
||||
rowHeight: 1,
|
||||
showValue: 'never',
|
||||
tooltip: {
|
||||
hideZeros: false,
|
||||
mode: 'single',
|
||||
sort: 'none',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
local b = import 'builders.libsonnet';
|
||||
|
||||
b.tab(
|
||||
'Time',
|
||||
b.grid([
|
||||
b.place(84, 0, 0, 6, 4),
|
||||
b.place(85, 6, 0, 6, 4),
|
||||
b.place(86, 12, 0, 6, 4),
|
||||
b.place(87, 18, 0, 6, 4),
|
||||
b.place(88, 0, 4, 24, 8),
|
||||
]),
|
||||
[
|
||||
b.panel.new(84, 'Time zone', 'stat')
|
||||
+ b.panel.withDescription('Needs the time collector, which is not enabled by default.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_time_timezone{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: '',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
colorMode: 'none',
|
||||
reduceOptions: {
|
||||
fields: '/^timezone$/',
|
||||
},
|
||||
textMode: 'value',
|
||||
}),
|
||||
|
||||
b.panel.new(85, 'Clock sync source', 'stat')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_time_clock_sync_source{job=~"$job", instance="$instance"} == 1', 'A')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: '',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
colorMode: 'none',
|
||||
reduceOptions: {
|
||||
fields: '/^type$/',
|
||||
},
|
||||
textMode: 'value',
|
||||
}),
|
||||
|
||||
b.panel.new(86, 'NTP time sources', 'stat')
|
||||
+ b.panel.withDescription('Number of time sources the NTP client uses. 0 means the clock is not synchronized.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_time_ntp_client_time_sources{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withInstant(),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
thresholds: {
|
||||
steps: [
|
||||
{
|
||||
color: 'red',
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
color: 'green',
|
||||
value: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
|
||||
b.panel.new(87, 'Clock offset', 'stat')
|
||||
+ b.panel.withDescription('Absolute offset between the system clock and the selected time source.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('abs(windows_time_computed_time_offset_seconds{job=~"$job", instance="$instance"})', 'A'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
thresholds: {
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
color: 'orange',
|
||||
value: 0.5,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
value: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
unit: 's',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
graphMode: 'area',
|
||||
}),
|
||||
|
||||
b.panel.new(88, 'Clock offset and NTP round-trip delay', 'timeseries')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_time_computed_time_offset_seconds{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withLegendFormat('clock offset'),
|
||||
b.query.new('windows_time_ntp_round_trip_delay_seconds{job=~"$job", instance="$instance"}', 'B')
|
||||
+ b.query.withLegendFormat('NTP round-trip delay'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: 's',
|
||||
}),
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,102 @@
|
||||
local b = import 'builders.libsonnet';
|
||||
local variables = b.dashboard.spec.variables;
|
||||
local query = variables.QueryVariableKind;
|
||||
local datasource = variables.DatasourceVariableKind;
|
||||
local common = query.withKind()
|
||||
+ query.spec.withAllowCustomValue(true)
|
||||
+ query.spec.withCurrent({ text: '', value: '' })
|
||||
+ query.spec.withHide('dontHide')
|
||||
+ query.spec.withIncludeAll(false)
|
||||
+ query.spec.withMulti(false)
|
||||
+ query.spec.withOptions([])
|
||||
+ query.spec.withRefresh('onTimeRangeChanged')
|
||||
+ query.spec.withRegex('')
|
||||
+ query.spec.withSkipUrlSync(false)
|
||||
+ query.spec.withSort('alphabeticalAsc');
|
||||
local new(name, definition) = common
|
||||
+ query.spec.withName(name)
|
||||
+ query.spec.withDefinition(definition)
|
||||
+ query.spec.withQuery(b.dataQuery({
|
||||
qryType: 1,
|
||||
query: definition,
|
||||
refId: 'PrometheusVariableQueryEditor-VariableQuery',
|
||||
}));
|
||||
|
||||
[
|
||||
datasource.withKind()
|
||||
+ datasource.spec.withAllowCustomValue(true)
|
||||
+ datasource.spec.withCurrent({
|
||||
text: '',
|
||||
value: '',
|
||||
})
|
||||
+ datasource.spec.withHide('dontHide')
|
||||
+ datasource.spec.withIncludeAll(false)
|
||||
+ datasource.spec.withLabel('Data source')
|
||||
+ datasource.spec.withMulti(false)
|
||||
+ datasource.spec.withName('datasource')
|
||||
+ datasource.spec.withOptions([])
|
||||
+ datasource.spec.withPluginId('prometheus')
|
||||
+ datasource.spec.withRefresh('onDashboardLoad')
|
||||
+ datasource.spec.withRegex('')
|
||||
+ datasource.spec.withSkipUrlSync(false)
|
||||
,
|
||||
new('job', 'label_values(windows_os_hostname, job)')
|
||||
+ query.spec.withCurrent({
|
||||
text: [
|
||||
'All',
|
||||
],
|
||||
value: [
|
||||
'$__all',
|
||||
],
|
||||
})
|
||||
+ query.spec.withIncludeAll(true)
|
||||
+ query.spec.withLabel('Job')
|
||||
+ query.spec.withMulti(true)
|
||||
,
|
||||
new('hostname', 'label_values(windows_os_hostname{job=~"$job"}, hostname)')
|
||||
+ query.spec.withCurrent({
|
||||
text: [
|
||||
'All',
|
||||
],
|
||||
value: [
|
||||
'$__all',
|
||||
],
|
||||
})
|
||||
+ query.spec.withIncludeAll(true)
|
||||
+ query.spec.withLabel('Hostname')
|
||||
+ query.spec.withMulti(true)
|
||||
,
|
||||
new('instance', 'label_values(windows_os_hostname{job=~"$job", hostname=~"$hostname"}, instance)')
|
||||
+ query.spec.withLabel('Instance')
|
||||
,
|
||||
new('show_hostname', 'label_values(windows_os_hostname{job=~"$job", instance="$instance"}, hostname)')
|
||||
+ query.spec.withHide('hideVariable')
|
||||
,
|
||||
new('volume', 'label_values(windows_logical_disk_size_bytes{job=~"$job", instance="$instance"}, volume)')
|
||||
+ query.spec.withCurrent({
|
||||
text: [
|
||||
'All',
|
||||
],
|
||||
value: [
|
||||
'$__all',
|
||||
],
|
||||
})
|
||||
+ query.spec.withIncludeAll(true)
|
||||
+ query.spec.withLabel('Volume')
|
||||
+ query.spec.withMulti(true)
|
||||
+ query.spec.withRegex('/^(?!HarddiskVolume).+$/')
|
||||
,
|
||||
new('nic', 'label_values(windows_net_bytes_total{job=~"$job", instance="$instance"}, nic)')
|
||||
+ query.spec.withCurrent({
|
||||
text: [
|
||||
'All',
|
||||
],
|
||||
value: [
|
||||
'$__all',
|
||||
],
|
||||
})
|
||||
+ query.spec.withIncludeAll(true)
|
||||
+ query.spec.withLabel('Network interface')
|
||||
+ query.spec.withMulti(true)
|
||||
+ query.spec.withRegex('/^(?!isatap|Teredo|6to4).+$/'),
|
||||
]
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,467 +0,0 @@
|
||||
// Helpers to build a Grafana v2 dashboard (dashboard.grafana.app/v2) with tabs.
|
||||
//
|
||||
// Grafonnet builds the resource, the panel options and field config, and the Prometheus queries.
|
||||
// The panel options and field config have the same shape in v1 panels and in v2 VizConfig.
|
||||
// The helpers wrap them into v2 elements and lay the elements out in tabs, rows and grids.
|
||||
local g = import 'github.com/grafana/grafonnet/gen/grafonnet-v13.0.0/main.libsonnet';
|
||||
|
||||
local prometheus = g.query.prometheus;
|
||||
local stat = g.panel.stat;
|
||||
local stateTimeline = g.panel.stateTimeline;
|
||||
local table = g.panel.table;
|
||||
local timeSeries = g.panel.timeSeries;
|
||||
|
||||
local datasource = { name: '${datasource}' };
|
||||
|
||||
// steps builds absolute thresholds from [color, value] pairs. The first value is null (base).
|
||||
local steps(pairs) = {
|
||||
mode: 'absolute',
|
||||
steps: [{ color: p[0], value: p[1] } for p in pairs],
|
||||
};
|
||||
|
||||
// Thresholds inside field overrides leave out the null value of the base step.
|
||||
local overrideSteps(pairs) = {
|
||||
mode: 'absolute',
|
||||
steps: [{ color: p[0] } + (if p[1] == null then {} else { value: p[1] }) for p in pairs],
|
||||
};
|
||||
|
||||
local optional(value, fn) = if value == null then {} else fn(value);
|
||||
|
||||
// vizConfig keeps the fields of a grafonnet panel that make up the v2 VizConfig spec.
|
||||
local vizConfig(group, panel) = {
|
||||
group: group,
|
||||
kind: 'VizConfig',
|
||||
spec: { fieldConfig: panel.fieldConfig, options: panel.options },
|
||||
version: '',
|
||||
};
|
||||
|
||||
local query(q, refId) = {
|
||||
kind: 'PanelQuery',
|
||||
spec: {
|
||||
hidden: false,
|
||||
query: {
|
||||
datasource: datasource,
|
||||
group: 'prometheus',
|
||||
kind: 'DataQuery',
|
||||
spec:
|
||||
prometheus.withEditorMode('code')
|
||||
+ prometheus.withExpr(q.expr)
|
||||
+ prometheus.withLegendFormat(if q.legend == '' then '__auto' else q.legend)
|
||||
+ prometheus.withInstant(q.instant)
|
||||
+ prometheus.withRange(!q.instant)
|
||||
+ (if q.table then prometheus.withFormat('table') else {}),
|
||||
version: 'v0',
|
||||
},
|
||||
refId: refId,
|
||||
},
|
||||
};
|
||||
|
||||
// element is a v2 panel. The dashboard function adds its ID and grid position.
|
||||
local element(title, description, queries, viz, width, height, transformations=[]) = {
|
||||
width:: width,
|
||||
height:: height,
|
||||
kind: 'Panel',
|
||||
spec: {
|
||||
data: {
|
||||
kind: 'QueryGroup',
|
||||
spec: {
|
||||
queries: [query(queries[i], std.char(std.codepoint('A') + i)) for i in std.range(0, std.length(queries) - 1)],
|
||||
queryOptions: {},
|
||||
transformations: [
|
||||
{ group: t.id, kind: 'Transformation', spec: { options: t.options } }
|
||||
for t in transformations
|
||||
],
|
||||
},
|
||||
},
|
||||
description: description,
|
||||
links: [],
|
||||
title: title,
|
||||
vizConfig: viz,
|
||||
},
|
||||
};
|
||||
|
||||
local gridItems(bands) =
|
||||
local heights = [std.foldl(std.max, [p.height for p in band], 0) for band in bands];
|
||||
std.flattenArrays([
|
||||
local y = std.foldl(function(sum, h) sum + h, heights[0:b], 0);
|
||||
[
|
||||
{
|
||||
panel:: bands[b][i],
|
||||
kind: 'GridLayoutItem',
|
||||
spec: {
|
||||
height: bands[b][i].height,
|
||||
width: bands[b][i].width,
|
||||
x: std.foldl(function(sum, p) sum + p.width, bands[b][0:i], 0),
|
||||
y: y,
|
||||
},
|
||||
}
|
||||
for i in std.range(0, std.length(bands[b]) - 1)
|
||||
]
|
||||
for b in std.range(0, std.length(bands) - 1)
|
||||
]);
|
||||
|
||||
{
|
||||
// host selects the host chosen in the Instance variable.
|
||||
host:: 'job=~"$job", instance="$instance"',
|
||||
// fleet selects all hosts of the chosen jobs. Fleet queries add fleetJoin or fleetFilter for the Hostname variable.
|
||||
fleet:: 'job=~"$job"',
|
||||
|
||||
// fleetJoin adds the hostname label and restricts to the selected hostnames.
|
||||
fleetJoin(expr)::
|
||||
'(%s) * on (instance) group_left (hostname) max by (instance, hostname) (windows_os_hostname{%s, hostname=~"$hostname"})' % [expr, self.fleet],
|
||||
|
||||
// fleetFilter restricts to the selected hostnames without adding labels (for tables).
|
||||
fleetFilter(expr)::
|
||||
'(%s) and on (instance) windows_os_hostname{%s, hostname=~"$hostname"}' % [expr, self.fleet],
|
||||
|
||||
// q is a Prometheus query. An empty legend uses Grafana's automatic legend.
|
||||
q(expr, legend=''):: { expr: expr, legend: legend, instant: false, table: false },
|
||||
|
||||
steps:: steps,
|
||||
pctSteps:: steps([['green', null], ['orange', 80], ['red', 90]]),
|
||||
plainSteps:: steps([['green', null]]),
|
||||
textSteps:: steps([['text', null]]),
|
||||
|
||||
prop(id, value):: { id: id, value: value },
|
||||
fixedColor(color):: self.prop('color', { fixedColor: color, mode: 'fixed' }),
|
||||
dashed:: [self.prop('custom.fillOpacity', 0), self.prop('custom.lineStyle', { dash: [10, 10], fill: 'dash' })],
|
||||
|
||||
byName(name, props):: timeSeries.fieldOverride.byName.new(name) + { properties: props },
|
||||
byRegexp(regex, props):: timeSeries.fieldOverride.byRegexp.new(regex) + { properties: props },
|
||||
|
||||
// negativeY mirrors series whose display name matches the regular expression below the X axis.
|
||||
negativeY(regex):: self.byRegexp(regex, [self.prop('custom.transform', 'negative-Y')]),
|
||||
|
||||
// valueMapping maps the values 0 and 1 to texts and colors.
|
||||
valueMapping(zero, one):: [{ type: 'value', options: { '0': zero { index: 0 }, '1': one { index: 1 } } }],
|
||||
|
||||
// gaugeCell shows a table cell as a gauge from 0 to maxValue.
|
||||
gaugeCell(unit, maxValue):: [
|
||||
self.prop('unit', unit),
|
||||
self.prop('min', 0),
|
||||
self.prop('max', maxValue),
|
||||
self.prop('thresholds', overrideSteps([['green', null], ['orange', 80], ['red', 90]])),
|
||||
self.prop('color', { mode: 'continuous-GrYlRd' }),
|
||||
self.prop('custom.cellOptions', { type: 'gauge', mode: 'basic', valueDisplayMode: 'text' }),
|
||||
self.prop('decimals', 1),
|
||||
],
|
||||
overrideSteps:: overrideSteps,
|
||||
|
||||
unitCell(unit):: [self.prop('unit', unit)],
|
||||
|
||||
// timeseries draws a time series panel. legend is table (with calcs), list or hidden.
|
||||
// sortBy sorts the table legend by that column, descending.
|
||||
timeseries(
|
||||
title,
|
||||
targets,
|
||||
width,
|
||||
height,
|
||||
description='',
|
||||
unit='',
|
||||
stack=false,
|
||||
min=null,
|
||||
max=null,
|
||||
decimals=null,
|
||||
legend='table',
|
||||
calcs=['mean', 'max', 'lastNotNull'],
|
||||
overrides=[],
|
||||
fill=10,
|
||||
thresholds=self.plainSteps,
|
||||
showThresholds=false,
|
||||
sortBy='',
|
||||
)::
|
||||
local custom = timeSeries.fieldConfig.defaults.custom;
|
||||
local panel =
|
||||
custom.withAxisBorderShow(false)
|
||||
+ custom.withAxisCenteredZero(false)
|
||||
+ custom.withAxisColorMode('text')
|
||||
+ custom.withAxisLabel('')
|
||||
+ custom.withAxisPlacement('auto')
|
||||
+ custom.withBarAlignment(0)
|
||||
+ custom.withDrawStyle('line')
|
||||
+ custom.withFillOpacity(fill)
|
||||
+ custom.withGradientMode('opacity')
|
||||
+ custom.hideFrom.withLegend(false)
|
||||
+ custom.hideFrom.withTooltip(false)
|
||||
+ custom.hideFrom.withViz(false)
|
||||
+ custom.withInsertNulls(false)
|
||||
+ custom.withLineInterpolation('linear')
|
||||
+ custom.withLineWidth(1)
|
||||
+ custom.withPointSize(4)
|
||||
+ custom.scaleDistribution.withType('linear')
|
||||
+ custom.withShowPoints('never')
|
||||
+ custom.withSpanNulls(false)
|
||||
+ custom.stacking.withGroup('A')
|
||||
+ custom.stacking.withMode(if stack then 'normal' else 'none')
|
||||
+ custom.thresholdsStyle.withMode(if showThresholds then 'dashed' else 'off')
|
||||
+ timeSeries.standardOptions.color.withMode('palette-classic')
|
||||
+ timeSeries.standardOptions.thresholds.withMode(thresholds.mode)
|
||||
+ timeSeries.standardOptions.thresholds.withSteps(thresholds.steps)
|
||||
+ timeSeries.standardOptions.withUnit(unit)
|
||||
+ optional(min, timeSeries.standardOptions.withMin)
|
||||
+ optional(max, timeSeries.standardOptions.withMax)
|
||||
+ optional(decimals, timeSeries.standardOptions.withDecimals)
|
||||
+ timeSeries.standardOptions.withOverrides(overrides)
|
||||
+ timeSeries.options.legend.withPlacement('bottom')
|
||||
+ (
|
||||
if legend == 'table' then
|
||||
timeSeries.options.legend.withCalcs(calcs)
|
||||
+ timeSeries.options.legend.withDisplayMode('table')
|
||||
+ timeSeries.options.legend.withShowLegend(true)
|
||||
+ (if sortBy == '' then {} else timeSeries.options.legend.withSortBy(sortBy) + timeSeries.options.legend.withSortDesc(true))
|
||||
else
|
||||
timeSeries.options.legend.withCalcs([])
|
||||
+ timeSeries.options.legend.withDisplayMode('list')
|
||||
+ timeSeries.options.legend.withShowLegend(legend == 'list')
|
||||
)
|
||||
+ timeSeries.options.tooltip.withHideZeros(false)
|
||||
+ timeSeries.options.tooltip.withMode('multi')
|
||||
+ timeSeries.options.tooltip.withSort('desc');
|
||||
element(title, description, targets, vizConfig('timeseries', panel), width=width, height=height),
|
||||
|
||||
// stat shows the last value of expr. With text, it shows that label of the result instead of the value.
|
||||
stat(
|
||||
title,
|
||||
expr,
|
||||
width,
|
||||
height,
|
||||
description='',
|
||||
unit='',
|
||||
thresholds=self.textSteps,
|
||||
graph=false,
|
||||
colorMode='value',
|
||||
decimals=null,
|
||||
min=null,
|
||||
max=null,
|
||||
text='',
|
||||
mappings=[],
|
||||
)::
|
||||
local panel =
|
||||
stat.standardOptions.color.withMode('thresholds')
|
||||
+ (if mappings == [] then {} else stat.standardOptions.withMappings(mappings))
|
||||
+ stat.standardOptions.thresholds.withMode(thresholds.mode)
|
||||
+ stat.standardOptions.thresholds.withSteps(thresholds.steps)
|
||||
+ stat.standardOptions.withUnit(unit)
|
||||
+ optional(decimals, stat.standardOptions.withDecimals)
|
||||
+ optional(min, stat.standardOptions.withMin)
|
||||
+ optional(max, stat.standardOptions.withMax)
|
||||
+ stat.standardOptions.withOverrides([])
|
||||
+ stat.options.withColorMode(colorMode)
|
||||
+ stat.options.withGraphMode(if graph then 'area' else 'none')
|
||||
+ stat.options.withJustifyMode('auto')
|
||||
+ stat.options.withOrientation('auto')
|
||||
+ stat.options.withPercentChangeColorMode('standard')
|
||||
+ stat.options.reduceOptions.withCalcs(['lastNotNull'])
|
||||
+ stat.options.reduceOptions.withFields(if text == '' then '' else '/^' + text + '$/')
|
||||
+ stat.options.reduceOptions.withValues(false)
|
||||
+ stat.options.withShowPercentChange(false)
|
||||
+ stat.options.withTextMode(if text == '' then 'auto' else 'value')
|
||||
+ stat.options.withWideLayout(true);
|
||||
// A label shown instead of the value needs the query result as a table.
|
||||
local target = self.q(expr) { instant: !graph, table: text != '' };
|
||||
element(title, description, [target], vizConfig('stat', panel), width=width, height=height),
|
||||
|
||||
// table joins instant queries into one table. The value columns are named "Value #A", "Value #B" and
|
||||
// so on by query; rename, order and the overrides refer to those names. footer lists columns summed
|
||||
// in the table footer.
|
||||
table(
|
||||
title,
|
||||
targets,
|
||||
width,
|
||||
height,
|
||||
description='',
|
||||
joinBy='',
|
||||
exclude=[],
|
||||
rename={},
|
||||
order={},
|
||||
overrides=[],
|
||||
sortBy=[],
|
||||
transformations=[],
|
||||
footer=null,
|
||||
)::
|
||||
local excluded = { Time: true } + { [e]: true for e in exclude } + {
|
||||
['Time ' + (i + 1)]: true
|
||||
for i in std.range(0, std.length(targets) - 1)
|
||||
};
|
||||
local panel =
|
||||
table.standardOptions.color.withMode('thresholds')
|
||||
+ table.fieldConfig.defaults.custom.withAlign('auto')
|
||||
+ table.fieldConfig.defaults.custom.withCellOptions({ type: 'auto' })
|
||||
+ table.fieldConfig.defaults.custom.withFilterable(false)
|
||||
+ table.fieldConfig.defaults.custom.withInspect(false)
|
||||
+ table.fieldConfig.defaults.custom.withMinWidth(60)
|
||||
+ table.standardOptions.thresholds.withMode(self.plainSteps.mode)
|
||||
+ table.standardOptions.thresholds.withSteps(self.plainSteps.steps)
|
||||
+ table.standardOptions.withOverrides(overrides)
|
||||
+ table.options.withCellHeight('sm')
|
||||
+ table.options.withFooter({
|
||||
countRows: false,
|
||||
fields: if footer == null then '' else footer,
|
||||
reducer: ['sum'],
|
||||
show: footer != null,
|
||||
})
|
||||
+ table.options.withShowHeader(true)
|
||||
+ table.options.withSortBy(sortBy)
|
||||
+ { options+: { enablePagination: false } };
|
||||
element(
|
||||
title,
|
||||
description,
|
||||
[t { instant: true, table: true } for t in targets],
|
||||
vizConfig('table', panel),
|
||||
width=width,
|
||||
height=height,
|
||||
transformations=(if joinBy == '' then [] else [{ id: 'joinByField', options: { byField: joinBy, mode: 'outer' } }])
|
||||
+ transformations
|
||||
+ [{
|
||||
id: 'organize',
|
||||
options: { excludeByName: excluded, includeByName: {}, indexByName: order, renameByName: rename },
|
||||
}],
|
||||
),
|
||||
|
||||
local timeline(title, description, target, defaults, width, height) =
|
||||
local custom = stateTimeline.fieldConfig.defaults.custom;
|
||||
local panel =
|
||||
{ fieldConfig+: { defaults+: defaults } }
|
||||
+ custom.withFillOpacity(90)
|
||||
+ custom.hideFrom.withLegend(false)
|
||||
+ custom.hideFrom.withTooltip(false)
|
||||
+ custom.hideFrom.withViz(false)
|
||||
+ custom.withInsertNulls(false)
|
||||
+ custom.withLineWidth(0)
|
||||
+ custom.withSpanNulls(false)
|
||||
+ stateTimeline.standardOptions.withMin(0)
|
||||
+ stateTimeline.standardOptions.withOverrides([])
|
||||
+ stateTimeline.options.withAlignValue('left')
|
||||
+ stateTimeline.options.legend.withDisplayMode('list')
|
||||
+ stateTimeline.options.legend.withPlacement('bottom')
|
||||
+ stateTimeline.options.legend.withShowLegend(false)
|
||||
+ stateTimeline.options.withMergeValues(false)
|
||||
+ stateTimeline.options.withRowHeight(1)
|
||||
+ stateTimeline.options.withShowValue('never')
|
||||
+ stateTimeline.options.tooltip.withHideZeros(false)
|
||||
+ stateTimeline.options.tooltip.withMode('single')
|
||||
+ stateTimeline.options.tooltip.withSort('none');
|
||||
element(title, description, [target], vizConfig('state-timeline', panel), width=width, height=height),
|
||||
|
||||
// stateTimeline draws one row per series, colored from green to red by value. It is used for per-core
|
||||
// CPU load, where a line per core becomes unreadable.
|
||||
stateTimeline(title, description, unit, maxValue, target, width, height)::
|
||||
timeline(title, description, target, {
|
||||
color: { mode: 'continuous-GrYlRd' },
|
||||
max: maxValue,
|
||||
thresholds: $.plainSteps,
|
||||
unit: unit,
|
||||
}, width, height),
|
||||
|
||||
// statusTimeline draws a 0/1 metric per series as a red/green timeline.
|
||||
statusTimeline(title, description, target, okText, failText, width, height)::
|
||||
timeline(title, description, target, {
|
||||
color: { mode: 'thresholds' },
|
||||
mappings: $.valueMapping({ color: 'red', text: failText }, { color: 'green', text: okText }),
|
||||
max: 1,
|
||||
thresholds: steps([['red', null], ['green', 1]]),
|
||||
unit: 'none',
|
||||
}, width, height),
|
||||
|
||||
// variable is a Prometheus query variable.
|
||||
variable(name, label, query, multi=false, includeAll=false, hide=false, regex=''):: {
|
||||
kind: 'QueryVariable',
|
||||
spec: {
|
||||
allowCustomValue: true,
|
||||
current: if includeAll then { text: ['All'], value: ['$__all'] } else { text: '', value: '' },
|
||||
definition: query,
|
||||
hide: if hide then 'hideVariable' else 'dontHide',
|
||||
includeAll: includeAll,
|
||||
[if label != '' then 'label']: label,
|
||||
multi: multi,
|
||||
name: name,
|
||||
options: [],
|
||||
query: {
|
||||
datasource: datasource,
|
||||
group: 'prometheus',
|
||||
kind: 'DataQuery',
|
||||
spec: { qryType: 1, query: query, refId: 'PrometheusVariableQueryEditor-VariableQuery' },
|
||||
version: 'v0',
|
||||
},
|
||||
refresh: 'onTimeRangeChanged',
|
||||
regex: regex,
|
||||
skipUrlSync: false,
|
||||
sort: 'alphabeticalAsc',
|
||||
},
|
||||
},
|
||||
|
||||
// row is a row of a tab. bands is a list of panel lists; the panels of a band sit side by side
|
||||
// and the bands are stacked.
|
||||
row(title, bands):: { title: title, bands: bands },
|
||||
|
||||
// tab is a tab of the dashboard. A tab is either one grid of bands, or a list of rows.
|
||||
tab(title, bands=[], rows=[]):: { title: title, rows: if rows == [] then [$.row('', bands)] else rows },
|
||||
|
||||
// dashboard builds the v2 dashboard from the tabs. Panels are numbered in order, and every row
|
||||
// takes one number before its panels, as in the classic dashboard the layout was converted from.
|
||||
// The element names (panel-<number>) are taken from these numbers.
|
||||
dashboard(name, title, tabs, variables=[], annotations=[], spec={})::
|
||||
local v2 = g.apps.dashboard.v2;
|
||||
local rows = [
|
||||
tabs[t].rows[r] { tab:: t }
|
||||
for t in std.range(0, std.length(tabs) - 1)
|
||||
for r in std.range(0, std.length(tabs[t].rows) - 1)
|
||||
];
|
||||
local panelCount(row) = std.length(std.flattenArrays(row.bands));
|
||||
// The row itself takes the number before its first panel.
|
||||
local firstID = [
|
||||
std.foldl(function(sum, r) sum + 1 + panelCount(r), rows[0:i], 0) + 2
|
||||
for i in std.range(0, std.length(rows) - 1)
|
||||
];
|
||||
local numbered = [
|
||||
local items = gridItems(rows[i].bands);
|
||||
rows[i] {
|
||||
items:: [
|
||||
items[j] {
|
||||
id:: firstID[i] + j,
|
||||
spec+: { element: { kind: 'ElementReference', name: 'panel-' + (firstID[i] + j) } },
|
||||
}
|
||||
for j in std.range(0, std.length(items) - 1)
|
||||
],
|
||||
}
|
||||
for i in std.range(0, std.length(rows) - 1)
|
||||
];
|
||||
local grid(row) = { kind: 'GridLayout', spec: { items: row.items } };
|
||||
local tabRows(t) = [r for r in numbered if r.tab == t];
|
||||
v2.new(name, title)
|
||||
+ v2.spec.withAnnotations(annotations)
|
||||
+ v2.spec.withElements({
|
||||
[item.spec.element.name]: item.panel { spec+: { id: item.id } }
|
||||
for row in numbered
|
||||
for item in row.items
|
||||
})
|
||||
+ v2.spec.withLayout({
|
||||
kind: 'TabsLayout',
|
||||
spec: {
|
||||
tabs: [
|
||||
local tab = tabs[t];
|
||||
local r = tabRows(t);
|
||||
{
|
||||
kind: 'TabsLayoutTab',
|
||||
spec: {
|
||||
layout:
|
||||
if std.length(r) == 1 && r[0].title == '' then grid(r[0])
|
||||
else {
|
||||
kind: 'RowsLayout',
|
||||
spec: {
|
||||
rows: [
|
||||
{ kind: 'RowsLayoutRow', spec: { collapse: false, layout: grid(x), title: x.title } }
|
||||
for x in r
|
||||
],
|
||||
},
|
||||
},
|
||||
title: tab.title,
|
||||
},
|
||||
}
|
||||
for t in std.range(0, std.length(tabs) - 1)
|
||||
],
|
||||
},
|
||||
})
|
||||
+ v2.spec.withVariables(variables)
|
||||
+ { spec+: spec },
|
||||
}
|
||||
Reference in New Issue
Block a user