diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index 58374dbe8..2763b5b74 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -10,6 +10,7 @@ - [VM] Move boot order setting from Disk tab to Advanced tab [#1523](https://github.com/vatesfr/xen-orchestra/issues/1523#issuecomment-563141573) (PR [#4975](https://github.com/vatesfr/xen-orchestra/pull/4975)) - [XOA/licenses] Display proxy licenses (PR [#4944](https://github.com/vatesfr/xen-orchestra/pull/4944)) - [Network selector] Display pool's name [#4885](https://github.com/vatesfr/xen-orchestra/issues/4885) (PR [#4990](https://github.com/vatesfr/xen-orchestra/pull/4990)) +- [Usage report] Include CSV raw data files to the sent email [#4970](https://github.com/vatesfr/xen-orchestra/issues/4970) (PR [#4979](https://github.com/vatesfr/xen-orchestra/pull/4979)) ### Bug fixes @@ -35,6 +36,7 @@ > > In case of conflict, the highest (lowest in previous list) `$version` wins. +- xo-server-usage-report minor - @xen-orchestra/fs patch - xo-server patch - xo-web minor diff --git a/packages/xo-server-usage-report/package.json b/packages/xo-server-usage-report/package.json index c8b24dc61..d4c544d81 100644 --- a/packages/xo-server-usage-report/package.json +++ b/packages/xo-server-usage-report/package.json @@ -38,6 +38,7 @@ "@xen-orchestra/async-map": "^0.0.0", "@xen-orchestra/cron": "^1.0.6", "@xen-orchestra/log": "^0.2.0", + "csv-stringify": "^5.5.0", "handlebars": "^4.0.6", "html-minifier": "^4.0.0", "human-format": "^0.10.0", diff --git a/packages/xo-server-usage-report/src/index.js b/packages/xo-server-usage-report/src/index.js index 868497285..59387a672 100644 --- a/packages/xo-server-usage-report/src/index.js +++ b/packages/xo-server-usage-report/src/index.js @@ -2,6 +2,7 @@ import asyncMap from '@xen-orchestra/async-map' import createLogger from '@xen-orchestra/log' import Handlebars from 'handlebars' import humanFormat from 'human-format' +import stringify from 'csv-stringify' import { createSchedule } from '@xen-orchestra/cron' import { minify } from 'html-minifier' import { @@ -703,6 +704,68 @@ const CRON_BY_PERIODICITY = { daily: '0 6 * * *', } +// let field empty in case of "NaN" and "NONE" +const CSV_CAST = { + number: value => (Number.isNaN(value) ? undefined : String(value)), + string: value => (value === 'NONE' ? undefined : value), +} + +const CSV_COLUMNS = { + cpu: { key: 'cpu', header: 'CPU (%)' }, + cpuEvolution: { key: 'evolution.cpu', header: 'CPU evolution (%)' }, + diskRead: { key: 'diskRead', header: 'Disk read (MiB)' }, + diskReadEvolution: { + key: 'evolution.diskRead', + header: 'Disk read evolution (%)', + }, + diskWrite: { key: 'diskWrite', header: 'Disk write (MiB)' }, + diskWriteEvolution: { + key: 'evolution.diskWrite', + header: 'Disk write evolution (%)', + }, + iopsRead: { key: 'iopsRead', header: 'IOPS read' }, + iopsReadEvolution: { + key: 'evolution.iopsRead', + header: 'IOPS read evolution (%)', + }, + iopsTotal: { key: 'iopsTotal', header: 'IOPS total' }, + iopsTotalEvolution: { + key: 'evolution.iopsTotal', + header: 'IOPS total evolution (%)', + }, + iopsWrite: { key: 'iopsWrite', header: 'IOPS write' }, + iopsWriteEvolution: { + key: 'evolution.iopsWrite', + header: 'IOPS write evolution (%)', + }, + load: { key: 'load', header: 'Load average' }, + loadEvolution: { + key: 'evolution.load', + header: 'Load average evolution (%)', + }, + name: { key: 'name', header: 'Name' }, + netReception: { key: 'netReception', header: 'Network RX (KiB)' }, + netReceptionEvolution: { + key: 'evolution.netReception', + header: 'Network RX evolution (%)', + }, + netTransmission: { key: 'netTransmission', header: 'Network TX (KiB)' }, + netTransmissionEvolution: { + key: 'evolution.netTransmission', + header: 'Network TX evolution (%)', + }, + ram: { key: 'ram', header: 'RAM (GiB)' }, + ramEvolution: { key: 'evolution.ram', header: 'RAM evolution (%)' }, + spaceFree: { key: 'freeSpace', header: 'Free space (GiB)' }, + spaceTotal: { key: 'total', header: 'Total space (GiB)' }, + spaceTotalEvolution: { + key: 'evolution.total', + header: 'Total space evolution (%)', + }, + spaceUsed: { key: 'usedSpace', header: 'Used space (GiB)' }, + uuid: { key: 'uuid', header: 'UUID' }, +} + class UsageReportPlugin { constructor({ xo, getDataDir }) { this._xo = xo @@ -772,6 +835,83 @@ class UsageReportPlugin { all: this._conf.all, }) + const attachments = [ + { + filename: `xoReport_${currDate}.html`, + content: template(data), + }, + ] + + if (data.allResources !== undefined) { + attachments.push( + { + filename: `xoReport_${currDate}_vms.csv`, + content: stringify(data.allResources.vms, { + cast: CSV_CAST, + header: true, + columns: [ + CSV_COLUMNS.uuid, + CSV_COLUMNS.name, + CSV_COLUMNS.cpu, + CSV_COLUMNS.cpuEvolution, + CSV_COLUMNS.ram, + CSV_COLUMNS.ramEvolution, + CSV_COLUMNS.diskRead, + CSV_COLUMNS.diskReadEvolution, + CSV_COLUMNS.diskWrite, + CSV_COLUMNS.diskWriteEvolution, + CSV_COLUMNS.iopsRead, + CSV_COLUMNS.iopsReadEvolution, + CSV_COLUMNS.iopsWrite, + CSV_COLUMNS.iopsWriteEvolution, + CSV_COLUMNS.iopsTotal, + CSV_COLUMNS.iopsTotalEvolution, + CSV_COLUMNS.netReception, + CSV_COLUMNS.netReceptionEvolution, + CSV_COLUMNS.netTransmission, + CSV_COLUMNS.netTransmissionEvolution, + ], + }), + }, + { + filename: `xoReport_${currDate}_hosts.csv`, + content: stringify(data.allResources.hosts, { + cast: CSV_CAST, + header: true, + columns: [ + CSV_COLUMNS.uuid, + CSV_COLUMNS.name, + CSV_COLUMNS.cpu, + CSV_COLUMNS.cpuEvolution, + CSV_COLUMNS.ram, + CSV_COLUMNS.ramEvolution, + CSV_COLUMNS.load, + CSV_COLUMNS.loadEvolution, + CSV_COLUMNS.netReception, + CSV_COLUMNS.netReceptionEvolution, + CSV_COLUMNS.netTransmission, + CSV_COLUMNS.netTransmissionEvolution, + ], + }), + }, + { + filename: `xoReport_${currDate}_srs.csv`, + content: stringify(data.allResources.srs, { + cast: CSV_CAST, + header: true, + columns: [ + CSV_COLUMNS.uuid, + CSV_COLUMNS.name, + CSV_COLUMNS.spaceTotal, + CSV_COLUMNS.spaceTotalEvolution, + CSV_COLUMNS.spaceUsed, + CSV_COLUMNS.spaceFree, + ], + }), + } + ) + } + await Promise.all([ xo.sendEmail({ to: this._conf.emails, @@ -782,12 +922,7 @@ class UsageReportPlugin { Please, find the attached report. best regards.`, - attachments: [ - { - filename: `xoReport_${currDate}.html`, - content: template(data), - }, - ], + attachments, }), storeData && storeStats({ diff --git a/yarn.lock b/yarn.lock index 057f3ffe1..28ad291de 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5088,6 +5088,11 @@ csv-parser@^2.1.0: minimist "^1.2.0" ndjson "^1.4.0" +csv-stringify@^5.5.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/csv-stringify/-/csv-stringify-5.5.0.tgz#0bdeaaf60d6e15b89c752a0eceb4b4c2c8af5a8a" + integrity sha512-G05575DSO/9vFzQxZN+Srh30cNyHk0SM0ePyiTChMD5WVt7GMTVPBQf4rtgMF6mqhNCJUPw4pN8LDe8MF9EYOA== + cuint@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/cuint/-/cuint-0.2.2.tgz#408086d409550c2631155619e9fa7bcadc3b991b"