Files
windows_exporter/contrib/mixin/dashboards/exporter.libsonnet
T

650 lines
22 KiB
Jsonnet

local b = import 'builders.libsonnet';
function(on)
b.tab(
'Exporter',
b.rows([
b.summary([90, 91, 92, 93, 94, 95]),
b.row('Scrape', b.flow([[96, 12, 8], [97, 12, 8]])),
b.row('Collectors', b.flow([[99, 8, 8], [100, 16, 8], [101, 12, 9], [102, 12, 9]])),
b.row('Process and Go runtime', b.flow([[104, 8, 8], [105, 8, 8], [106, 8, 8], [107, 8, 8], [108, 8, 8], [109, 8, 8], [110, 12, 8], [111, 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('Number of collectors that failed on the latest scrape, including timeouts. The graph shows the count over the selected time range.')
+ b.panel.withQueries([
b.query.new('count(windows_exporter_collector_success{job=~"$job", instance="$instance"} == 0) or vector(0)', 'A'),
])
+ b.panel.withDefaults({
decimals: 0,
min: 0,
thresholds: {
steps: [
{
color: 'green',
value: null,
},
{
color: 'red',
value: 1,
},
],
},
})
+ b.panel.withOptions({
graphMode: 'area',
}),
b.panel.new(94, 'Timeouts', 'stat')
+ b.panel.withDescription('Number of collectors that timed out on the latest scrape, and whether the latest scrape timed out while it waited for an earlier scrape, so that no collector ran. The graph shows the values over the selected time range.')
+ b.panel.withQueries([
b.query.new('count(windows_exporter_collector_timeout{job=~"$job", instance="$instance"} == 1) or vector(0)', 'A')
+ b.query.withLegendFormat('collectors'),
b.query.new('max(windows_exporter_scrape_queue_timeout{job=~"$job", instance="$instance"}) or vector(0)', 'B')
+ b.query.withLegendFormat('queued scrape'),
])
+ b.panel.withDefaults({
decimals: 0,
min: 0,
thresholds: {
steps: [
{
color: 'green',
value: null,
},
{
color: 'red',
value: 1,
},
],
},
})
+ b.panel.withOptions({
graphMode: 'area',
}),
b.panel.new(95, 'Samples per scrape', 'stat')
+ b.panel.withDescription('Number of metric samples returned before Prometheus metric relabeling. Changes can reflect collector configuration or changing device/process counts; unexpected drops should be checked against scrape and collector success.')
+ 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.withDescription('Latest success, timeout and duration for each enabled collector. Success is 1 for a completed collection and 0 for a failure; timeout is 1 when the scrape budget was exceeded. Disabled collectors are absent.')
+ 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. The states need an exporter build with the Go scheduler metrics; the total line works with every build. The "not in Go" state is left out: since 0.32 a Go runtime bug (golang/go#82136) counts each WMI callback from a Windows thread once and never subtracts it, so it keeps growing without a leak.')
+ 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_goroutines{job=~"$job", instance="$instance"}', 'D')
+ 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',
},
}),
]
)