From 354fd88028fb99931cd0410f6cb0957de055d35f Mon Sep 17 00:00:00 2001 From: Nicolas Raynaud Date: Wed, 31 Jan 2018 01:41:28 -0700 Subject: [PATCH] feat(xo-server-perf-plugin): initial release (#33) --- packages/xo-server-perf-alert/.babelrc.js | 38 + packages/xo-server-perf-alert/.npmignore | 24 + packages/xo-server-perf-alert/README.md | 31 + packages/xo-server-perf-alert/package.json | 45 ++ packages/xo-server-perf-alert/src/index.js | 839 +++++++++++++++++++++ yarn.lock | 14 +- 6 files changed, 989 insertions(+), 2 deletions(-) create mode 100644 packages/xo-server-perf-alert/.babelrc.js create mode 100644 packages/xo-server-perf-alert/.npmignore create mode 100644 packages/xo-server-perf-alert/README.md create mode 100644 packages/xo-server-perf-alert/package.json create mode 100644 packages/xo-server-perf-alert/src/index.js diff --git a/packages/xo-server-perf-alert/.babelrc.js b/packages/xo-server-perf-alert/.babelrc.js new file mode 100644 index 000000000..5fdaef4ec --- /dev/null +++ b/packages/xo-server-perf-alert/.babelrc.js @@ -0,0 +1,38 @@ +'use strict' + +const NODE_ENV = process.env.NODE_ENV || 'development' +const __PROD__ = NODE_ENV === 'production' +const __TEST__ = NODE_ENV === 'test' + +const pkg = require('./package') + +let nodeCompat = (pkg.engines || {}).node +if (nodeCompat === undefined) { + nodeCompat = '6' +} else { + const trimChars = '^=>~' + while (trimChars.includes(nodeCompat[0])) { + nodeCompat = nodeCompat.slice(1) + } +} + +module.exports = { + comments: !__PROD__, + ignore: __TEST__ ? undefined : [/\.spec\.js$/], + plugins: ['lodash'], + presets: [ + [ + '@babel/env', + { + debug: !__TEST__, + loose: true, + shippedProposals: true, + targets: __PROD__ + ? { node: nodeCompat } + : { node: 'current' }, + useBuiltIns: '@babel/polyfill' in (pkg.dependencies || {}) && 'usage', + }, + ], + '@babel/flow', + ], +} diff --git a/packages/xo-server-perf-alert/.npmignore b/packages/xo-server-perf-alert/.npmignore new file mode 100644 index 000000000..e058b6bc1 --- /dev/null +++ b/packages/xo-server-perf-alert/.npmignore @@ -0,0 +1,24 @@ +/benchmark/ +/benchmarks/ +*.bench.js +*.bench.js.map + +/examples/ +example.js +example.js.map +*.example.js +*.example.js.map + +/fixture/ +/fixtures/ +*.fixture.js +*.fixture.js.map +*.fixtures.js +*.fixtures.js.map + +/test/ +/tests/ +*.spec.js +*.spec.js.map + +__snapshots__/ diff --git a/packages/xo-server-perf-alert/README.md b/packages/xo-server-perf-alert/README.md new file mode 100644 index 000000000..926267aea --- /dev/null +++ b/packages/xo-server-perf-alert/README.md @@ -0,0 +1,31 @@ +# xo-server-perf-alert [![Build Status](https://travis-ci.org/vatesfr/xen-orchestra.png?branch=master)](https://travis-ci.org/vatesfr/xen-orchestra) + +> Alert plugin for Xen Orchestra + +## Install + +Installation of the [npm package](https://npmjs.org/package/xo-server-perf-alert): + +``` +> npm install --global xo-server-perf-alert +``` + +## Usage + +Like all xo-server plugins, it can be configured directly via +the web interface, see [the plugin documentation](https://xen-orchestra.com/docs/plugins.html). + +## Contributions + +Contributions are *very* welcomed, either on the documentation or on +the code. + +You may: + +- report any [issue](https://github.com/vatesfr/xo-web/issues) + you've encountered; +- fork and create a pull request. + +## License + +AGPL3 © [Vates SAS](http://vates.fr) diff --git a/packages/xo-server-perf-alert/package.json b/packages/xo-server-perf-alert/package.json new file mode 100644 index 000000000..dfef17cb1 --- /dev/null +++ b/packages/xo-server-perf-alert/package.json @@ -0,0 +1,45 @@ +{ + "name": "xo-server-perf-alert", + "version": "0.0.0", + "license": "AGPL-3.0", + "description": "", + "keywords": [], + "homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/packages/xo-server-perf-alert", + "bugs": "https://github.com/vatesfr/xo-web/issues", + "repository": { + "type": "git", + "url": "https://github.com/vatesfr/xen-orchestra.git" + }, + "preferGlobal": true, + "main": "dist/", + "bin": {}, + "files": [ + "dist/" + ], + "engines": { + "node": ">=6" + }, + "dependencies": { + "cron": "^1.3.0", + "d3-time-format": "^2.1.1", + "json5": "^0.5.1", + "lodash": "^4.17.4" + }, + "devDependencies": { + "@babel/cli": "7.0.0-beta.38", + "@babel/core": "7.0.0-beta.38", + "@babel/preset-env": "7.0.0-beta.38", + "@babel/preset-flow": "^7.0.0-beta.38", + "babel-plugin-lodash": "^3.3.2", + "cross-env": "^5.1.3", + "rimraf": "^2.6.2" + }, + "scripts": { + "build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/", + "clean": "rimraf dist/", + "dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/", + "prebuild": "yarn run clean", + "predev": "yarn run clean", + "prepublishOnly": "yarn run build" + } +} diff --git a/packages/xo-server-perf-alert/src/index.js b/packages/xo-server-perf-alert/src/index.js new file mode 100644 index 000000000..fe07a5997 --- /dev/null +++ b/packages/xo-server-perf-alert/src/index.js @@ -0,0 +1,839 @@ +import JSON5 from 'json5' +import { CronJob } from 'cron' +import { forOwn, map, mean } from 'lodash' +import { utcParse } from 'd3-time-format' + +const VM_FUNCTIONS = { + cpuUsage: { + description: + 'Raises an alarm when the average usage of any CPU is higher than the threshold', + unit: '%', + comparator: '>', + createParser: (legend, threshold) => { + const regex = /cpu[0-9]+/ + const filteredLegends = legend.filter(l => l.name.match(regex)) + const accumulator = Object.assign( + ...filteredLegends.map(l => ({ [l.name]: [] })) + ) + const getDisplayableValue = () => { + const means = Object.keys(accumulator).map(l => mean(accumulator[l])) + return Math.max(...means) * 100 + } + return { + parseRow: data => { + filteredLegends.forEach(l => { + accumulator[l.name].push(data.values[l.index]) + }) + }, + getDisplayableValue, + shouldAlarm: () => getDisplayableValue() > threshold, + } + }, + }, + memoryUsage: { + description: + 'Raises an alarm when the used memory % is higher than the threshold', + unit: '% used', + comparator: '>', + createParser: (legend, threshold) => { + const memoryBytesLegend = legend.find(l => l.name === 'memory') + const memoryKBytesFreeLegend = legend.find( + l => l.name === 'memory_internal_free' + ) + const usedMemoryRatio = [] + const getDisplayableValue = () => mean(usedMemoryRatio) * 100 + return { + parseRow: data => { + const memory = data.values[memoryBytesLegend.index] + usedMemoryRatio.push( + (memory - 1024 * data.values[memoryKBytesFreeLegend.index]) / memory + ) + }, + getDisplayableValue, + shouldAlarm: () => { + return getDisplayableValue() > threshold + }, + } + }, + }, +} + +const HOST_FUNCTIONS = { + cpuUsage: { + description: + 'Raises an alarm when the average usage of any CPU is higher than the threshold', + unit: '%', + comparator: '>', + createParser: (legend, threshold) => { + const regex = /^cpu[0-9]+$/ + const filteredLegends = legend.filter(l => l.name.match(regex)) + const accumulator = Object.assign( + ...filteredLegends.map(l => ({ [l.name]: [] })) + ) + const getDisplayableValue = () => { + const means = Object.keys(accumulator).map(l => mean(accumulator[l])) + return Math.max(...means) * 100 + } + return { + parseRow: data => { + filteredLegends.forEach(l => { + accumulator[l.name].push(data.values[l.index]) + }) + }, + getDisplayableValue, + shouldAlarm: () => getDisplayableValue() > threshold, + } + }, + }, + memoryUsage: { + description: + 'Raises an alarm when the used memory % is higher than the threshold', + unit: '% used', + comparator: '>', + createParser: (legend, threshold) => { + const memoryKBytesLegend = legend.find( + l => l.name === 'memory_total_kib' + ) + const memoryKBytesFreeLegend = legend.find( + l => l.name === 'memory_free_kib' + ) + const usedMemoryRatio = [] + const getDisplayableValue = () => mean(usedMemoryRatio) * 100 + return { + parseRow: data => { + const memory = data.values[memoryKBytesLegend.index] + usedMemoryRatio.push( + (memory - data.values[memoryKBytesFreeLegend.index]) / memory + ) + }, + getDisplayableValue, + shouldAlarm: () => { + return getDisplayableValue() > threshold + }, + } + }, + }, +} + +const TYPE_FUNCTION_MAP = { + vm: VM_FUNCTIONS, + host: HOST_FUNCTIONS, +} + +// list of currently ringing alarms, to avoid double notification +const currentAlarms = {} + +export const configurationSchema = { + type: 'object', + properties: { + baseUrl: { + type: 'string', + title: 'Xen Orchestra URL', + description: + 'URL used in alert messages to quickly get to the VMs (ex: https://xoa.company.tld/ )', + }, + hostMonitors: { + type: 'array', + title: 'Host Monitors', + description: + 'Alarms checking hosts on all pools. The selected performance counter is sampled regularly and averaged. ' + + 'The Average is compared to the threshold and an alarm is raised upon crossing', + items: { + type: 'object', + properties: { + uuids: { + title: 'Hosts', + type: 'array', + items: { + type: 'string', + $type: 'Host', + }, + }, + variableName: { + title: 'Alarm Type', + description: Object.keys(HOST_FUNCTIONS) + .map( + k => + ` * ${k} (${HOST_FUNCTIONS[k].unit}): ${ + HOST_FUNCTIONS[k].description + }` + ) + .join('\n'), + type: 'string', + default: Object.keys(HOST_FUNCTIONS)[0], + enum: Object.keys(HOST_FUNCTIONS), + }, + alarmTriggerLevel: { + title: 'Threshold', + description: + 'The direction of the crossing is given by the Alarm type', + type: 'number', + default: 40, + }, + alarmTriggerPeriod: { + title: 'Average Length (s)', + description: + 'The points are averaged this number of seconds then the average is compared with the threshold', + type: 'number', + default: 60, + enum: [60, 600], + }, + }, + }, + }, + vmMonitors: { + type: 'array', + title: 'VM Monitors', + description: + 'Alarms checking all VMs on all pools. The selected performance counter is sampled regularly and averaged. ' + + 'The Average is compared to the threshold and an alarm is raised upon crossing', + items: { + type: 'object', + properties: { + uuids: { + title: 'Virtual Machines', + type: 'array', + items: { + type: 'string', + $type: 'VM', + }, + }, + variableName: { + title: 'Alarm Type', + description: Object.keys(VM_FUNCTIONS) + .map( + k => + ` * ${k} (${VM_FUNCTIONS[k].unit}):${ + VM_FUNCTIONS[k].description + }` + ) + .join('\n'), + type: 'string', + default: Object.keys(VM_FUNCTIONS)[0], + enum: Object.keys(VM_FUNCTIONS), + }, + alarmTriggerLevel: { + title: 'Threshold', + description: + 'The direction of the crossing is given by the Alarm type', + type: 'number', + default: 40, + }, + alarmTriggerPeriod: { + title: 'Average Length (s)', + description: + 'The points are averaged this number of seconds then the average is compared with the threshold', + type: 'number', + default: 60, + enum: [60, 600], + }, + }, + required: ['uuids'], + }, + }, + toEmails: { + type: 'array', + title: 'Email addresses', + description: 'Email addresses of the alert recipients', + + items: { + type: 'string', + }, + minItems: 1, + }, + }, +} + +const clearCurrentAlarms = () => + forOwn(currentAlarms, (v, k) => { + delete currentAlarms[k] + }) + +const raiseOrLowerAlarm = ( + alarmId, + shouldRaise, + raiseCallback, + lowerCallback +) => { + const current = currentAlarms[alarmId] + if (shouldRaise) { + if (!current) { + currentAlarms[alarmId] = true + raiseCallback(alarmId) + } + } else { + if (current) { + try { + lowerCallback(alarmId) + } finally { + delete currentAlarms[alarmId] + } + } + } +} + +async function getServerTimestamp (xapi, host) { + const serverLocalTime = await xapi.call('host.get_servertime', host.$ref) + return Math.floor( + utcParse('%Y%m%dT%H:%M:%SZ')(serverLocalTime).getTime() / 1000 + ) +} + +class PerfAlertXoPlugin { + constructor (xo) { + this._xo = xo + this._job = new CronJob({ + cronTime: '* * * * *', + start: false, + onTick: this._checkMonitors.bind(this), + }) + } + + async configure (configuration) { + this._configuration = configuration + clearCurrentAlarms() + } + + _generateUrl (type, object) { + const map = { + vm: () => `${this._configuration.baseUrl}#/vms/${object.uuid}/stats`, + host: () => `${this._configuration.baseUrl}#/hosts/${object.uuid}/stats`, + } + return map[type]() + } + + async test () { + const hostMonitorPart2 = await Promise.all( + map(this._getMonitors(), async m => { + const tableBody = (await m.snapshot()).map(entry => entry.tableItem) + return ` +## Monitor for ${m.title} + +${m.tableHeader} +${tableBody.join('')}` + }) + ) + + this._sendAlertEmail( + 'TEST', + ` +# Performance Alert Test +Your alarms and their current status: +${hostMonitorPart2.join('\n')}` + ) + } + + load () { + this._job.start() + } + + unload () { + this._job.stop() + } + + _parseDefinition (definition) { + const alarmId = `${definition.objectType}|${definition.variableName}|${ + definition.alarmTriggerLevel + }` + const typeFunction = + TYPE_FUNCTION_MAP[definition.objectType][definition.variableName] + const parseData = (result, uuid) => { + const parsedLegend = result.meta.legend.map((l, index) => { + const [operation, type, uuid, name] = l.split(':') + const parsedName = name.split('_') + const lastComponent = parsedName[parsedName.length - 1] + const relatedEntity = + parsedName.length > 1 && lastComponent.match(/^[0-9a-f]{8}$/) + ? lastComponent + : null + return { + operation, + type, + uuid, + name, + relatedEntity, + parsedName, + index, + } + }) + const legendTree = {} + const getNode = (element, name, defaultValue = {}) => { + const child = element[name] + if (child === undefined) { + element[name] = defaultValue + return defaultValue + } + return child + } + parsedLegend.forEach(l => { + const root = getNode(legendTree, l.uuid) + const relatedNode = getNode(root, l.relatedEntity) + relatedNode[l.name] = l + }) + const parser = typeFunction.createParser( + parsedLegend.filter(l => l.uuid === uuid), + definition.alarmTriggerLevel + ) + result.data.forEach(d => parser.parseRow(d)) + return parser + } + const observationPeriod = + definition.alarmTriggerPeriod !== undefined + ? definition.alarmTriggerPeriod + : 60 + const typeText = definition.objectType === 'host' ? 'Host' : 'VM' + return { + ...definition, + alarmId, + vmFunction: typeFunction, + title: `${typeText} ${definition.variableName} ${ + typeFunction.comparator + } ${definition.alarmTriggerLevel}${typeFunction.unit}`, + tableHeader: `${typeText} | Value | Alert\n--- | -----:| ---:`, + snapshot: async () => { + return Promise.all( + map(definition.uuids, async uuid => { + try { + const monitoredObject = this._xo.getXapi(uuid).getObject(uuid) + const objectLink = `[${ + monitoredObject.name_label + }](${this._generateUrl(definition.objectType, monitoredObject)})` + const rrd = await this.getRrd(monitoredObject, observationPeriod) + const couldFindRRD = rrd !== null + const result = { + object: monitoredObject, + couldFindRRD, + objectLink: objectLink, + listItem: ` * ${typeText} ${objectLink} ${ + definition.variableName + }: **Can't read performance counters**\n`, + tableItem: `${objectLink} | - | **Can't read performance counters**\n`, + } + if (!couldFindRRD) { + return result + } + const data = parseData(rrd, monitoredObject.uuid) + const textValue = + data.getDisplayableValue().toFixed(1) + typeFunction.unit + const shouldAlarm = data.shouldAlarm() + return { + ...result, + value: data.getDisplayableValue(), + shouldAlarm: shouldAlarm, + textValue: textValue, + listItem: ` * ${typeText} ${objectLink} ${ + definition.variableName + }: ${textValue}\n`, + tableItem: `${objectLink} | ${textValue} | ${ + shouldAlarm ? '**Alert Ongoing**' : 'no alert' + }\n`, + } + } catch (_) { + return { + uuid, + object: null, + couldFindRRD: false, + objectLink: `cannot find object ${uuid}`, + listItem: ` * ${typeText} ${uuid} ${ + definition.variableName + }: **Can't read performance counters**\n`, + tableItem: `object ${uuid} | - | **Can't read performance counters**\n`, + } + } + }) + ) + }, + } + } + + _getMonitors () { + return map(this._configuration.hostMonitors, def => + this._parseDefinition({ ...def, objectType: 'host' }) + ).concat( + map(this._configuration.vmMonitors, def => + this._parseDefinition({ ...def, objectType: 'vm' }) + ) + ) + } + + async _checkMonitors () { + const monitors = this._getMonitors() + for (const monitor of monitors) { + const snapshot = await monitor.snapshot() + for (const entry of snapshot) { + raiseOrLowerAlarm( + `${monitor.alarmId}|${entry.uuid}|RRD`, + !entry.couldFindRRD, + () => { + this._sendAlertEmail( + 'Secondary Issue', + ` +## There was an issue when trying to check ${monitor.title} +${entry.listItem}` + ) + }, + () => {} + ) + if (!entry.couldFindRRD) { + continue + } + const raiseAlarm = alarmId => { + // sample XenCenter message: + // value: 1.242087 config: + this._xo + .getXapi(entry.object.uuid) + .call( + 'message.create', + 'ALARM', + 3, + entry.object.$type, + entry.object.uuid, + `value: ${entry.value.toFixed( + 1 + )} config: ` + ) + this._sendAlertEmail( + '', + ` +## ALERT ${monitor.title} +${entry.listItem} +### Description + ${monitor.vmFunction.description}` + ) + } + const lowerAlarm = alarmId => { + console.log('lowering Alarm', alarmId) + this._sendAlertEmail( + 'END OF ALERT', + ` +## END OF ALERT ${monitor.title} +${entry.listItem} +### Description + ${monitor.vmFunction.description}` + ) + } + raiseOrLowerAlarm( + `${monitor.alarmId}|${entry.uuid}`, + entry.shouldAlarm, + raiseAlarm, + lowerAlarm + ) + } + } + } + + _sendAlertEmail (subjectSuffix, markdownBody) { + if ( + this._configuration.toEmails !== undefined && + this._xo.sendEmail !== undefined + ) { + this._xo.sendEmail({ + to: this._configuration.toEmails, + subject: `[Xen Orchestra] − Performance Alert ${subjectSuffix}`, + markdown: + markdownBody + + `\n\n\nSent from Xen Orchestra [perf-alert plugin](${ + this._configuration.baseUrl + }#/settings/plugins)\n`, + }) + } else { + throw new Error('The email alert system has a configuration issue.') + } + } + + async getRrd (xoObject, secondsAgo) { + const host = xoObject.$type === 'host' ? xoObject : xoObject.$resident_on + if (host == null) { + return null + } + // we get the xapi per host, because the alarms can check VMs in various pools + const xapi = this._xo.getXapi(host.uuid) + const serverTimestamp = await getServerTimestamp(xapi, host) + const payload = { + host, + query: { + cf: 'AVERAGE', + host: (xoObject.$type === 'host').toString(), + json: 'true', + start: serverTimestamp - secondsAgo, + }, + } + if (xoObject.$type === 'vm') { + payload['vm_uuid'] = xoObject.uuid + } + // JSON is not well formed, can't use the default node parser + return JSON5.parse( + await (await xapi.getResource('/rrd_updates', payload)).readAll() + ) + } +} + +exports.default = function ({ xo }) { + return new PerfAlertXoPlugin(xo) +} + +/* example legend fields: +host : memory_total_kib +host : memory_free_kib +host : cpu_avg +host : cpu3 +host : cpu2 +host : cpu1 +host : cpu0 +host : loadavg +host : CPU3-avg-freq +host : CPU2-avg-freq +host : CPU1-avg-freq +host : CPU0-avg-freq +host : cpu3-P15 +host : cpu3-P14 +host : cpu3-P13 +host : cpu3-P12 +host : cpu3-P11 +host : cpu3-P10 +host : cpu3-P9 +host : cpu3-P8 +host : cpu3-P7 +host : cpu3-P6 +host : cpu3-P5 +host : cpu3-P4 +host : cpu3-P3 +host : cpu3-P2 +host : cpu3-P1 +host : cpu3-P0 +host : cpu2-P15 +host : cpu2-P14 +host : cpu2-P13 +host : cpu2-P12 +host : cpu2-P11 +host : cpu2-P10 +host : cpu2-P9 +host : cpu2-P8 +host : cpu2-P7 +host : cpu2-P6 +host : cpu2-P5 +host : cpu2-P4 +host : cpu2-P3 +host : cpu2-P2 +host : cpu2-P1 +host : cpu2-P0 +host : cpu1-P15 +host : cpu1-P14 +host : cpu1-P13 +host : cpu1-P12 +host : cpu1-P11 +host : cpu1-P10 +host : cpu1-P9 +host : cpu1-P8 +host : cpu1-P7 +host : cpu1-P6 +host : cpu1-P5 +host : cpu1-P4 +host : cpu1-P3 +host : cpu1-P2 +host : cpu1-P1 +host : cpu1-P0 +host : cpu0-P15 +host : cpu0-P14 +host : cpu0-P13 +host : cpu0-P12 +host : cpu0-P11 +host : cpu0-P10 +host : cpu0-P9 +host : cpu0-P8 +host : cpu0-P7 +host : cpu0-P6 +host : cpu0-P5 +host : cpu0-P4 +host : cpu0-P3 +host : cpu0-P2 +host : cpu0-P1 +host : cpu0-P0 +host : cpu3-C6 +host : cpu3-C5 +host : cpu3-C4 +host : cpu3-C3 +host : cpu3-C2 +host : cpu3-C1 +host : cpu3-C0 +host : cpu2-C6 +host : cpu2-C5 +host : cpu2-C4 +host : cpu2-C3 +host : cpu2-C2 +host : cpu2-C1 +host : cpu2-C0 +host : cpu1-C6 +host : cpu1-C5 +host : cpu1-C4 +host : cpu1-C3 +host : cpu1-C2 +host : cpu1-C1 +host : cpu1-C0 +host : cpu0-C6 +host : cpu0-C5 +host : cpu0-C4 +host : cpu0-C3 +host : cpu0-C2 +host : cpu0-C1 +host : cpu0-C0 +host : Tapdisks_in_low_memory_mode +host : memory_reclaimed_max +host : memory_reclaimed +host : pif_aggr_rx +host : pif_aggr_tx +host : pif_eth2_rx +host : pif_eth2_tx +host : pif_eth0_rx +host : pif_eth0_tx +host : pif_eth1_rx +host : pif_eth1_tx +host : xapi_open_fds +host : pool_task_count +host : pool_session_count +host : xapi_memory_usage_kib +host : xapi_free_memory_kib +host : xapi_live_memory_kib +host : xapi_allocation_kib +host : inflight_2b7b1501 +host : iowait_2b7b1501 +host : iops_total_2b7b1501 +host : iops_write_2b7b1501 +host : iops_read_2b7b1501 +host : io_throughput_total_2b7b1501 +host : io_throughput_write_2b7b1501 +host : io_throughput_read_2b7b1501 +host : write_latency_2b7b1501 +host : read_latency_2b7b1501 +host : write_2b7b1501 +host : read_2b7b1501 +host : inflight_72cc0148 +host : iowait_72cc0148 +host : iops_total_72cc0148 +host : iops_write_72cc0148 +host : iops_read_72cc0148 +host : io_throughput_total_72cc0148 +host : io_throughput_write_72cc0148 +host : io_throughput_read_72cc0148 +host : write_latency_72cc0148 +host : read_latency_72cc0148 +host : write_72cc0148 +host : read_72cc0148 +host : inflight_9218facb +host : iowait_9218facb +host : iops_total_9218facb +host : iops_write_9218facb +host : iops_read_9218facb +host : io_throughput_total_9218facb +host : io_throughput_write_9218facb +host : io_throughput_read_9218facb +host : write_latency_9218facb +host : read_latency_9218facb +host : write_9218facb +host : read_9218facb +host : inflight_44f9108d +host : iowait_44f9108d +host : iops_total_44f9108d +host : iops_write_44f9108d +host : iops_read_44f9108d +host : io_throughput_total_44f9108d +host : io_throughput_write_44f9108d +host : io_throughput_read_44f9108d +host : write_latency_44f9108d +host : read_latency_44f9108d +host : write_44f9108d +host : read_44f9108d +host : inflight_438aa8dd +host : iowait_438aa8dd +host : iops_total_438aa8dd +host : iops_write_438aa8dd +host : iops_read_438aa8dd +host : io_throughput_total_438aa8dd +host : io_throughput_write_438aa8dd +host : io_throughput_read_438aa8dd +host : write_latency_438aa8dd +host : read_latency_438aa8dd +host : write_438aa8dd +host : read_438aa8dd +host : inflight_69a97fd4 +host : iowait_69a97fd4 +host : iops_total_69a97fd4 +host : iops_write_69a97fd4 +host : iops_read_69a97fd4 +host : io_throughput_total_69a97fd4 +host : io_throughput_write_69a97fd4 +host : io_throughput_read_69a97fd4 +host : write_latency_69a97fd4 +host : read_latency_69a97fd4 +host : write_69a97fd4 +host : read_69a97fd4 +host : inflight_85536572 +host : iowait_85536572 +host : iops_total_85536572 +host : iops_write_85536572 +host : iops_read_85536572 +host : io_throughput_total_85536572 +host : io_throughput_write_85536572 +host : io_throughput_read_85536572 +host : write_latency_85536572 +host : read_latency_85536572 +host : write_85536572 +host : read_85536572 +host : inflight_d4a3c32d +host : iowait_d4a3c32d +host : iops_total_d4a3c32d +host : iops_write_d4a3c32d +host : iops_read_d4a3c32d +host : io_throughput_total_d4a3c32d +host : io_throughput_write_d4a3c32d +host : io_throughput_read_d4a3c32d +host : write_latency_d4a3c32d +host : read_latency_d4a3c32d +host : write_d4a3c32d +host : read_d4a3c32d +host : inflight_c5bb6dc6 +host : iowait_c5bb6dc6 +host : iops_total_c5bb6dc6 +host : iops_write_c5bb6dc6 +host : iops_read_c5bb6dc6 +host : io_throughput_total_c5bb6dc6 +host : io_throughput_write_c5bb6dc6 +host : io_throughput_read_c5bb6dc6 +host : write_latency_c5bb6dc6 +host : read_latency_c5bb6dc6 +host : write_c5bb6dc6 +host : read_c5bb6dc6 +vm : cpu1 +vm : cpu0 +vm : memory +vm : vbd_xvda_inflight +vm : vbd_xvda_iowait +vm : vbd_xvda_iops_total +vm : vbd_xvda_iops_write +vm : vbd_xvda_iops_read +vm : vbd_xvda_io_throughput_total +vm : vbd_xvda_io_throughput_write +vm : vbd_xvda_io_throughput_read +vm : vbd_xvda_write_latency +vm : vbd_xvda_read_latency +vm : vbd_xvda_write +vm : vbd_xvda_read +vm : vbd_xvdb_inflight +vm : vbd_xvdb_iowait +vm : vbd_xvdb_iops_total +vm : vbd_xvdb_iops_write +vm : vbd_xvdb_iops_read +vm : vbd_xvdb_io_throughput_total +vm : vbd_xvdb_io_throughput_write +vm : vbd_xvdb_io_throughput_read +vm : vbd_xvdb_write_latency +vm : vbd_xvdb_read_latency +vm : vbd_xvdb_write +vm : vbd_xvdb_read +vm : vif_0_tx +vm : vif_0_rx +vm : memory_target +vm : memory_internal_free + */ diff --git a/yarn.lock b/yarn.lock index 4976c2f07..e0675cede 100644 --- a/yarn.lock +++ b/yarn.lock @@ -464,7 +464,7 @@ invariant "^2.2.2" semver "^5.3.0" -"@babel/preset-flow@7.0.0-beta.38": +"@babel/preset-flow@7.0.0-beta.38", "@babel/preset-flow@^7.0.0-beta.38": version "7.0.0-beta.38" resolved "https://registry.yarnpkg.com/@babel/preset-flow/-/preset-flow-7.0.0-beta.38.tgz#dda9cb14833cf353cc5e9db7a7a13fb7e5f84fba" dependencies: @@ -1908,7 +1908,7 @@ cosmiconfig@^4.0.0: parse-json "^4.0.0" require-from-string "^2.0.1" -cron@^1.1.0, cron@^1.2.1: +cron@^1.1.0, cron@^1.2.1, cron@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/cron/-/cron-1.3.0.tgz#7e459968eaf94e1a445be796ce402166c234659d" dependencies: @@ -1965,6 +1965,16 @@ cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0": dependencies: cssom "0.3.x" +d3-time-format@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-2.1.1.tgz#85b7cdfbc9ffca187f14d3c456ffda268081bb31" + dependencies: + d3-time "1" + +d3-time@1: + version "1.0.8" + resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-1.0.8.tgz#dbd2d6007bf416fe67a76d17947b784bffea1e84" + dashdash@^1.12.0, dashdash@^1.14.0: version "1.14.1" resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"