'use strict'; exports = module.exports = { getSystem, getContainerStats }; const apps = require('./apps.js'), assert = require('assert'), BoxError = require('./boxerror.js'), docker = require('./docker.js'), os = require('os'), safe = require('safetydance'), services = require('./services.js'), superagent = require('superagent'); // for testing locally: curl 'http://${graphite-ip}:8000/graphite-web/render?format=json&from=-1min&target=absolute(collectd.localhost.du-docker.capacity-usage)' // the datapoint is (value, timestamp) https://graphite.readthedocs.io/en/latest/ async function getGraphiteUrl() { const [error, result] = await safe(docker.inspect('graphite')); if (error && error.reason === BoxError.NOT_FOUND) return { status: exports.SERVICE_STATUS_STOPPED }; if (error) throw error; const ip = safe.query(result, 'NetworkSettings.Networks.cloudron.IPAddress', null); if (!ip) throw new BoxError(BoxError.INACTIVE, 'Error getting IP of graphite service'); return `http://${ip}:8000/graphite-web/render`; } async function getContainerStats(name, fromMinutes, noNullPoints) { assert.strictEqual(typeof name, 'string'); assert.strictEqual(typeof fromMinutes, 'number'); assert.strictEqual(typeof noNullPoints, 'boolean'); const timeBucketSize = fromMinutes > (24 * 60) ? (6*60) : 5; const graphiteUrl = await getGraphiteUrl(); // https://collectd.org/wiki/index.php/Data_source . the gauge is point in time value. counter is the change of value const targets = [ `summarize(collectd.localhost.docker-stats-${name}.gauge-cpu-perc, "${timeBucketSize}min", "avg")`, `summarize(collectd.localhost.docker-stats-${name}.gauge-mem-used, "${timeBucketSize}min", "avg")`, // `summarize(collectd.localhost.docker-stats-${name}.gauge-mem-max, "${timeBucketSize}min", "avg")`, `summarize(collectd.localhost.docker-stats-${name}.counter-blockio-read, "${timeBucketSize}min", "sum")`, `summarize(collectd.localhost.docker-stats-${name}.counter-blockio-write, "${timeBucketSize}min", "sum")`, `summarize(collectd.localhost.docker-stats-${name}.counter-network-read, "${timeBucketSize}min", "sum")`, `summarize(collectd.localhost.docker-stats-${name}.counter-network-write, "${timeBucketSize}min", "sum")`, `summarize(collectd.localhost.docker-stats-${name}.gauge-blockio-read, "${fromMinutes}min", "max")`, `summarize(collectd.localhost.docker-stats-${name}.gauge-blockio-write, "${fromMinutes}min", "max")`, `summarize(collectd.localhost.docker-stats-${name}.gauge-network-read, "${fromMinutes}min", "max")`, `summarize(collectd.localhost.docker-stats-${name}.gauge-network-write, "${fromMinutes}min", "max")`, ]; const results = []; for (const target of targets) { const query = { target: target, format: 'json', from: `-${fromMinutes}min`, until: 'now', noNullPoints: !!noNullPoints }; const [error, response] = await safe(superagent.get(graphiteUrl).query(query).timeout(30 * 1000).ok(() => true)); if (error) throw new BoxError(BoxError.NETWORK_ERROR, error.message); if (response.status !== 200) throw new BoxError(BoxError.EXTERNAL_ERROR, `Unknown error with ${target}: ${response.status} ${response.text}`); results.push(response.body[0] && response.body[0].datapoints ? response.body[0].datapoints : []); } // results are datapoints[[value, ts], [value, ts], ...]; return { cpu: results[0], memory: results[1], blockRead: results[2], blockWrite: results[3], networkRead: results[4], networkWrite: results[5], blockReadTotal: results[6][0] && results[6][0][0] ? results[6][0][0] : 0, blockWriteTotal: results[7][0] && results[7][0][0] ? results[7][0][0] : 0, networkReadTotal: results[8][0] && results[8][0][0] ? results[8][0][0] : 0, networkWriteTotal: results[9][0] && results[9][0][0] ? results[9][0][0] : 0, cpuCount: os.cpus().length }; } async function getSystem(fromMinutes, noNullPoints) { assert.strictEqual(typeof fromMinutes, 'number'); assert.strictEqual(typeof noNullPoints, 'boolean'); const timeBucketSize = fromMinutes > (24 * 60) ? (6*60) : 5; const graphiteUrl = await getGraphiteUrl(); const cpuQuery = `summarize(sum(collectd.localhost.aggregation-cpu-sum.cpu-system, collectd.localhost.aggregation-cpu-sum.cpu-user), "${timeBucketSize}min", "avg")`; const memoryQuery = `summarize(collectd.localhost.memory.memory-used, "${timeBucketSize}min", "avg")`; const query = { target: [ cpuQuery, memoryQuery ], format: 'json', from: `-${fromMinutes}min`, until: 'now' }; const [memCpuError, memCpuResponse] = await safe(superagent.get(graphiteUrl).query(query).timeout(30 * 1000).ok(() => true)); if (memCpuError) throw new BoxError(BoxError.NETWORK_ERROR, memCpuError.message); if (memCpuResponse.status !== 200) throw new BoxError(BoxError.EXTERNAL_ERROR, `Unknown error: ${memCpuResponse.status} ${memCpuResponse.text}`); const appResponses = {}; for (const app of await apps.list()) { appResponses[app.id] = await getContainerStats(app.id, fromMinutes, noNullPoints); } const serviceResponses = {}; for (const serviceId of await services.listServices()) { serviceResponses[serviceId] = await getContainerStats(serviceId, fromMinutes, noNullPoints); } return { cpu: memCpuResponse.body[0] && memCpuResponse.body[0].datapoints ? memCpuResponse.body[0].datapoints : [], memory: memCpuResponse.body[1] && memCpuResponse.body[1].datapoints ? memCpuResponse.body[1].datapoints : [], apps: appResponses, services: serviceResponses, cpuCount: os.cpus().length }; }