mirror of https://github.com/prometheus/prometheus
Browse Source
This provides the basic js, css and console template templates required to build dashboards. Included as an example are consoles for the node_exporter. Change-Id: I4cfeea5e9691a9413f74ae98ca32a908df8e4a59pull/428/head
Brian Brazil
10 years ago
9 changed files with 1211 additions and 0 deletions
@ -0,0 +1,56 @@ |
|||||||
|
{{/* vim: set ft=html: */}} |
||||||
|
|
||||||
|
{{/* Navbar, should be passed . */}} |
||||||
|
{{ define "navbar" }} |
||||||
|
<div class="navbar navbar-inverse navbar-static-top"> |
||||||
|
<div class="navbar-inner"> |
||||||
|
<ul class="navbar-nav nav"> |
||||||
|
<a class="brand" href="/">Prometheus</a> |
||||||
|
<li><a href="/alerts">Alerts</a></li> |
||||||
|
<!-- Add in navbar links here --> |
||||||
|
<li><a href="https://pagerduty.com/">PagerDuty</a></li> |
||||||
|
</ul> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
{{ end }} |
||||||
|
|
||||||
|
{{/* LHS menu, should be passed . */}} |
||||||
|
{{ define "menu" }} |
||||||
|
<div class="prom_lhs_menu"> |
||||||
|
<ul> |
||||||
|
{{ $dot := . }} |
||||||
|
|
||||||
|
{{ template "_menuItem" (args . "index.html.example" "Overview") }} |
||||||
|
{{ template "_menuItem" (args . "node.html" "Node") }} |
||||||
|
{{ if match "^node" .Path }} |
||||||
|
<ul> |
||||||
|
{{ if .Params.instance }} |
||||||
|
<li {{ if eq .Path "node-overview.html" }}class="prom_lhs_menu_selected"{{end}}> |
||||||
|
<a href="node-overview.html?instance={{ .Params.instance }}">{{ reReplaceAll "(.*?://)([^:/]+?)(:\\d+)?/.*" "$2" .Params.instance }}</a> |
||||||
|
</li> |
||||||
|
<ul> |
||||||
|
<li {{ if eq .Path "node-cpu.html" }}class="prom_lhs_menu_selected"{{end}}> |
||||||
|
<a href="node-cpu.html?instance={{ .Params.instance }}">CPU</a> |
||||||
|
</li> |
||||||
|
<li {{ if eq .Path "node-disk.html" }}class="prom_lhs_menu_selected"{{end}}> |
||||||
|
<a href="node-disk.html?instance={{ .Params.instance }}">Disk</a> |
||||||
|
</li> |
||||||
|
</ul> |
||||||
|
{{ else }} |
||||||
|
{{ range query "up{job='node'}" | sortByLabel "instance" }} |
||||||
|
<li><a href="node-overview.html?instance={{ .Labels.instance }}">{{ reReplaceAll "(.*?://)([^:/]+?)(:\\d+)?/.*" "$2" .Labels.instance }}</a></li> |
||||||
|
{{ end }} |
||||||
|
{{ end }} |
||||||
|
</ul> |
||||||
|
{{ end }} |
||||||
|
|
||||||
|
|
||||||
|
</ul> |
||||||
|
</div> |
||||||
|
{{ end }} |
||||||
|
|
||||||
|
{{/* Helper, pass (args . path name) */}} |
||||||
|
{{ define "_menuItem" }} |
||||||
|
<li {{ if eq .arg0.Path .arg1 }} class="prom_lhs_menu_selected" {{ end }}><a href="{{ .arg1 }}">{{ .arg2 }}</a></li> |
||||||
|
{{ end }} |
||||||
|
|
@ -0,0 +1,112 @@ |
|||||||
|
{{/* vim: set ft=html: */}} |
||||||
|
{{/* Load Prometheus console library JS/CSS. Should go in <head> */}} |
||||||
|
{{define "prom_console_head"}} |
||||||
|
<link type="text/css" rel="stylesheet" href="/static/vendor/rickshaw/rickshaw.min.css"> |
||||||
|
<link type="text/css" rel="stylesheet" href="/static/vendor/bootstrap/css/bootstrap.css"> |
||||||
|
<link type="text/css" rel="stylesheet" href="/static/css/prom_console.css"> |
||||||
|
<script src="/static/vendor/rickshaw/vendor/d3.min.js"></script> |
||||||
|
<script src="/static/vendor/rickshaw/vendor/d3.layout.min.js"></script> |
||||||
|
<script src="/static/vendor/rickshaw/rickshaw.min.js"></script> |
||||||
|
<script src="/static/vendor/js/jquery.min.js"></script> |
||||||
|
<script src="/static/vendor/bootstrap/js/bootstrap.min.js"></script> |
||||||
|
<script src="/static/js/prom_console.js"></script> |
||||||
|
{{end}} |
||||||
|
|
||||||
|
{{/* Top of all pages. */}} |
||||||
|
{{define "head"}} |
||||||
|
<html> |
||||||
|
<head> |
||||||
|
{{template "prom_console_head"}} |
||||||
|
</head> |
||||||
|
<body> |
||||||
|
{{template "navbar" .}} |
||||||
|
{{template "menu" .}} |
||||||
|
{{end}} |
||||||
|
|
||||||
|
{{ define "__prom_query_drilldown_noop" }}{{ . }}{{ end }} |
||||||
|
{{ define "humanize" }}{{ humanize . }}{{ end }} |
||||||
|
{{ define "humanizeNoSmallPrefix" }}{{ if and (lt . 1.0) (gt . -1.0) }}{{ printf "%.3g" . }}{{ else }}{{ humanize . }}{{ end }}{{ end }} |
||||||
|
{{ define "humanize1024" }}{{ humanize1024 . }}{{ end }} |
||||||
|
{{ define "humanizeDuration" }}{{ humanizeDuration . }}{{ end }} |
||||||
|
{{ define "printf.3g" }}{{ printf "%.3g" . }}{{ end }} |
||||||
|
|
||||||
|
{{/* prom_query_drilldown (args expr suffix? renderTemplate?) |
||||||
|
Displays the result of the expression, with a link to /graph for it. |
||||||
|
|
||||||
|
renderTemplate is the name of the template to use to render the value. |
||||||
|
*/}} |
||||||
|
{{ define "prom_query_drilldown" }} |
||||||
|
{{ $expr := .arg0}}{{ $suffix := (or .arg1 "")}}{{ $renderTemplate := (or .arg2 "__prom_query_drilldown_noop")}} |
||||||
|
<a class="prom_query_drilldown" href="{{ graphLink $expr }}">{{ with query $expr }}{{tmpl $renderTemplate ( . | first | value )}}{{ $suffix }}{{else}}-{{ end }}</a> |
||||||
|
{{ end }} |
||||||
|
|
||||||
|
{{ define "prom_path" }}/consoles/{{.Path}}?{{range $param, $value := .Params}}{{$param}}={{$value}}&{{end}}{{ end }}" |
||||||
|
|
||||||
|
{{/* Top and bottom of table on RHS */}} |
||||||
|
{{define "prom_right_table_head"}} |
||||||
|
<div class="prom_console_rhs"> |
||||||
|
<table class="table table-bordered table-hover table-condensed"> |
||||||
|
{{end}} |
||||||
|
{{define "prom_right_table_tail"}} |
||||||
|
</table> |
||||||
|
</div> |
||||||
|
|
||||||
|
{{end}} |
||||||
|
|
||||||
|
{{/* Top and bottom of main content */}} |
||||||
|
{{define "prom_content_head"}} |
||||||
|
<div class="prom_console_content"> |
||||||
|
{{template "prom_graph_timecontrol" .}} |
||||||
|
{{end}} |
||||||
|
{{define "prom_content_tail"}} |
||||||
|
</div> |
||||||
|
{{end}} |
||||||
|
|
||||||
|
{{define "prom_graph_timecontrol"}} |
||||||
|
<div class="prom_graph_timecontrol"> |
||||||
|
<div class="prom_graph_timecontrol_inner"> |
||||||
|
<label for="prom_graph_duration">Range:</label> |
||||||
|
<div class="input-prepend input-append"> |
||||||
|
<button class="btn btn-mini" type="button" id="prom_graph_duration_shrink" title="Shrink the time range."> |
||||||
|
<i class="icon-minus"></i> |
||||||
|
</button> |
||||||
|
<input class="input-mini" title="Time range of graph" type="text" id="prom_graph_duration"> |
||||||
|
<button class="btn btn-mini" type="button" id="prom_graph_duration_grow" title="Grow the time range."> |
||||||
|
<i class="icon-plus"></i> |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
<label for="prom_graph_duration">End:</label> |
||||||
|
<div class="input-prepend input-append"> |
||||||
|
<button class="btn btn-mini" type="button" id="prom_graph_time_back" title="Rewind the end time."> |
||||||
|
<i class="icon-backward"></i> |
||||||
|
</button> |
||||||
|
<input class="input-medium" title="End time of graph" placeholder="Until" type="text" id="prom_graph_time_end" size="16" value=""> |
||||||
|
<button class="btn btn-mini" type="button" id="prom_graph_time_forward" title="Advance the end time."> |
||||||
|
<i class="icon-forward"></i> |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
<div class="input-prepend input-append"> |
||||||
|
<div class="btn-group dropup prom_graph_timecontrol_refresh"> |
||||||
|
<button type="button" class="btn btn-mini" id="prom_graph_refresh_button"> |
||||||
|
<span class="icon-repeat"></span> |
||||||
|
(<span id="prom_graph_refresh_button_value">Off</span>) |
||||||
|
</button> |
||||||
|
<button type="button" class="btn btn-mini dropdown-toggle" data-toggle="dropdown"> |
||||||
|
<span class="caret"></span> |
||||||
|
</button> |
||||||
|
<ul class="dropdown-menu" id="prom_graph_refresh_intervals" role="menu"> |
||||||
|
</ul> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<script> |
||||||
|
new PromConsole.TimeControl(); |
||||||
|
</script> |
||||||
|
</div> |
||||||
|
{{end}} |
||||||
|
|
||||||
|
{{/* Bottom of all pages. */}} |
||||||
|
{{define "tail"}} |
||||||
|
</body> |
||||||
|
</html> |
||||||
|
{{end}} |
@ -0,0 +1,11 @@ |
|||||||
|
{{ template "head" . }} |
||||||
|
|
||||||
|
{{template "prom_right_table_head"}} |
||||||
|
{{template "prom_right_table_tail"}} |
||||||
|
|
||||||
|
{{template "prom_content_head" .}} |
||||||
|
<h1>Overview</h1> |
||||||
|
<p>These are example consoles for Prometheus, they are still under development.</p> |
||||||
|
{{template "prom_content_tail" .}} |
||||||
|
|
||||||
|
{{template "tail"}} |
@ -0,0 +1,58 @@ |
|||||||
|
{{template "head" .}} |
||||||
|
|
||||||
|
{{template "prom_right_table_head"}} |
||||||
|
<tr><th colspan="2">CPU</th></tr> |
||||||
|
{{ range printf "sum by (mode)(rate(node_cpu{job='node',instance='%s'}[5m])) * 100 / scalar(count(count by (cpu)(node_cpu{job='node',instance='%s'})))" .Params.instance .Params.instance | query | sortByLabel "mode"}} |
||||||
|
<tr> |
||||||
|
<td>{{ .Labels.mode | title }} CPU</td> |
||||||
|
<td>{{ .Value | printf "%.3g" }}%</td> |
||||||
|
</tr> |
||||||
|
{{ end }} |
||||||
|
<tr><th colspan="2">Misc</th></tr> |
||||||
|
<tr> |
||||||
|
<td>Processes Running</td> |
||||||
|
<td>{{ template "prom_query_drilldown" (args (printf "node_procs_running{job='node',instance='%s'}" .Params.instance) "" "humanize") }}</td> |
||||||
|
</tr> |
||||||
|
<tr> |
||||||
|
<td>Processes Blocked</td> |
||||||
|
<td>{{ template "prom_query_drilldown" (args (printf "node_procs_blocked{job='node',instance='%s'}" .Params.instance) "" "humanize") }}</td> |
||||||
|
</tr> |
||||||
|
<tr> |
||||||
|
<td>Forks</td> |
||||||
|
<td>{{ template "prom_query_drilldown" (args (printf "rate(node_forks{job='node',instance='%s'}[5m])" .Params.instance) "/s" "humanize") }}</td> |
||||||
|
</tr> |
||||||
|
<tr> |
||||||
|
<td>Context Switches</td> |
||||||
|
<td>{{ template "prom_query_drilldown" (args (printf "rate(node_context_switches{job='node',instance='%s'}[5m])" .Params.instance) "/s" "humanize") }}</td> |
||||||
|
</tr> |
||||||
|
<tr> |
||||||
|
<td>Interrupts</td> |
||||||
|
<td>{{ template "prom_query_drilldown" (args (printf "rate(node_intr{job='node',instance='%s'}[5m])" .Params.instance) "/s" "humanize") }}</td> |
||||||
|
</tr> |
||||||
|
<tr> |
||||||
|
<td>1m Loadavg</td> |
||||||
|
<td>{{ template "prom_query_drilldown" (args (printf "node_load1{job='node',instance='%s'}" .Params.instance)) }}</td> |
||||||
|
</tr> |
||||||
|
<tr> |
||||||
|
</tr> |
||||||
|
{{template "prom_right_table_tail"}} |
||||||
|
|
||||||
|
{{template "prom_content_head" .}} |
||||||
|
<h1>Node CPU - {{ reReplaceAll "(.*?://)([^:/]+?)(:\\d+)?/.*" "$2" .Params.instance }}</h1> |
||||||
|
|
||||||
|
<h3>CPU Usage</h3> |
||||||
|
<div id="cpuGraph"></div> |
||||||
|
<script> |
||||||
|
new PromConsole.Graph({ |
||||||
|
node: document.querySelector("#cpuGraph"), |
||||||
|
expr: "sum by (mode)(rate(node_cpu{job='node',instance='{{.Params.instance}}',mode!='idle'}[5m]))", |
||||||
|
renderer: 'area', |
||||||
|
max: {{ with printf "count(count by (cpu)(node_cpu{job='node',instance='%s'}))" .Params.instance | query }}{{ . | first | value}}{{else}}undefined{{end}}, |
||||||
|
yAxisFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix, |
||||||
|
yHoverFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix, |
||||||
|
yTitle: 'Cores' |
||||||
|
}) |
||||||
|
</script> |
||||||
|
{{template "prom_content_tail" .}} |
||||||
|
|
||||||
|
{{template "tail"}} |
@ -0,0 +1,76 @@ |
|||||||
|
{{template "head" .}} |
||||||
|
|
||||||
|
{{template "prom_right_table_head"}} |
||||||
|
<th colspan="2">Disks</th> |
||||||
|
</tr> |
||||||
|
{{ range printf "node_disk_io_time_ms{job='node',instance='%s'}" .Params.instance | query | sortByLabel "device"}} |
||||||
|
<th colspan="2">{{ .Labels.device }}</th> |
||||||
|
<tr> |
||||||
|
<td>Utilization</td> |
||||||
|
<td>{{ template "prom_query_drilldown" (args (printf "rate(node_disk_io_time_ms{job='node',instance='%s',device='%s'}[5m]) / 1000 * 100" .Labels.instance .Labels.device) "%" "printf.3g") }}</td> |
||||||
|
</tr> |
||||||
|
<tr> |
||||||
|
<td>Throughput</td> |
||||||
|
<td>{{ template "prom_query_drilldown" (args (printf "rate(node_disk_sectors_read{job='node',instance='%s',device='%s'}[5m]) * 512 + rate(node_disk_sectors_written{job='node',instance='%s',device='%s'}[5m]) * 512" .Labels.instance .Labels.device .Labels.instance .Labels.device) "B/s" "humanize") }}</td> |
||||||
|
</tr> |
||||||
|
<tr> |
||||||
|
<td>Avg Read Time</td> |
||||||
|
<td>{{ template "prom_query_drilldown" (args (printf "rate(node_disk_read_time_ms{job='node',instance='%s',device='%s'}[5m]) / 1000 / rate(node_disk_reads_completed{job='node',instance='%s',device='%s'}[5m])" .Labels.instance .Labels.device .Labels.instance .Labels.device) "s" "humanize") }}</td> |
||||||
|
</tr> |
||||||
|
<tr> |
||||||
|
<td>Avg Write Time</td> |
||||||
|
<td>{{ template "prom_query_drilldown" (args (printf "rate(node_disk_write_time_ms{job='node',instance='%s',device='%s'}[5m]) / 1000 / rate(node_disk_writes_completed{job='node',instance='%s',device='%s'}[5m])" .Labels.instance .Labels.device .Labels.instance .Labels.device) "s" "humanize") }}</td> |
||||||
|
</tr> |
||||||
|
{{ end }} |
||||||
|
<th colspan="2">Filesystem Fullness</th> |
||||||
|
</tr> |
||||||
|
{{ define "roughlyNearZero"}} |
||||||
|
{{ if gt .1 . }}~0{{ else }}{{ printf "%.3g" . }}{{ end }} |
||||||
|
{{ end }} |
||||||
|
{{ range printf "node_filesystem_size{job='node',instance='%s'}" .Params.instance | query | sortByLabel "filesystem"}} |
||||||
|
<tr> |
||||||
|
<td>{{.Labels.filesystem}}</td> |
||||||
|
<td>{{ template "prom_query_drilldown" (args (printf "100 - node_filesystem_free{job='node',instance='%s',filesystem='%s'} / node_filesystem_size{job='node'} * 100" .Labels.instance .Labels.filesystem) "%" "roughlyNearZero") }}</td> |
||||||
|
</tr> |
||||||
|
{{ end }} |
||||||
|
<tr> |
||||||
|
</tr> |
||||||
|
{{template "prom_right_table_tail"}} |
||||||
|
|
||||||
|
{{template "prom_content_head" .}} |
||||||
|
<h1>Node Disk - {{ reReplaceAll "(.*?://)([^:/]+?)(:\\d+)?/.*" "$2" .Params.instance }}</h1> |
||||||
|
|
||||||
|
<h3>Disk I/O Utilization</h3> |
||||||
|
<div id="diskioGraph"></div> |
||||||
|
<script> |
||||||
|
new PromConsole.Graph({ |
||||||
|
node: document.querySelector("#diskioGraph"), |
||||||
|
expr: [ |
||||||
|
"rate(node_disk_io_time_ms{job='node',instance='{{.Params.instance}}',device!~'^(md\\d+$|dm-)'}[5m]) / 1000 * 100", |
||||||
|
], |
||||||
|
min: 0, |
||||||
|
name: '[[ device ]]', |
||||||
|
yUnits: "%", |
||||||
|
yAxisFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix, |
||||||
|
yHoverFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix, |
||||||
|
yTitle: 'Disk I/O Utilization' |
||||||
|
}) |
||||||
|
</script> |
||||||
|
<h3>Filesystem Usage</h3> |
||||||
|
<div id="fsGraph"></div> |
||||||
|
<script> |
||||||
|
new PromConsole.Graph({ |
||||||
|
node: document.querySelector("#fsGraph"), |
||||||
|
expr: "100 - node_filesystem_free{job='node',instance='{{ .Params.instance }}'} / node_filesystem_size{job='node'} * 100", |
||||||
|
min: 0, |
||||||
|
max: 100, |
||||||
|
name: '[[ filesystem ]]', |
||||||
|
yUnits: "%", |
||||||
|
yAxisFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix, |
||||||
|
yHoverFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix, |
||||||
|
yTitle: 'Filesystem Fullness' |
||||||
|
}) |
||||||
|
</script> |
||||||
|
{{template "prom_content_tail" .}} |
||||||
|
|
||||||
|
{{template "tail"}} |
@ -0,0 +1,122 @@ |
|||||||
|
{{template "head" .}} |
||||||
|
|
||||||
|
{{template "prom_right_table_head"}} |
||||||
|
<tr><th colspan="2">Overview</th></tr> |
||||||
|
<tr> |
||||||
|
<td>User CPU</td> |
||||||
|
<td>{{ template "prom_query_drilldown" (args (printf "sum(rate(node_cpu{job='node',instance='%s',mode='user'}[5m])) * 100 / count(count by (cpu)(node_cpu{job='node',instance='%s'}))" .Params.instance .Params.instance) "%" "printf.3g") }}</td> |
||||||
|
</tr> |
||||||
|
<tr> |
||||||
|
<td>System CPU</td> |
||||||
|
<td>{{ template "prom_query_drilldown" (args (printf "sum(rate(node_cpu{job='node',instance='%s',mode='system'}[5m])) * 100 / count(count by (cpu)(node_cpu{job='node',instance='%s'}))" .Params.instance .Params.instance) "%" "printf.3g") }}</td> |
||||||
|
</tr> |
||||||
|
<tr> |
||||||
|
<td>Memory Total</td> |
||||||
|
<td>{{ template "prom_query_drilldown" (args (printf "node_memory_MemTotal{job='node',instance='%s'}" .Params.instance) "B" "humanize1024") }}</td> |
||||||
|
</tr> |
||||||
|
<tr> |
||||||
|
<td>Memory Free</td> |
||||||
|
<td>{{ template "prom_query_drilldown" (args (printf "node_memory_MemFree{job='node',instance='%s'}" .Params.instance) "B" "humanize1024") }}</td> |
||||||
|
</tr> |
||||||
|
<tr> |
||||||
|
<th colspan="2">Network</th> |
||||||
|
</tr> |
||||||
|
{{ range printf "node_network_receive_bytes{job='node',instance='%s',device!='lo'}" .Params.instance | query | sortByLabel "device"}} |
||||||
|
<tr> |
||||||
|
<td>{{ .Labels.device }} Received</td> |
||||||
|
<td>{{ template "prom_query_drilldown" (args (printf "rate(node_network_receive_bytes{job='node',instance='%s',device='%s'}[5m])" .Labels.instance .Labels.device) "B/s" "humanize") }}</td> |
||||||
|
</tr> |
||||||
|
<tr> |
||||||
|
<td>{{ .Labels.device }} Transmitted</td> |
||||||
|
<td>{{ template "prom_query_drilldown" (args (printf "rate(node_network_transmit_bytes{job='node',instance='%s',device='%s'}[5m])" .Labels.instance .Labels.device) "B/s" "humanize") }}</td> |
||||||
|
</tr> |
||||||
|
{{ end }} |
||||||
|
</tr> |
||||||
|
<tr> |
||||||
|
<th colspan="2">Disks</th> |
||||||
|
</tr> |
||||||
|
{{ range printf "node_disk_io_time_ms{job='node',instance='%s',device!~'^(md\\d+$|dm-)'}" .Params.instance | query | sortByLabel "device"}} |
||||||
|
<tr> |
||||||
|
<td>{{ .Labels.device }} Utilization</td> |
||||||
|
<td>{{ template "prom_query_drilldown" (args (printf "rate(node_disk_io_time_ms{job='node',instance='%s',device='%s'}[5m]) / 1000 * 100" .Labels.instance .Labels.device) "%" "printf.3g") }}</td> |
||||||
|
</tr> |
||||||
|
{{ end }} |
||||||
|
{{ range printf "node_disk_io_time_ms{job='node',instance='%s'}" .Params.instance | query | sortByLabel "device"}} |
||||||
|
<tr> |
||||||
|
<td>{{ .Labels.device }} Throughput</td> |
||||||
|
<td>{{ template "prom_query_drilldown" (args (printf "rate(node_disk_sectors_read{job='node',instance='%s',device='%s'}[5m]) * 512 + rate(node_disk_sectors_written{job='node',instance='%s',device='%s'}[5m]) * 512" .Labels.instance .Labels.device .Labels.instance .Labels.device) "B/s" "humanize") }}</td> |
||||||
|
</tr> |
||||||
|
{{ end }} |
||||||
|
<tr> |
||||||
|
<th colspan="2">Filesystem Fullness</th> |
||||||
|
</tr> |
||||||
|
{{ define "roughlyNearZero"}} |
||||||
|
{{ if gt .1 . }}~0{{ else }}{{ printf "%.3g" . }}{{ end }} |
||||||
|
{{ end }} |
||||||
|
{{ range printf "node_filesystem_size{job='node',instance='%s'}" .Params.instance | query | sortByLabel "filesystem"}} |
||||||
|
<tr> |
||||||
|
<td>{{.Labels.filesystem}}</td> |
||||||
|
<td>{{ template "prom_query_drilldown" (args (printf "100 - node_filesystem_free{job='node',instance='%s',filesystem='%s'} / node_filesystem_size{job='node'} * 100" .Labels.instance .Labels.filesystem) "%" "roughlyNearZero") }}</td> |
||||||
|
</tr> |
||||||
|
{{ end }} |
||||||
|
</tr> |
||||||
|
{{template "prom_right_table_tail"}} |
||||||
|
|
||||||
|
{{template "prom_content_head" .}} |
||||||
|
<h1>Node Overview - {{ reReplaceAll "(.*?://)([^:/]+?)(:\\d+)?/.*" "$2" .Params.instance }}</h1> |
||||||
|
|
||||||
|
<h3>CPU Usage</h3> |
||||||
|
<div id="cpuGraph"></div> |
||||||
|
<script> |
||||||
|
new PromConsole.Graph({ |
||||||
|
node: document.querySelector("#cpuGraph"), |
||||||
|
expr: "sum by (mode)(rate(node_cpu{job='node',instance='{{.Params.instance}}',mode!='idle'}[5m]))", |
||||||
|
renderer: 'area', |
||||||
|
max: {{ with printf "count(count by (cpu)(node_cpu{job='node',instance='%s'}))" .Params.instance | query }}{{ . | first | value}}{{else}}undefined{{end}}, |
||||||
|
yAxisFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix, |
||||||
|
yHoverFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix, |
||||||
|
yTitle: 'Cores' |
||||||
|
}) |
||||||
|
</script> |
||||||
|
|
||||||
|
<h3>Disk I/O Utilization</h3> |
||||||
|
<div id="diskioGraph"></div> |
||||||
|
<script> |
||||||
|
new PromConsole.Graph({ |
||||||
|
node: document.querySelector("#diskioGraph"), |
||||||
|
expr: [ |
||||||
|
"rate(node_disk_io_time_ms{job='node',instance='{{.Params.instance}}',device!~'^(md\\d+$|dm-)'}[5m]) / 1000 * 100", |
||||||
|
], |
||||||
|
min: 0, |
||||||
|
name: '[[ device ]]', |
||||||
|
yAxisFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix, |
||||||
|
yHoverFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix, |
||||||
|
yUnits: "%", |
||||||
|
yTitle: 'Disk I/O Utilization' |
||||||
|
}) |
||||||
|
</script> |
||||||
|
|
||||||
|
<h3>Memory</h3> |
||||||
|
<div id="memoryGraph"></div> |
||||||
|
<script> |
||||||
|
new PromConsole.Graph({ |
||||||
|
node: document.querySelector("#memoryGraph"), |
||||||
|
renderer: 'area', |
||||||
|
expr: [ |
||||||
|
"node_memory_Cached{job='node',instance='{{.Params.instance}}'}", |
||||||
|
"node_memory_Buffers{job='node',instance='{{.Params.instance}}'}", |
||||||
|
"node_memory_MemTotal{job='node',instance='{{.Params.instance}}'} - node_memory_MemFree{job='node',instance='{{.Params.instance}}'} - node_memory_Buffers{job='node',instance='{{.Params.instance}}'} - node_memory_Cached{job='node',instance='{{.Params.instance}}'}", |
||||||
|
"node_memory_MemFree{job='node',instance='{{.Params.instance}}'}", |
||||||
|
], |
||||||
|
name: function(metric) { |
||||||
|
return !metric.__name__ ? 'Used' : metric.__name__.split('_', 3)[2] }, |
||||||
|
min: 0, |
||||||
|
yUnits: "B", |
||||||
|
yAxisFormatter: PromConsole.NumberFormatter.humanize1024, |
||||||
|
yHoverFormatter: PromConsole.NumberFormatter.humanize1024, |
||||||
|
yTitle: 'Memory' |
||||||
|
}) |
||||||
|
</script> |
||||||
|
{{template "prom_content_tail" .}} |
||||||
|
|
||||||
|
{{template "tail"}} |
@ -0,0 +1,16 @@ |
|||||||
|
{{template "head" .}} |
||||||
|
|
||||||
|
{{template "prom_right_table_head"}} |
||||||
|
<tr> |
||||||
|
<th>Node</th> |
||||||
|
<th>{{ template "prom_query_drilldown" (args "sum(up{job='node'})") }} / {{ template "prom_query_drilldown" (args "count(up{job='node'})") }}</th> |
||||||
|
</tr> |
||||||
|
{{template "prom_right_table_tail"}} |
||||||
|
|
||||||
|
{{template "prom_content_head" .}} |
||||||
|
<h1>Node</h1> |
||||||
|
|
||||||
|
Choose an instance on the left. |
||||||
|
{{template "prom_content_tail" .}} |
||||||
|
|
||||||
|
{{template "tail"}} |
@ -0,0 +1,176 @@ |
|||||||
|
.prom_lhs_menu { |
||||||
|
float: left; |
||||||
|
margin-right: 2ex; |
||||||
|
background: #000000; |
||||||
|
min-height: 100%; |
||||||
|
overflow: auto; |
||||||
|
} |
||||||
|
.prom_lhs_menu ul { |
||||||
|
list-style: none; |
||||||
|
padding-left: .5ex; |
||||||
|
margin-left: .5ex; |
||||||
|
} |
||||||
|
.prom_lhs_menu li { |
||||||
|
padding-right: 1ex; |
||||||
|
padding-left: 1ex; |
||||||
|
} |
||||||
|
.prom_lhs_menu a, |
||||||
|
.prom_lhs_menu li { |
||||||
|
color: #999999; |
||||||
|
display: block; |
||||||
|
} |
||||||
|
.prom_lhs_menu a { |
||||||
|
text-decoration: none; |
||||||
|
} |
||||||
|
.prom_lhs_menu_selected a { |
||||||
|
pointer-events: none; |
||||||
|
cursor: default; |
||||||
|
} |
||||||
|
.prom_lhs_menu_selected { |
||||||
|
background: #555555; |
||||||
|
background-clip: padding-box; |
||||||
|
} |
||||||
|
.prom_lhs_menu span:hover, |
||||||
|
.prom_lhs_menu a:hover { |
||||||
|
background: #666666; |
||||||
|
} |
||||||
|
|
||||||
|
.prom_console_rhs { |
||||||
|
float: right; |
||||||
|
margin-left: 1ex; |
||||||
|
height: 100%; |
||||||
|
} |
||||||
|
.prom_console_rhs table { |
||||||
|
margin-top: 1ex; |
||||||
|
margin-bottom: 32px; /* Space for time control. */ |
||||||
|
} |
||||||
|
.prom_console_rhs th { |
||||||
|
text-align: center; |
||||||
|
} |
||||||
|
.prom_console_rhs td:nth-child(2) { |
||||||
|
text-align: right; |
||||||
|
} |
||||||
|
|
||||||
|
.prom_console_content { |
||||||
|
overflow: visible; |
||||||
|
margin-bottom: 32px; /* Space for time control. */ |
||||||
|
} |
||||||
|
|
||||||
|
.prom_query_drilldown { |
||||||
|
text-decoration: none; |
||||||
|
color: black; |
||||||
|
} |
||||||
|
.prom_query_drilldown:hover, a.prom_query_drilldown:active { |
||||||
|
text-decoration: underline; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
.rickshaw_legend { |
||||||
|
padding: 2px; |
||||||
|
margin-top: 1px; |
||||||
|
} |
||||||
|
.rickshaw_legend li { |
||||||
|
min-width: 0; |
||||||
|
} |
||||||
|
.rickshaw_legend ul li { |
||||||
|
list-style-type: none; |
||||||
|
display: inline-block; |
||||||
|
} |
||||||
|
.rickshaw_graph { |
||||||
|
width: 100%; |
||||||
|
padding: 0; |
||||||
|
} |
||||||
|
.prom_graph_hover_flipped.x_label { |
||||||
|
right: 0; |
||||||
|
} |
||||||
|
.prom_graph_hover_flipped.item { |
||||||
|
right: 10px; |
||||||
|
} |
||||||
|
.rickshaw_graph .detail .prom_graph_hover_flipped.item:before { |
||||||
|
content: "\25b8"; |
||||||
|
left: auto; |
||||||
|
right: 1px; |
||||||
|
font-size: 0.8em; |
||||||
|
} |
||||||
|
.prom_graph_ytitle { |
||||||
|
-webkit-transform: rotate(-90deg); |
||||||
|
-moz-transform: rotate(-90deg); |
||||||
|
font-size: 11px; |
||||||
|
font-family: Arial; |
||||||
|
max-width: 13px; |
||||||
|
white-space: nowrap; |
||||||
|
} |
||||||
|
.prom_graph_xtitle { |
||||||
|
text-align: center; |
||||||
|
font-size: 11px; |
||||||
|
font-family: Arial; |
||||||
|
} |
||||||
|
.prom_graph_loading { |
||||||
|
position: absolute; |
||||||
|
top: 0px; |
||||||
|
left: 0px; |
||||||
|
} |
||||||
|
.prom_graph_error { |
||||||
|
font-family: Arial; |
||||||
|
text-align: center; |
||||||
|
} |
||||||
|
a.prom_graph_link { |
||||||
|
text-decoration: none; |
||||||
|
color: black; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
.prom_graph_timecontrol { |
||||||
|
background: #000000; |
||||||
|
position: fixed; |
||||||
|
padding: 2px; |
||||||
|
bottom: 0; |
||||||
|
left: 0; |
||||||
|
width: 100%; |
||||||
|
text-align: center; |
||||||
|
z-index: 2; |
||||||
|
} |
||||||
|
.prom_graph_timecontrol_inner { |
||||||
|
display: inline-block; |
||||||
|
} |
||||||
|
.prom_graph_timecontrol button { |
||||||
|
font-size: 10pt; |
||||||
|
margin-right: .3em; |
||||||
|
padding-top: 2px; |
||||||
|
padding-bottom: 2px; |
||||||
|
} |
||||||
|
.prom_graph_timecontrol_refresh button { |
||||||
|
padding-top: 0; |
||||||
|
padding-bottom: 0; |
||||||
|
border-bottom: 0; |
||||||
|
border-top: 0; |
||||||
|
} |
||||||
|
.prom_graph_timecontrol_refresh .dropdown-menu { |
||||||
|
min-width: 70px; |
||||||
|
padding-top: 0; |
||||||
|
padding-bottom: 0; |
||||||
|
} |
||||||
|
.prom_graph_timecontrol input { |
||||||
|
font-size: 10pt; |
||||||
|
margin-left: -4px; |
||||||
|
margin-right: -4px; |
||||||
|
text-align: center; |
||||||
|
} |
||||||
|
.prom_graph_timecontrol label { |
||||||
|
display: inline; |
||||||
|
color: #999999; |
||||||
|
} |
||||||
|
.prom_graph_timecontrol .input-append { |
||||||
|
margin: 2px; |
||||||
|
} |
||||||
|
.prom_graph_timecontrol .input-append .btn:first-child i { |
||||||
|
margin-right: 4px; |
||||||
|
margin-left: 2px; |
||||||
|
} |
||||||
|
.prom_graph_timecontrol .input-append .btn:last-child i { |
||||||
|
margin-left: 6px; |
||||||
|
} |
||||||
|
.prom_graph_timecontrol .input-append i { |
||||||
|
padding-top: -4px; |
||||||
|
margin-top: 0; |
||||||
|
} |
@ -0,0 +1,584 @@ |
|||||||
|
/* |
||||||
|
* Functions to make it easier to write prometheus consoles, such |
||||||
|
* as graphs. |
||||||
|
* |
||||||
|
*/ |
||||||
|
|
||||||
|
PromConsole = {}; |
||||||
|
|
||||||
|
PromConsole.NumberFormatter = {}; |
||||||
|
PromConsole.NumberFormatter.prefixesBig = ["k", "M", "G", "T", "P", "E", "Z", "Y"]; |
||||||
|
PromConsole.NumberFormatter.prefixesBig1024 = ["ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi", "Yi"]; |
||||||
|
PromConsole.NumberFormatter.prefixesSmall = ["m", "u", "n", "p", "f", "a", "z", "y"]; |
||||||
|
|
||||||
|
PromConsole._stripTrailingZero = function(x) { |
||||||
|
if (x.indexOf("e") == -1) { |
||||||
|
// It's not safe to strip if it's scientific notation.
|
||||||
|
return x.replace(/\.?0*$/, ''); |
||||||
|
} |
||||||
|
return x; |
||||||
|
} |
||||||
|
|
||||||
|
// Humanize a number.
|
||||||
|
PromConsole.NumberFormatter.humanize = function(x) { |
||||||
|
var ret = PromConsole.NumberFormatter._humanize( |
||||||
|
x, PromConsole.NumberFormatter.prefixesBig, |
||||||
|
PromConsole.NumberFormatter.prefixesSmall, 1000); |
||||||
|
x = ret[0]; |
||||||
|
var prefix = ret[1]; |
||||||
|
if (Math.abs(x) < 1) { |
||||||
|
return x.toExponential(3) + prefix; |
||||||
|
} |
||||||
|
return PromConsole._stripTrailingZero(x.toFixed(3)) + prefix; |
||||||
|
} |
||||||
|
|
||||||
|
// Humanize a number, don't use milli/micro/etc. prefixes.
|
||||||
|
PromConsole.NumberFormatter.humanizeNoSmallPrefix = function(x) { |
||||||
|
if (Math.abs(x) < 1) { |
||||||
|
return PromConsole._stripTrailingZero(x.toPrecision(3)); |
||||||
|
} |
||||||
|
var ret = PromConsole.NumberFormatter._humanize( |
||||||
|
x, PromConsole.NumberFormatter.prefixesBig, |
||||||
|
[], 1000); |
||||||
|
x = ret[0]; |
||||||
|
var prefix = ret[1]; |
||||||
|
return PromConsole._stripTrailingZero(x.toFixed(3)) + prefix; |
||||||
|
} |
||||||
|
|
||||||
|
// Humanize a number with 1024 as the base, rather than 1000.
|
||||||
|
PromConsole.NumberFormatter.humanize1024 = function(x) { |
||||||
|
var ret = PromConsole.NumberFormatter._humanize( |
||||||
|
x, PromConsole.NumberFormatter.prefixesBig1024, |
||||||
|
[], 1024); |
||||||
|
x = ret[0]; |
||||||
|
var prefix = ret[1]; |
||||||
|
if (Math.abs(x) < 1) { |
||||||
|
return x.toExponential(3) + prefix; |
||||||
|
} |
||||||
|
return PromConsole._stripTrailingZero(x.toFixed(3)) + prefix; |
||||||
|
} |
||||||
|
|
||||||
|
// Humanize a number, returning an exact representation.
|
||||||
|
PromConsole.NumberFormatter.humanizeExact = function(x) { |
||||||
|
var ret = PromConsole.NumberFormatter._humanize( |
||||||
|
x, PromConsole.NumberFormatter.prefixesBig, |
||||||
|
PromConsole.NumberFormatter.prefixesSmall, 1000); |
||||||
|
return ret[0] + ret[1]; |
||||||
|
} |
||||||
|
|
||||||
|
PromConsole.NumberFormatter._humanize = function(x, prefixesBig, prefixesSmall, factor) { |
||||||
|
var prefix = "" |
||||||
|
if (x == 0) { |
||||||
|
/* Do nothing. */ |
||||||
|
} else if (Math.abs(x) >= 1) { |
||||||
|
for (var i=0; i < prefixesBig.length && Math.abs(x) >= factor; ++i) { |
||||||
|
x /= factor; |
||||||
|
prefix = prefixesBig[i]; |
||||||
|
} |
||||||
|
} else { |
||||||
|
for (var i=0; i < prefixesSmall.length && Math.abs(x) < 1; ++i) { |
||||||
|
x *= factor; |
||||||
|
prefix = prefixesSmall[i]; |
||||||
|
} |
||||||
|
} |
||||||
|
return [x, prefix]; |
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
PromConsole.TimeControl = function() { |
||||||
|
document.getElementById("prom_graph_duration_shrink").onclick = this.decreaseDuration.bind(this); |
||||||
|
document.getElementById("prom_graph_duration_grow").onclick = this.increaseDuration.bind(this); |
||||||
|
document.getElementById("prom_graph_time_back").onclick = this.decreaseEnd.bind(this); |
||||||
|
document.getElementById("prom_graph_time_forward").onclick = this.increaseEnd.bind(this); |
||||||
|
document.getElementById("prom_graph_refresh_button").onclick = this.refresh.bind(this); |
||||||
|
this.durationElement = document.getElementById("prom_graph_duration"); |
||||||
|
this.endElement = document.getElementById("prom_graph_time_end"); |
||||||
|
this.durationElement.oninput = this.dispatch.bind(this); |
||||||
|
this.endElement.oninput = this.dispatch.bind(this); |
||||||
|
this.endElement.oninput = this.dispatch.bind(this); |
||||||
|
this.refreshValueElement = document.getElementById("prom_graph_refresh_button_value"); |
||||||
|
|
||||||
|
var refreshList = document.getElementById("prom_graph_refresh_intervals"); |
||||||
|
var refreshIntervals = ["Off", "1m", "5m", "15m", "1h"]; |
||||||
|
for (var i=0; i < refreshIntervals.length; ++i) { |
||||||
|
var li = document.createElement("li"); |
||||||
|
li.onclick = this.setRefresh.bind(this, refreshIntervals[i]); |
||||||
|
li.textContent = refreshIntervals[i]; |
||||||
|
refreshList.appendChild(li); |
||||||
|
} |
||||||
|
|
||||||
|
this.durationElement.value = PromConsole.TimeControl.prototype.getHumanDuration( |
||||||
|
PromConsole.TimeControl._initialValues.duration); |
||||||
|
if (PromConsole.TimeControl._initialValues.endTimeNow === undefined) { |
||||||
|
this.endElement.value = PromConsole.TimeControl.prototype.getHumanDate( |
||||||
|
new Date(PromConsole.TimeControl._initialValues.endTime * 1000)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
PromConsole.TimeControl.timeFactors = { |
||||||
|
"y": 60 * 60 * 24 * 365, |
||||||
|
"w": 60 * 60 * 24 * 7, |
||||||
|
"d": 60 * 60 * 24, |
||||||
|
"h": 60 * 60, |
||||||
|
"m": 60, |
||||||
|
"s": 1 |
||||||
|
}; |
||||||
|
|
||||||
|
PromConsole.TimeControl.stepValues = [ |
||||||
|
"10s", "1m", "5m", "15m", "30m", "1h", "2h", "6h", "12h", "1d", "2d", |
||||||
|
"1w", "2w", "4w", "8w", "1y", "2y" |
||||||
|
]; |
||||||
|
|
||||||
|
PromConsole.TimeControl.prototype._setHash = function() { |
||||||
|
var duration = this.parseDuration(this.durationElement.value); |
||||||
|
var endTime = this.getEndDate() / 1000; |
||||||
|
window.location.hash = "#pctc" + encodeURIComponent(JSON.stringify( |
||||||
|
{duration: duration, endTime: endTime})); |
||||||
|
} |
||||||
|
|
||||||
|
PromConsole.TimeControl._initialValues = function() { |
||||||
|
var hash = window.location.hash; |
||||||
|
if (hash.indexOf('#pctc') == 0) { |
||||||
|
return JSON.parse(decodeURIComponent(hash.substring(5))); |
||||||
|
} |
||||||
|
return {duration: 3600, endTime: new Date().getTime() / 1000, endTimeNow: true}; |
||||||
|
}(); |
||||||
|
|
||||||
|
PromConsole.TimeControl.prototype.parseDuration = function(durationText) { |
||||||
|
var durationRE = new RegExp("^([0-9]+)([ywdhms]?)$"); |
||||||
|
var matches = durationText.match(durationRE); |
||||||
|
if (!matches) { return 3600; } |
||||||
|
var value = parseInt(matches[1]); |
||||||
|
var unit = matches[2] || 's'; |
||||||
|
return value * PromConsole.TimeControl.timeFactors[unit]; |
||||||
|
}; |
||||||
|
|
||||||
|
PromConsole.TimeControl.prototype.getHumanDuration = function(duration) { |
||||||
|
var units = []; |
||||||
|
for (var key in PromConsole.TimeControl.timeFactors) { |
||||||
|
units.push([PromConsole.TimeControl.timeFactors[key], key]); |
||||||
|
} |
||||||
|
units.sort(function(a, b) { return b[0] - a[0] }); |
||||||
|
for (var i = 0; i < units.length; ++i) { |
||||||
|
if (duration % units[i][0] == 0) { |
||||||
|
return (duration / units[i][0]) + units[i][1]; |
||||||
|
} |
||||||
|
} |
||||||
|
return duration; |
||||||
|
}; |
||||||
|
|
||||||
|
PromConsole.TimeControl.prototype.increaseDuration = function() { |
||||||
|
var durationSeconds = this.parseDuration(this.durationElement.value); |
||||||
|
for (var i = 0; i < PromConsole.TimeControl.stepValues.length; i++) { |
||||||
|
if (durationSeconds < this.parseDuration(PromConsole.TimeControl.stepValues[i])) { |
||||||
|
this.setDuration(PromConsole.TimeControl.stepValues[i]); |
||||||
|
this.dispatch(); |
||||||
|
return; |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
PromConsole.TimeControl.prototype.decreaseDuration = function() { |
||||||
|
var durationSeconds = this.parseDuration(this.durationElement.value); |
||||||
|
for (var i = PromConsole.TimeControl.stepValues.length - 1; i >= 0; i--) { |
||||||
|
if (durationSeconds > this.parseDuration(PromConsole.TimeControl.stepValues[i])) { |
||||||
|
this.setDuration(PromConsole.TimeControl.stepValues[i]); |
||||||
|
this.dispatch(); |
||||||
|
return; |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
PromConsole.TimeControl.prototype.setDuration = function(duration) { |
||||||
|
this.durationElement.value = duration; |
||||||
|
this._setHash(); |
||||||
|
}; |
||||||
|
|
||||||
|
PromConsole.TimeControl.prototype.getEndDate = function() { |
||||||
|
if (this.endElement.value == '') { |
||||||
|
return null; |
||||||
|
} |
||||||
|
return new Date(this.endElement.value).getTime(); |
||||||
|
}; |
||||||
|
|
||||||
|
PromConsole.TimeControl.prototype.getOrSetEndDate = function() { |
||||||
|
var date = this.getEndDate(); |
||||||
|
if (date) { |
||||||
|
return date; |
||||||
|
} |
||||||
|
date = new Date(); |
||||||
|
this.setEndDate(date); |
||||||
|
return date; |
||||||
|
} |
||||||
|
|
||||||
|
PromConsole.TimeControl.prototype.getHumanDate = function(date) { |
||||||
|
var hours = date.getHours() < 10 ? '0' + date.getHours() : date.getHours(); |
||||||
|
var minutes = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes(); |
||||||
|
return date.getFullYear() + "-" + (date.getMonth()+1) + "-" + date.getDate() + " " + |
||||||
|
hours + ":" + minutes; |
||||||
|
} |
||||||
|
|
||||||
|
PromConsole.TimeControl.prototype.setEndDate = function(date) { |
||||||
|
this.setRefresh("Off"); |
||||||
|
this.endElement.value = this.getHumanDate(date); |
||||||
|
this._setHash(); |
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
PromConsole.TimeControl.prototype.increaseEnd = function() { |
||||||
|
// Increase duration 25% range & convert ms to s.
|
||||||
|
this.setEndDate(new Date(this.getOrSetEndDate() + this.parseDuration(this.durationElement.value) * 1000/4 )); |
||||||
|
this.dispatch(); |
||||||
|
}; |
||||||
|
|
||||||
|
PromConsole.TimeControl.prototype.decreaseEnd = function() { |
||||||
|
this.setEndDate(new Date(this.getOrSetEndDate() - this.parseDuration(this.durationElement.value) * 1000/4 )); |
||||||
|
this.dispatch(); |
||||||
|
}; |
||||||
|
|
||||||
|
PromConsole.TimeControl.prototype.refresh = function() { |
||||||
|
this.endElement.value = ''; |
||||||
|
this._setHash(); |
||||||
|
this.dispatch(); |
||||||
|
} |
||||||
|
|
||||||
|
PromConsole.TimeControl.prototype.dispatch = function() { |
||||||
|
var durationSeconds = this.parseDuration(this.durationElement.value); |
||||||
|
var end = this.getEndDate(); |
||||||
|
if (end === null) { |
||||||
|
end = new Date().getTime(); |
||||||
|
} |
||||||
|
for (var i = 0; i< PromConsole._graph_registry.length; i++) { |
||||||
|
var graph = PromConsole._graph_registry[i]; |
||||||
|
graph.params.duration = durationSeconds; |
||||||
|
graph.params.endTime = end / 1000; |
||||||
|
graph.dispatch(); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
PromConsole.TimeControl.prototype._refreshInterval = null; |
||||||
|
|
||||||
|
PromConsole.TimeControl.prototype.setRefresh = function(duration) { |
||||||
|
if (this._refreshInterval !== null) { |
||||||
|
window.clearInterval(this._refreshInterval); |
||||||
|
this._refreshInterval = null; |
||||||
|
} |
||||||
|
if (duration != "Off") { |
||||||
|
if (this.endElement.value != '') { |
||||||
|
this.refresh(); |
||||||
|
} |
||||||
|
var durationSeconds = this.parseDuration(duration); |
||||||
|
this._refreshInterval = window.setInterval(this.dispatch.bind(this), durationSeconds * 1000); |
||||||
|
} |
||||||
|
this.refreshValueElement.textContent = duration; |
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// List of all graphs, used by time controls.
|
||||||
|
PromConsole._graph_registry = []; |
||||||
|
|
||||||
|
PromConsole.graphDefaults = { |
||||||
|
expr: null, // Expression to graph. Can be a list of strings.
|
||||||
|
node: null, // DOM node to place graph under.
|
||||||
|
// How long the graph is over, in seconds.
|
||||||
|
duration: PromConsole.TimeControl._initialValues.duration, |
||||||
|
// The unixtime the graph ends at.
|
||||||
|
endTime: PromConsole.TimeControl._initialValues.endTime, |
||||||
|
width: null, // Height of the graph div, excluding titles and legends.
|
||||||
|
// Defaults to auto-detection.
|
||||||
|
height: 200, // Height of the graph div, excluding titles and legends.
|
||||||
|
min: "auto", // Minimum Y-axis value, defaults to lowest data value.
|
||||||
|
max: undefined, // Maximum Y-axis value, defaults to highest data value.
|
||||||
|
renderer: 'line', // Type of graphs, options are 'line' and 'area'.
|
||||||
|
name: null, // What to call plots, defaults to trying to do
|
||||||
|
// something reasonable.
|
||||||
|
// If a string, it'll use that. [[ label ]] will be substituted.
|
||||||
|
// If a function it'll be called with a map of keys to values,
|
||||||
|
// and should return the name to use.
|
||||||
|
xTitle: "Time", // The title of the x axis.
|
||||||
|
yUnits: "", // The units of the y axis.
|
||||||
|
yTitle: "", // The title of the y axis.
|
||||||
|
// Number formatter for y axis.
|
||||||
|
yAxisFormatter: PromConsole.NumberFormatter.humanize, |
||||||
|
// Number formatter for y values hover detail.
|
||||||
|
yHoverFormatter: PromConsole.NumberFormatter.humanizeExact, |
||||||
|
}; |
||||||
|
|
||||||
|
PromConsole.Graph = function(params) { |
||||||
|
for (var k in PromConsole.graphDefaults) { |
||||||
|
if (!(k in params)) { |
||||||
|
params[k] = PromConsole.graphDefaults[k]; |
||||||
|
} |
||||||
|
} |
||||||
|
if (typeof params.expr == "string") { |
||||||
|
params.expr = [params.expr] |
||||||
|
} |
||||||
|
|
||||||
|
this.params = params; |
||||||
|
this.rendered_data = null; |
||||||
|
PromConsole._graph_registry.push(this); |
||||||
|
|
||||||
|
/* |
||||||
|
* Table layout: |
||||||
|
* | yTitle | Graph | |
||||||
|
* | | xTitle | |
||||||
|
* | /graph | Legend | |
||||||
|
*/ |
||||||
|
var table = document.createElement("table"); |
||||||
|
table.className = "prom_graph_table"; |
||||||
|
params.node.appendChild(table); |
||||||
|
var tr = document.createElement("tr"); |
||||||
|
table.appendChild(tr); |
||||||
|
var yTitleTd = document.createElement("td"); |
||||||
|
tr.appendChild(yTitleTd); |
||||||
|
var yTitleDiv = document.createElement("td"); |
||||||
|
yTitleTd.appendChild(yTitleDiv); |
||||||
|
yTitleDiv.className = "prom_graph_ytitle"; |
||||||
|
yTitleDiv.textContent = params.yTitle + (params.yUnits ? " (" + params.yUnits.trim() + ")" : ""); |
||||||
|
|
||||||
|
this.graphTd = document.createElement("td"); |
||||||
|
tr.appendChild(this.graphTd); |
||||||
|
this.graphTd.className = "rickshaw_graph"; |
||||||
|
this.graphTd.width = params.width; |
||||||
|
this.graphTd.height = params.height; |
||||||
|
|
||||||
|
tr = document.createElement("tr"); |
||||||
|
table.appendChild(tr); |
||||||
|
tr.appendChild(document.createElement("td")); |
||||||
|
var xTitleTd = document.createElement("td"); |
||||||
|
tr.appendChild(xTitleTd); |
||||||
|
xTitleTd.className = "prom_graph_xtitle"; |
||||||
|
xTitleTd.textContent = params.xTitle; |
||||||
|
|
||||||
|
tr = document.createElement("tr"); |
||||||
|
table.appendChild(tr); |
||||||
|
var graphLinkTd = document.createElement("td"); |
||||||
|
tr.appendChild(graphLinkTd); |
||||||
|
var graphLinkA = document.createElement("a"); |
||||||
|
graphLinkTd.appendChild(graphLinkA); |
||||||
|
graphLinkA.className = "prom_graph_link"; |
||||||
|
graphLinkA.textContent = "+"; |
||||||
|
graphLinkA.href = PromConsole._graphsToSlashGraphURL(params.expr); |
||||||
|
var legendTd = document.createElement("td"); |
||||||
|
tr.appendChild(legendTd); |
||||||
|
this.legendDiv = document.createElement("div"); |
||||||
|
legendTd.width = params.width; |
||||||
|
legendTd.appendChild(this.legendDiv); |
||||||
|
|
||||||
|
window.addEventListener('resize', function() { |
||||||
|
if(this.rendered_data !== null) { |
||||||
|
this._render(this.rendered_data); |
||||||
|
} |
||||||
|
}.bind(this)) |
||||||
|
|
||||||
|
this.dispatch(); |
||||||
|
|
||||||
|
}; |
||||||
|
|
||||||
|
PromConsole.Graph.prototype._render = function(data) { |
||||||
|
var palette = new Rickshaw.Color.Palette(); |
||||||
|
var series = []; |
||||||
|
|
||||||
|
// This will be used on resize.
|
||||||
|
this.rendered_data = data; |
||||||
|
|
||||||
|
var nameFunc; |
||||||
|
if (this.params.name === null) { |
||||||
|
nameFunc = PromConsole._chooseNameFunction(data); |
||||||
|
} else if (typeof this.params.name == "string") { |
||||||
|
nameFunc = function(metric) { |
||||||
|
return PromConsole._interpolateName(this.params.name, metric); |
||||||
|
}.bind(this); |
||||||
|
} else { |
||||||
|
nameFunc = this.params.name; |
||||||
|
} |
||||||
|
|
||||||
|
// Get the data into the right format.
|
||||||
|
for (var e = 0; e < data.length; e++) { |
||||||
|
var len = 0; |
||||||
|
for (var i = 0; i < data[e].Value.length; i++) { |
||||||
|
series[len++] = { |
||||||
|
data: data[e].Value[i].Values.map(function(s) {return {x: s.Timestamp, y: parseFloat(s.Value)} }), |
||||||
|
color: palette.color(), |
||||||
|
name: nameFunc(data[e].Value[i].Metric), |
||||||
|
}; |
||||||
|
} |
||||||
|
} |
||||||
|
this._clearGraph(); |
||||||
|
if (!series.length) { |
||||||
|
var errorText = document.createElement("div"); |
||||||
|
errorText.className = 'prom_graph_error'; |
||||||
|
errorText.textContent = 'No timeseries returned'; |
||||||
|
this.graphTd.appendChild(errorText); |
||||||
|
return; |
||||||
|
} |
||||||
|
// Render.
|
||||||
|
var graph = new Rickshaw.Graph({ |
||||||
|
interpolation: "linear", |
||||||
|
width: this.graphTd.offsetWidth, |
||||||
|
height: this.params.height, |
||||||
|
element: this.graphTd, |
||||||
|
renderer: this.params.renderer, |
||||||
|
max: this.params.max, |
||||||
|
min: this.params.min, |
||||||
|
series: series |
||||||
|
}); |
||||||
|
var hoverDetail = new Rickshaw.Graph.HoverDetail({ |
||||||
|
graph: graph, |
||||||
|
onRender: function() { |
||||||
|
var xLabel = this.element.getElementsByClassName("x_label")[0]; |
||||||
|
var item = this.element.getElementsByClassName("item")[0]; |
||||||
|
if (xLabel.offsetWidth + xLabel.offsetLeft + this.element.offsetLeft > graph.element.offsetWidth |
||||||
|
|| item.offsetWidth + item.offsetLeft + this.element.offsetLeft > graph.element.offsetWidth) { |
||||||
|
xLabel.classList.add("prom_graph_hover_flipped"); |
||||||
|
item.classList.add("prom_graph_hover_flipped"); |
||||||
|
} else { |
||||||
|
xLabel.classList.remove("prom_graph_hover_flipped"); |
||||||
|
item.classList.remove("prom_graph_hover_flipped"); |
||||||
|
} |
||||||
|
}, |
||||||
|
yFormatter: function(y) {return this.params.yHoverFormatter(y) + this.params.yUnits}.bind(this) |
||||||
|
}); |
||||||
|
var yAxis = new Rickshaw.Graph.Axis.Y({ |
||||||
|
graph: graph, |
||||||
|
tickFormat: this.params.yAxisFormatter |
||||||
|
}); |
||||||
|
var xAxis = new Rickshaw.Graph.Axis.Time({ |
||||||
|
graph: graph, |
||||||
|
}); |
||||||
|
var legend = new Rickshaw.Graph.Legend({ |
||||||
|
graph: graph, |
||||||
|
element: this.legendDiv |
||||||
|
}); |
||||||
|
xAxis.render(); |
||||||
|
yAxis.render(); |
||||||
|
graph.render(); |
||||||
|
}; |
||||||
|
|
||||||
|
PromConsole.Graph.prototype._clearGraph = function() { |
||||||
|
while (this.graphTd.lastChild) { |
||||||
|
this.graphTd.removeChild(this.graphTd.lastChild); |
||||||
|
} |
||||||
|
while (this.legendDiv.lastChild) { |
||||||
|
this.legendDiv.removeChild(this.legendDiv.lastChild); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
PromConsole.Graph.prototype._xhrs = [] |
||||||
|
|
||||||
|
PromConsole.Graph.prototype.dispatch = function() { |
||||||
|
for (var j = 0; j < this._xhrs.length; j++) { |
||||||
|
this._xhrs[j].abort(); |
||||||
|
} |
||||||
|
var all_data = new Array(this.params.expr.length); |
||||||
|
this._xhrs = new Array(this.params.expr.length); |
||||||
|
var pending_requests = this.params.expr.length; |
||||||
|
for (var i = 0; i < this.params.expr.length; ++i) { |
||||||
|
var endTime = this.params.endTime; |
||||||
|
var url = "/api/query_range?expr=" + encodeURIComponent(this.params.expr[i]) |
||||||
|
+ "&step=" + this.params.duration / this.graphTd.offsetWidth |
||||||
|
+ "&range=" + this.params.duration + "&end=" + endTime; |
||||||
|
var xhr = new XMLHttpRequest(); |
||||||
|
xhr.open('get', url, true); |
||||||
|
xhr.responseType = 'json'; |
||||||
|
xhr.onerror = function(xhr, i) { |
||||||
|
this._clearGraph(); |
||||||
|
var errorText = document.createElement("div"); |
||||||
|
errorText.className = 'prom_graph_error'; |
||||||
|
errorText.textContent = 'Error loading data'; |
||||||
|
this.graphTd.appendChild(errorText); |
||||||
|
console.log('Error loading data for ' + this.params.expr[i]); |
||||||
|
pending_requests = 0; |
||||||
|
// onabort gets any aborts.
|
||||||
|
for (var j = 0; j < pending_requests; j++) { |
||||||
|
this._xhrs[j].abort(); |
||||||
|
} |
||||||
|
}.bind(this, xhr, i) |
||||||
|
xhr.onload = function(xhr, i) { |
||||||
|
if (pending_requests == 0) { |
||||||
|
// Got an error before this success.
|
||||||
|
return; |
||||||
|
} |
||||||
|
var data = xhr.response; |
||||||
|
pending_requests -= 1; |
||||||
|
all_data[i] = data; |
||||||
|
if (pending_requests == 0) { |
||||||
|
this._xhrs = []; |
||||||
|
this._render(all_data); |
||||||
|
} |
||||||
|
}.bind(this, xhr, i) |
||||||
|
xhr.send(); |
||||||
|
this._xhrs[i] = xhr; |
||||||
|
} |
||||||
|
|
||||||
|
var loadingImg = document.createElement("img"); |
||||||
|
loadingImg.src = '/static/img/ajax-loader.gif'; |
||||||
|
loadingImg.alt = 'Loading...'; |
||||||
|
loadingImg.className = 'prom_graph_loading'; |
||||||
|
this.graphTd.appendChild(loadingImg); |
||||||
|
}; |
||||||
|
|
||||||
|
// Substitue the value of 'label' for [[ label ]].
|
||||||
|
PromConsole._interpolateName = function(name, metric) { |
||||||
|
var re = /(.*?)\[\[\s*(\w+)+\s*\]\](.*?)/g; |
||||||
|
var result = ''; |
||||||
|
while (match = re.exec(name)) { |
||||||
|
result = result + match[1] + metric[match[2]] + match[3] |
||||||
|
} |
||||||
|
if (!result) { |
||||||
|
return name; |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
// Given the data returned by the API, return an appropriate function
|
||||||
|
// to return plot names.
|
||||||
|
PromConsole._chooseNameFunction = function(data) { |
||||||
|
// By default, use the full metric name.
|
||||||
|
var nameFunc = function (metric) { |
||||||
|
name = metric.__name__ + "{"; |
||||||
|
for (var label in metric) { |
||||||
|
if (label.substring(0,2) == "__") { |
||||||
|
continue; |
||||||
|
} |
||||||
|
name += label + "='" + metric[label] + "',"; |
||||||
|
} |
||||||
|
return name + "}"; |
||||||
|
} |
||||||
|
// If only one label varies, use that value.
|
||||||
|
var labelValues = {}; |
||||||
|
for (var e = 0; e < data.length; e++) { |
||||||
|
for (var i = 0; i < data[e].Value.length; i++) { |
||||||
|
for (var label in data[e].Value[i].Metric) { |
||||||
|
if (!(label in labelValues)) { |
||||||
|
labelValues[label] = {}; |
||||||
|
} |
||||||
|
labelValues[label][data[e].Value[i].Metric[label]] = 1; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
var multiValueLabels = []; |
||||||
|
for (var label in labelValues) { |
||||||
|
if (Object.keys(labelValues[label]).length > 1) { |
||||||
|
multiValueLabels.push(label); |
||||||
|
} |
||||||
|
} |
||||||
|
if (multiValueLabels.length == 1) { |
||||||
|
nameFunc = function(metric) { |
||||||
|
return metric[multiValueLabels[0]]; |
||||||
|
} |
||||||
|
} |
||||||
|
return nameFunc; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
// Given a list of expressions, produce the /graph url for them.
|
||||||
|
PromConsole._graphsToSlashGraphURL = function(exprs) { |
||||||
|
var data = []; |
||||||
|
for (var i = 0; i < exprs.length; ++i) { |
||||||
|
data.push({'expr' : exprs[i]}); |
||||||
|
} |
||||||
|
return '/graph#' + encodeURIComponent(JSON.stringify(data)); |
||||||
|
|
||||||
|
}; |
Loading…
Reference in new issue