Skip to content

Instantly share code, notes, and snippets.

@mikepea
Last active February 24, 2022 10:53
Show Gist options
  • Save mikepea/07b1cede92c119e4f297 to your computer and use it in GitHub Desktop.
Save mikepea/07b1cede92c119e4f297 to your computer and use it in GitHub Desktop.
Grafana Scripted Dashboard example for collectd with SeparateInstances and StoreRates enabled
/* global _ */
/*
* Complex scripted dashboard
* This script generates a dashboard object that Grafana can load. It also takes a number of user
* supplied URL parameters (int ARGS variable)
*
* Global accessable variables
* window, document, $, jQuery, ARGS, moment
*
* Return a dashboard object, or a function
*
* For async scripts, return a function, this function must take a single callback function,
* call this function with the dasboard object
*
* Author: Anatoliy Dobrosynets, Recorded Future, Inc.
*/
// accessable variables in this scope
var window, document, ARGS, $, jQuery, moment, kbn;
// use defaults for URL arguments
var arg_env = 'metrics';
var arg_i = 'master';
var arg_span = 4;
var arg_from = '2h';
if(!_.isUndefined(ARGS.span)) {
arg_span = ARGS.span;
}
if(!_.isUndefined(ARGS.from)) {
arg_from = ARGS.from;
}
if(!_.isUndefined(ARGS.env)) {
arg_env = ARGS.env;
}
if(!_.isUndefined(ARGS.i)) {
arg_i = ARGS.i; // instance name
}
// return dashboard filter_list
// optionally include 'All'
function get_filter_object(name,query,show_all){
show_all = (typeof show_all === "undefined") ? true : show_all;
var arr = find_filter_values(query);
var opts = [];
for (var i in arr) {
opts.push({"text":arr[i], "value":arr[i]});
};
if (show_all == true) {
opts.unshift({"text":"All", "value": '{'+arr.join()+'}'});
};
return {
type: "filter",
name: name,
query: query,
options: opts,
current: opts[0],
includeAll: show_all
}
};
// execute graphite-api /metrics/find query
// return array of metric last names ( func('test.cpu-*') returns ['cpu-0','cpu-1',..] )
function find_filter_values(query){
var search_url = window.location.protocol + '//' + window.location.hostname.replace(/^grafana/,"graphite") + (window.location.port ? ":" + window.location.port : "") + '/metrics/find/?query=' + query;
var res = [];
var req = new XMLHttpRequest();
req.open('GET', search_url, false);
req.send(null);
var obj = JSON.parse(req.responseText);
for(var key in obj) {
if (obj[key].hasOwnProperty("text")) {
res.push(obj[key]["text"]);
}
}
return res;
};
// execute graphite-api /metrics/expand query
// return array of metric full names (func('*.cpu-*') returns ['test.cpu-0','test.cpu-1',..] )
function expand_filter_values(query){
var search_url = window.location.protocol + '//' + window.location.host + '/_graphite/metrics/expand/?query=' + query;
var req = new XMLHttpRequest();
req.open('GET', search_url, false);
req.send(null);
var obj = JSON.parse(req.responseText);
if (obj.hasOwnProperty('results')) {
return obj['results'];
} else {
return [];
};
};
// used to calculate aliasByNode index in panel template
function len(prefix){
return prefix.split('.').length - 2;
};
//---------------------------------------------------------------------------------------
function panel_collectd_delta_cpu(title,prefix){
var idx = len(prefix);
return {
title: title,
type: 'graphite',
span: arg_span,
renderer: "flot",
y_formats: ["none"],
grid: {max: null, min: 0},
lines: true,
fill: 1,
linewidth: 1,
stack: true,
legend: {show: true},
percentage: true,
nullPointMode: "null",
tooltip: {
value_type: "individual",
query_as_alias: true
},
targets: [
{ "target": "alias(sumSeries(" + prefix + "[[instance]].cpu.*.cpu.user),'user')" },
{ "target": "alias(sumSeries(" + prefix + "[[instance]].cpu.*.cpu.system),'system')" },
{ "target": "alias(sumSeries(" + prefix + "[[instance]].cpu.*.cpu.idle),'idle')" },
{ "target": "alias(sumSeries(" + prefix + "[[instance]].cpu.*.cpu.wait),'wait')" },
{ "target": "alias(sumSeries(" + prefix + "[[instance]].cpu.*.cpu.steal),'steal')" },
{ "target": "alias(sumSeries(" + prefix + "[[instance]].cpu.*.cpu.nice),'nice')" },
{ "target": "alias(sumSeries(" + prefix + "[[instance]].cpu.*.cpu.softirq),'irq')" },
{ "target": "alias(sumSeries(" + prefix + "[[instance]].cpu.*.cpu.interrupt),'intrpt')" },
],
aliasColors: {
"user": "#508642",
"system": "#EAB839",
"wait": "#890F02",
"steal": "#E24D42",
"idle": "#6ED0E0"
}
}
};
function panel_collectd_memory(title,prefix){
var idx = len(prefix);
return {
title: title,
type: 'graphite',
span: arg_span,
y_formats: ["bytes"],
grid: {max: null, min: 0},
lines: true,
fill: 1,
linewidth: 1,
stack: true,
nullPointMode: "null",
targets: [
{ "target": "aliasByNode(movingMedian(" + prefix + "[[instance]].memory.memory.{used,free,cached,buffered},'15min')," +(idx+4)+ ")" },
],
aliasColors: {
"free": "#629E51",
"used": "#1F78C1",
"cached": "#EF843C",
"buffered": "#CCA300"
}
}
};
function panel_collectd_loadavg(title,prefix){
var idx = len(prefix);
return {
title: title,
type: 'graphite',
span: arg_span,
y_formats: ["none"],
grid: {max: null, min: 0},
lines: true,
fill: 0,
linewidth: 2,
nullPointMode: "null",
targets: [
{ "target": "aliasByNode(movingMedian(" + prefix + "[[instance]].load.load.midterm,'10min')," +(idx+4)+ ")" },
]
}
};
function panel_collectd_swap_size(title,prefix){
var idx = len(prefix);
return {
title: title,
type: 'graphite',
span: arg_span,
y_formats: ["bytes"],
grid: {max: null, min: 0, leftMin: 0},
lines: true,
fill: 1,
linewidth: 1,
stack: true,
nullPointMode: "null",
targets: [
{ "target": "aliasByNode(" + prefix + "[[instance]].swap.swap.{free,used,cached}," +(idx+3)+ ")" },
],
aliasColors: {
"used": "#1F78C1",
"cached": "#EAB839",
"free": "#508642"
}
}
};
function panel_collectd_swap_io(title,prefix){
var idx = len(prefix);
return {
title: title,
type: 'graphite',
span: arg_span,
y_formats: ["bytes"],
grid: {max: null, min: 0},
lines: true,
fill: 1,
linewidth: 2,
nullPointMode: "null",
targets: [
{ "target": "aliasByNode(movingMedian(nonNegativeDerivative(keepLastValue(" + prefix + "[[instance]].swap.swap_io.in,10),0),'5min')," +(idx+3)+ ")" },
{ "target": "aliasByNode(movingMedian(scale(nonNegativeDerivative(keepLastValue(" + prefix + "[[instance]].swap.swap.io-out,10),0),-1),'5min')," +(idx+3)+ ")" },
]
}
};
function panel_collectd_network_octets(title,prefix,intrf){
intrf = (typeof intrf === "undefined") ? 'eth0' : intrf;
var idx = len(prefix);
return {
title: title + ', ' + intrf,
type: 'graphite',
span: arg_span,
y_formats: ["bytes"],
grid: {max: null, min: null},
lines: true,
fill: 1,
linewidth: 2,
nullPointMode: "null",
targets: [
{ "target": "aliasByNode(movingMedian(keepLastValue(" + prefix + "[[instance]].interface." + intrf + ".if_octets.rx,10),'5min')," +(idx+4)+ ")" },
{ "target": "aliasByNode(movingMedian(scale(keepLastValue(" + prefix + "[[instance]].interface." + intrf + ".if_octets.tx,10),-1),'5min')," +(idx+4)+ ")" }
]
}
};
function panel_collectd_network_packets(title,prefix,intrf){
intrf = (typeof intrf === "undefined") ? 'eth0' : intrf;
var idx = len(prefix);
return {
title: title + ', ' + intrf,
type: 'graphite',
span: arg_span,
y_formats: ["bytes"],
grid: {max: null, min: null},
lines: true,
fill: 1,
linewidth: 2,
nullPointMode: "null",
targets: [
{ "target": "aliasByNode(movingMedian(keepLastValue(" + prefix + "[[instance]].interface." + intrf + ".if_packets.rx,10),'5min')," +(idx+4)+ ")" },
{ "target": "aliasByNode(movingMedian(scale(keepLastValue(" + prefix + "[[instance]].interface." + intrf + ".if_packets.tx,10),-1),'5min')," +(idx+4)+ ")" }
]
}
};
function panel_collectd_df(title,prefix,vol){
vol = (typeof vol === "undefined") ? 'root' : vol;
var idx = len(prefix);
return {
title: title + ', ' + vol,
type: 'graphite',
span: arg_span,
y_formats: ["bytes"],
grid: {max: null, min: 0, leftMin: 0},
lines: true,
fill: 1,
linewidth: 2,
stack: true,
nullPointMode: "null",
targets: [
{ "target": "aliasByNode(" + prefix + "[[instance]].df." + vol + ".df_complex.{free,used,reserved}," +(idx+3)+ ")" },
],
aliasColors: {
"used": "#447EBC",
"free": "#508642",
"reserved": "#EAB839"
}
}
};
function panel_collectd_disk(title,prefix,vol){
vol = (typeof vol === "undefined") ? 'sda' : vol;
var idx = len(prefix);
return {
title: title + ', ' + vol,
type: 'graphite',
span: arg_span,
y_formats: ["none"],
grid: {max: null, min: null},
lines: true,
fill: 1,
linewidth: 2,
nullPointMode: "null",
targets: [
{ "target": "aliasByNode(nonNegativeDerivative(" + prefix + "[[instance]].disk." + vol + ".disk_ops.write,10)," +(idx+2)+ "," +(idx+4)+ ")" },
{ "target": "aliasByNode(scale(nonNegativeDerivative(" + prefix + "[[instance]].disk." + vol + ".disk_ops.read,10),-1)," +(idx+2)+ "," +(idx+4)+ ")" }
],
aliasColors: {
"write": "#447EBC",
"read": "#508642",
}
}
};
/*
row templates
*/
function row_delimiter(title){
return {
title: "_____ " + title,
height: "20px",
collapse: false,
editable: false,
collapsable: false,
panels: [{
title: title,
editable: false,
span: 12,
type: "text",
mode: "text"
}]
}
};
function row_cpu_memory(title,prefix){
return {
title: title,
height: '250px',
collapse: false,
panels: [
panel_collectd_delta_cpu('CPU, %',prefix),
panel_collectd_memory('Memory',prefix),
panel_collectd_loadavg('Load avg, 10min',prefix)
]
}
};
function row_swap(title,prefix){
return {
title: title,
height: '250px',
collapse: true,
panels: [
panel_collectd_swap_size('Swap size',prefix),
panel_collectd_swap_io('Swap IO',prefix),
]
}
};
function row_network(title,prefix,filter){
var interfaces = find_filter_values(filter + '.interface.*');
var panels_network = [];
for (var i in interfaces) {
panels_network.push(
panel_collectd_network_octets('network octets',prefix,interfaces[i]),
panel_collectd_network_packets('network packets',prefix,interfaces[i])
);
};
return {
title: title,
height: '250px',
collapse: true,
panels: panels_network
}
};
function row_disk_space(title,prefix,filter){
var volumes = find_filter_values(filter + '.df.*');
panels_disk_space = [];
for (var i in volumes) {
panels_disk_space.push(panel_collectd_df('disk space',prefix,volumes[i]));
};
return {
title: title,
height: '250px',
collapse: true,
panels: panels_disk_space
}
};
function row_disk_usage(title,prefix,filter){
var volumes = find_filter_values(filter + '.disk.*');
var panels_disk_usage = [];
for (var i in volumes) {
panels_disk_usage.push(panel_collectd_disk('disk ops read/write',prefix,volumes[i]));
};
return {
title: title,
height: '250px',
collapse: true,
panels: panels_disk_usage
}
};
//---------------------------------------------------------------------------------------
return function(callback) {
// Setup some variables
var dashboard;
/* prefix - depends on actual Graphite tree.
In my case it depends on environment which can be passed as argument too.
.collectd.hosts.
.statsd.hosts.
.jmxtrans.hosts.
*/
//var prefix = arg_env + '.collectd.hosts.';
var prefix = arg_env + '.';
var arg_filter = prefix + arg_i;
// set filter
var dashboard_filter = {
time: {
from: "now-" + arg_from,
to: "now"
},
list: [
get_filter_object("instance",arg_filter,false)
]
};
// define pulldowns
pulldowns = [
{
type: "filtering",
collapse: false,
notice: false,
enable: true
},
{
type: "annotations",
enable: false
}
];
// Intialize a skeleton with nothing but a rows array and service object
dashboard = {
rows : [],
services : {}
};
dashboard.title = arg_i + ' (' + arg_env + ')';
dashboard.editable = true;
dashboard.pulldowns = pulldowns;
dashboard.services.filter = dashboard_filter;
// custom dashboard rows (appended to the default dashboard rows)
var optional_rows = [];
$.ajax({
method: 'GET',
url: '/'
})
.done(function(result) {
// costruct dashboard rows
dashboard.rows.push(
row_cpu_memory('cpu, memory',prefix),
row_swap('swap',prefix),
row_network('network',prefix,arg_filter),
row_disk_space('disk space',prefix,arg_filter),
row_disk_usage('disk ops',prefix,arg_filter)
);
// custom rows
for (var i in optional_rows){
dashboard.rows.push(optional_rows[i]);
};
// when dashboard is composed call the callback
// function and pass the dashboard
callback(dashboard);
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment