Prepare for using Grafana annotations with tags (coming in Grafana 5.4) (#2152)

* Prepare for using Grafana annotations with tags (coming in Grafana 5.4) #1826

* push the cli when 5.4 Grafana is released
This commit is contained in:
Peter Hedenskog 2018-09-28 09:34:06 +02:00 committed by GitHub
parent a9073bf3fd
commit 3e24d65466
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 301 additions and 71 deletions

View File

@ -8,6 +8,7 @@ let yargs = require('yargs'),
cliUtil = require('./util'),
fs = require('fs'),
set = require('lodash.set'),
// grafanaConfig = require('../plugins/grafana/index').config,
graphiteConfig = require('../plugins/graphite/index').config,
influxdbConfig = require('../plugins/influxdb/index').config,
browsertimeConfig = require('../plugins/browsertime/index').config,
@ -394,6 +395,20 @@ module.exports.parseCommandLine = function parseCommandLine() {
describe: 'The max number of pages to test. Default is no limit.',
group: 'Crawler'
})
/**
Grafana cli option
*/
/** Activate them when Grafana 5.4 is live
.option('grafana.host', {
describe: 'The Grafana host used when sending annotations.',
group: 'Grafana'
})
.option('grafana.port', {
default: grafanaConfig.port,
describe: 'The Grafana port used when sending annotations to Grafana.',
group: 'Grafana'
})
*/
/**
Graphite cli option
*/

View File

@ -0,0 +1,52 @@
'use strict';
const sendAnnotations = require('./send-annotation');
const throwIfMissing = require('../../support/util').throwIfMissing;
const defaultConfig = {
port: 80
};
module.exports = {
open(context, options) {
throwIfMissing(options.grafana, ['host', 'port'], 'grafana');
this.options = options;
this.timestamp = context.timestamp;
this.resultUrls = context.resultUrls;
this.annotationType = 'webpagetest.pageSummary';
this.tsdbType = 'graphite';
this.make = context.messageMaker('grafana').make;
},
processMessage(message, queue) {
// First catch if we are running Browsertime and/or WebPageTest
if (message.type === 'browsertime.setup') {
this.annotationType = 'browsertime.pageSummary';
} else if (message.type === 'sitespeedio.setup') {
// Let other plugins know that the Grafana plugin is alive
queue.postMessage(this.make('grafana.setup'));
} else if (message.type === 'influxdb.setup') {
// Default we use Graphite config, else use influxdb
this.tsdbType = 'influxdb';
} else if (message.type === 'browsertime.config') {
if (message.data.screenshot) {
this.useScreenshots = message.data.screenshot;
this.screenshotType = message.data.screenshotType;
}
} else if (
message.type === this.annotationType &&
this.resultUrls.hasBaseUrl()
) {
const absolutePagePath = this.resultUrls.absoluteSummaryPageUrl(
message.url
);
return sendAnnotations.send(
message.url,
message.group,
absolutePagePath,
this.screenShotsEnabledInBrowsertime,
this.screenshotType,
this.timestamp,
this.tsdbType,
this.options
);
}
},
config: defaultConfig
};

View File

@ -0,0 +1,106 @@
'use strict';
const http = require('http');
const https = require('https');
const log = require('intel').getLogger('sitespeedio.plugin.grafana');
const Promise = require('bluebird');
const tsdbUtil = require('../../support/tsdbUtil');
const annotationsHelper = require('../../support/annotationsHelper');
const util = require('../../support/util');
module.exports = {
send(
url,
group,
absolutePagePath,
screenShotsEnabledInBrowsertime,
screenshotType,
time,
tsdbType,
options
) {
// The tags make it possible for the dashboard to use the
// templates to choose which annotations that will be showed.
// That's why we need to send tags that matches the template
// variables in Grafana.
const connectivity = tsdbUtil.getConnectivity(options);
const browser = options.browser;
// Hmm, here we have hardcoded Graphite ...
const namespace = options.graphite.namespace.split('.');
const urlAndGroup = tsdbUtil
.getURLAndGroup(
options,
group,
url,
tsdbType === 'graphite'
? options.graphite.includeQueryParams
: options.influxdb.includeQueryParams
)
.split('.');
const tags = [
connectivity,
browser,
namespace[0],
namespace[1],
urlAndGroup[0],
urlAndGroup[1]
];
const extraTags = util.toArray(options.grafana.annotationTag);
// We got some extra tag(s) from the user, let us add them to the annotation
if (extraTags.length > 0) {
tags.push(...extraTags);
}
const tagsArray = annotationsHelper.getTagsAsArray(tags);
const message = annotationsHelper.getAnnotationMessage(
absolutePagePath,
screenShotsEnabledInBrowsertime,
screenshotType,
options
);
const timestamp = Math.round(time.valueOf() / 1000);
const s = options.browsertime.iterations > 1 ? 's' : '';
const postData = `{"what": "${
options.browsertime.iterations
} run${s}", "tags": ${tagsArray}, "data": "${message}", "when": ${timestamp}}`;
const postOptions = {
hostname: options.grafana.host,
port: options.grafana.port,
path: '/api/annotations/graphite',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(postData)
}
};
// If Grafana is behind auth, use it!
if (options.grafana.auth) {
postOptions.headers.Authorization = 'Bearer ' + options.grafana.auth;
}
log.verbose('Send annotation to Grafana: %j', postData);
return new Promise((resolve, reject) => {
// not perfect but maybe work for us
const lib = options.grafana.port === 443 ? https : http;
const req = lib.request(postOptions, res => {
if (res.statusCode !== 200) {
const e = new Error(
`Got ${res.statusCode} from Grafana when sending annotation`
);
log.warn(e.message);
reject(e);
} else {
res.setEncoding('utf8');
log.debug('Sent annotation to Grafana');
resolve();
}
});
req.on('error', err => {
log.error('Got error from Grafana when sending annotation', err);
reject(err);
});
req.write(postData);
req.end();
});
}
};

View File

@ -41,9 +41,11 @@ module.exports = {
this.timestamp = context.timestamp;
this.resultUrls = context.resultUrls;
this.annotationType = 'webpagetest.pageSummary';
this.make = context.messageMaker('graphite').make;
this.sendAnnotation = true;
},
processMessage(message) {
processMessage(message, queue) {
// First catch if we are running Browsertime and/or WebPageTest
if (message.type === 'browsertime.setup') {
this.annotationType = 'browsertime.pageSummary';
@ -52,6 +54,11 @@ module.exports = {
this.useScreenshots = message.data.screenshot;
this.screenshotType = message.data.screenshotType;
}
} else if (message.type === 'sitespeedio.setup') {
// Let other plugins know that the Graphite plugin is alive
queue.postMessage(this.make('graphite.setup'));
} else if (message.type === 'grafana.setup') {
this.sendAnnotation = false;
}
const filterRegistry = this.filterRegistry;
@ -89,30 +96,21 @@ module.exports = {
// when we configured a base url
if (
message.type === this.annotationType &&
this.resultUrls.hasBaseUrl()
this.resultUrls.hasBaseUrl() &&
this.sendAnnotation
) {
const resultPageUrl = this.resultUrls.absoluteSummaryPagePath(
const absolutePagePath = this.resultUrls.absoluteSummaryPagePath(
message.url
);
let screenshotPath;
if (this.useScreenshots) {
screenshotPath =
resultPageUrl + 'data/screenshots/1.' + this.screenshotType;
}
const harPath =
resultPageUrl +
'data/browsertime.har' +
(this.options.gzipHAR ? '.gz' : '');
return sendAnnotations.send(
this.options,
message.group,
message.url,
resultPageUrl + 'index.html',
screenshotPath,
harPath,
this.timestamp
message.group,
absolutePagePath,
this.useScreenshots,
this.screenshotType,
this.timestamp,
this.options
);
}
});

View File

@ -4,10 +4,19 @@ const https = require('https');
const log = require('intel').getLogger('sitespeedio.plugin.graphite');
const Promise = require('bluebird');
const graphiteUtil = require('../../support/tsdbUtil');
const annotationsHelper = require('../../support/annotationsHelper');
const util = require('../../support/util');
module.exports = {
send(options, group, url, resultPageUrl, screenshotPath, harPath, time) {
send(
url,
group,
absolutePagePath,
screenShotsEnabledInBrowsertime,
screenshotType,
time,
options
) {
// The tags make it possible for the dashboard to use the
// templates to choose which annotations that will be showed.
// That's why we need to send tags that matches the template
@ -31,32 +40,18 @@ module.exports = {
if (extraTags.length > 0) {
tags.push(...extraTags);
}
let tagsString = `"`;
let tagsArray = '[';
for (let myTag of tags) {
tagsString += myTag + ',';
tagsArray += '"' + myTag + '",';
}
tagsString = tagsString.substring(0, tagsString.length - 1);
tagsArray = tagsArray.substring(0, tagsArray.length - 1);
tagsString += '"';
tagsArray += ']';
const theTags = options.graphite.arrayTags
? annotationsHelper.getTagsAsArray(tags)
: annotationsHelper.getTagsAsString(tags);
const s = options.browsertime.iterations > 1 ? 's' : '';
const screenshotSize = options.mobile ? 'height=200px' : 'width=100%';
let message =
screenshotPath &&
options.graphite &&
options.graphite.annotationScreenshot
? `<a href='${resultPageUrl}' target='_blank'><img src='${screenshotPath}' ${screenshotSize}></a><p><a href='${resultPageUrl}'>Result</a> - <a href='${harPath}'>Download HAR</a></p>`
: `<a href='${resultPageUrl}' target='_blank'>Result ${
options.browsertime.iterations
} run${s}</a>`;
if (options.graphite.annotationMessage) {
message += options.graphite.annotationMessage;
}
const message = annotationsHelper.getAnnotationMessage(
absolutePagePath,
screenShotsEnabledInBrowsertime,
screenshotType,
options
);
const timestamp = Math.round(time.valueOf() / 1000);
const theTags = options.graphite.arrayTags ? tagsArray : tagsString;
const postData = `{"what": "${
options.browsertime.iterations
} run${s}", "tags": ${theTags}, "data": "${message}", "when": ${timestamp}}`;

View File

@ -32,8 +32,10 @@ module.exports = {
this.resultUrls = context.resultUrls;
this.dataGenerator = new DataGenerator(opts.includeQueryParams, options);
this.annotationType = 'webpagetest.pageSummary';
this.make = context.messageMaker('influxdb').make;
this.sendAnnotation = true;
},
processMessage(message) {
processMessage(message, queue) {
const filterRegistry = this.filterRegistry;
// First catch if we are running Browsertime and/or WebPageTest
@ -44,6 +46,11 @@ module.exports = {
this.useScreenshots = message.data.screenshot;
this.screenshotType = message.data.screenshotType;
}
} else if (message.type === 'sitespeedio.setup') {
// Let other plugins know that the Graphite plugin is alive
queue.postMessage(this.make('graphite.setup'));
} else if (message.type === 'grafana.setup') {
this.sendAnnotation = false;
}
if (
@ -81,28 +88,21 @@ module.exports = {
// when we configured a base url
if (
message.type === this.annotationType &&
this.resultUrls.hasBaseUrl()
this.resultUrls.hasBaseUrl() &&
this.sendAnnotation
) {
const resultPageUrl = this.resultUrls.absoluteSummaryPagePath(
const absolutePagePath = this.resultUrls.absoluteSummaryPagePath(
message.url
);
let screenshotPath;
if (this.useScreenshots) {
screenshotPath =
resultPageUrl + 'data/screenshots/1.' + this.screenshotType;
}
const harPath =
resultPageUrl +
'data/browsertime.har' +
(this.options.gzipHAR ? '.gz' : '');
return sendAnnotations.send(
this.options,
message.group,
message.url,
resultPageUrl + 'index.html',
screenshotPath,
harPath
message.group,
absolutePagePath,
this.useScreenshots,
this.screenshotType,
this.timestamp,
this.options
);
}
});

View File

@ -5,10 +5,19 @@ const log = require('intel').getLogger('sitespeedio.plugin.influxdb');
const Promise = require('bluebird');
const querystring = require('querystring');
const tsdbUtil = require('../../support/tsdbUtil');
const annotationsHelper = require('../../support/annotationsHelper');
const moment = require('moment');
module.exports = {
send(options, group, url, resultPageUrl, screenshotPath, harPath) {
send(
url,
group,
absolutePagePath,
screenShotsEnabledInBrowsertime,
screenshotType,
time,
options
) {
// The tags make it possible for the dashboard to use the
// templates to choose which annotations that will be showed.
// That's why we need to send tags that matches the template
@ -18,15 +27,14 @@ module.exports = {
const urlAndGroup = tsdbUtil
.getURLAndGroup(options, group, url, options.influxdb.includeQueryParams)
.split('.');
let tags = `${connectivity},${browser},${urlAndGroup.join(',')}`;
let message =
screenshotPath &&
options.influxdb &&
options.influxdb.annotationScreenshot
? `<a href='${resultPageUrl}' target='_blank'><img src='${screenshotPath}' width=100%></a><a href='${resultPageUrl}'>Result</a> - <a href='${harPath}'>Download HAR</a>`
: `<a href='${resultPageUrl}' target='_blank'>Result ${
options.browsertime.iterations
} run(s)</a>`;
let tags = [connectivity, browser, urlAndGroup[0], urlAndGroup[1]];
const message = annotationsHelper.getAnnotationMessage(
absolutePagePath,
screenShotsEnabledInBrowsertime,
screenshotType,
options
);
const timestamp = Math.round(moment() / 1000);
// if we have a category, let us send that category too
if (options.influxdb.tags) {
@ -35,7 +43,8 @@ module.exports = {
tags += `,${keyAndValue[1]}`;
}
}
const postData = `events title="Sitespeed.io",text="${message}",tags="${tags}" ${timestamp}`;
const influxDBTags = annotationsHelper.getTagsAsString(tags);
const postData = `events title="Sitespeed.io",text="${message}",tags=${influxDBTags} ${timestamp}`;
const postOptions = {
hostname: options.influxdb.host,
port: options.influxdb.port,

View File

@ -0,0 +1,55 @@
'use strict';
module.exports = {
getAnnotationMessage(
absolutePagePath,
screenShotsEnabledInBrowsertime,
screenshotType,
options
) {
const screenshotSize = options.mobile ? 'height=200px' : 'width=100%';
const resultPageUrl = absolutePagePath + 'index.html';
let screenshotPath;
if (screenShotsEnabledInBrowsertime) {
screenshotPath =
absolutePagePath + 'data/screenshots/1.' + screenshotType;
}
const screenshotsEnabledForDatasource =
options.graphite.annotationScreenshot ||
options.influxdb.annotationScreenshot;
const harPath =
absolutePagePath +
'data/browsertime.har' +
(options.gzipHAR ? '.gz' : '');
const extraMessage = options.graphite.annotationMessage
? options.graphite.annotationMessage
: options.influxdb.annotationMessage
? options.influxdb.annotationMessage
: undefined;
const s = options.browsertime.iterations > 1 ? 's' : '';
let message =
screenShotsEnabledInBrowsertime && screenshotsEnabledForDatasource
? `<a href='${resultPageUrl}' target='_blank'><img src='${screenshotPath}' ${screenshotSize}></a><p><a href='${resultPageUrl}'>Result</a> - <a href='${harPath}'>Download HAR</a></p>`
: `<a href='${resultPageUrl}' target='_blank'>Result ${
options.browsertime.iterations
} run${s}</a>`;
if (extraMessage) {
message += extraMessage;
}
return message;
},
getTagsAsString(tags) {
return '"' + tags.join(',') + '"';
},
getTagsAsArray(tags) {
const stringTags = [];
for (let tag of tags) {
stringTags.push('"' + tag + '"');
}
return '[' + stringTags.join(',') + ']';
}
};