Break down frontend into components, use backend-side parsing

This commit is contained in:
Sandra
2022-01-31 01:47:59 +01:00
parent 4974c00a26
commit cfc940c878
9 changed files with 367 additions and 177 deletions

View File

@@ -119,3 +119,4 @@ else
echo "[$(date '+%Y/%m/%d %H:%M:%S')] Done!"
fi

View File

@@ -15,7 +15,7 @@
<MDBContainer class="my-5">
<MDBRow class="gy-4">
<MDBCol md="12" v-if="!summaryLoaded">
<MDBCol md="12" v-if="!report">
<MDBCard>
<MDBCardBody>
<MDBCardTitle>1. Generate report</MDBCardTitle>
@@ -30,7 +30,7 @@
</MDBCard>
</MDBCol>
<MDBCol md="12" v-if="!summaryLoaded">
<MDBCol md="12" v-if="!report">
<MDBCard>
<MDBCardBody>
<MDBCardTitle>2. Upload report</MDBCardTitle>
@@ -39,75 +39,20 @@
</MDBCard>
</MDBCol>
<MDBCol md="12" v-if="summaryLoaded">
<MDBCard>
<MDBCardBody>
<MDBCardTitle class="d-flex">
<span class="me-1">Summary</span>
<MDBBadge color="primary">/proc/stat</MDBBadge>
</MDBCardTitle>
<MDBCardText>
This section contains raw data from <code>/proc/stat</code>, visualised and with calculated relative (percent) share of the total CPU time.
<ul>
<li><em>Current run</em> represents the data between the start and the end of the report generation.</li>
<li><em>All time</em> represents absolute values from the last system restart until the end of the report generation.</li>
</ul>
</MDBCardText>
<MDBTabs v-model="summaryTab">
<MDBTabNav tabsClasses="mb-3">
<MDBTabItem tabId="summary-current-run" href="summary-current-run">Current run</MDBTabItem>
<MDBTabItem tabId="summary-all-time" href="summary-all-time">All time</MDBTabItem>
</MDBTabNav>
<MDBTabContent>
<MDBTabPane tabId="summary-current-run">
<Summary :key="summaryTab" :stats="summaryStats" :chart-series="summarySeries"/>
</MDBTabPane>
<MDBTabPane tabId="summary-all-time">
<Summary :key="summaryTab" :stats="summaryStatsAll" :chart-series="summarySeriesAll"/>
</MDBTabPane>
</MDBTabContent>
</MDBTabs>
</MDBCardBody>
</MDBCard>
<MDBCol md="12" v-if="report">
<ReportDetails :meta="report.meta"/>
</MDBCol>
<MDBCol md="12" v-if="summaryLoaded">
<MDBCard>
<MDBCardBody>
<MDBCardTitle class="d-flex">
<span class="me-1">History</span>
<MDBBadge color="primary">/proc/stat</MDBBadge>
</MDBCardTitle>
<MDBCardText>
This section contains data (currently <code>{{ historyTab.replace('history-', '') }}</code>) changes over time represented as a relative (percent) share of the total CPU time.
</MDBCardText>
<MDBTabs v-model="historyTab">
<MDBTabNav tabsClasses="mb-3">
<MDBTabItem v-for="label in dataLabels" :key="label" :tabId="`history-${label}`" :href="`history-${label}`">{{ label }}</MDBTabItem>
</MDBTabNav>
<MDBTabContent>
<MDBTabPane v-for="label in dataLabels" :key="label" :tabId="`history-${label}`">
<History :key="historyTab" :name="label" :chart-series="historySeries[label]"/>
</MDBTabPane>
</MDBTabContent>
</MDBTabs>
</MDBCardBody>
</MDBCard>
<MDBCol md="12" v-if="report">
<ReportSummary :report="report"/>
</MDBCol>
<MDBCol md="12" v-if="summaryLoaded">
<MDBCard>
<MDBCardBody>
<MDBCardTitle class="d-flex">
<span class="me-1">History</span>
<MDBBadge color="primary">/proc/meminfo</MDBBadge>
</MDBCardTitle>
<MDBCardText>
This section contains memory usage changes over time.
</MDBCardText>
<code>TODO</code>
</MDBCardBody>
</MDBCard>
<MDBCol md="12" v-if="report">
<ReportHistoryProcStat :report="report"/>
</MDBCol>
<MDBCol md="12" v-if="report">
<ReportHistoryProcMeminfo :report="report"/>
</MDBCol>
</MDBRow>
@@ -116,33 +61,19 @@
</template>
<script>
import {
MDBBadge,
MDBCard,
MDBCardBody,
MDBCardText,
MDBCardTitle,
MDBCol,
MDBContainer,
MDBFile,
MDBNavbar,
MDBNavbarItem,
MDBNavbarNav,
MDBRow,
MDBTabContent,
MDBTabItem,
MDBTabNav,
MDBTabPane,
MDBTabs
} from 'mdb-vue-ui-kit';
import Summary from "@/components/Summary";
import History from "@/components/History";
import {MDBCard, MDBCardBody, MDBCardText, MDBCardTitle, MDBCol, MDBContainer, MDBFile, MDBNavbar, MDBNavbarItem, MDBNavbarNav, MDBRow} from 'mdb-vue-ui-kit';
import ReportSummary from "@/components/report/summary/ReportSummary";
import ReportHistoryProcMeminfo from "@/components/report/history/ReportHistoryProcMeminfo";
import ReportHistoryProcStat from "@/components/report/history/ReportHistoryProcStat";
import ReportDetails from "@/components/report/ReportDetails";
import {ref} from "vue";
export default {
components: {
History,
Summary,
ReportDetails,
ReportHistoryProcStat,
ReportHistoryProcMeminfo,
ReportSummary,
MDBNavbar,
MDBNavbarNav,
MDBNavbarItem,
@@ -153,100 +84,30 @@ export default {
MDBFile,
MDBRow,
MDBCol,
MDBTabs,
MDBTabNav,
MDBTabItem,
MDBTabContent,
MDBTabPane,
MDBCardText,
MDBBadge
MDBCardText
},
props: {
msg: String
},
watch: {
files: async function (value) {
this.rawData = (await this.fileToString(value[0])).split(/\r?\n/).map(l => l.split(" "));
const firstLineData = this.rawData[0].slice(1).map(i => parseInt(i));
const lastLineData = this.rawData[this.rawData.length - 1].slice(1).map(i => parseInt(i));
// update summary
this.summarySeriesAll = lastLineData;
this.summarySeries = lastLineData.map((i, index) => i - firstLineData[index]);
this.summaryLoaded = true;
// update history
this.dataLabels.forEach((label, index) => {
this.historySeries[label] = this.calculateHistory(this.rawData, index);
});
},
summarySeries: function (value) {
this.summaryStats = this.calculateSummaryStats(value);
},
summarySeriesAll: function (value) {
this.summaryStatsAll = this.calculateSummaryStats(value);
let formData = new FormData();
formData.append('file', value[0]);
this.report = await this.axios.post('/v1/parse', formData, {headers: {'Content-Type': 'multipart/form-data'}})
.then((response) => response.data)
.catch((error) => console.log(error));
}
},
setup: function () {
return {
dataLabels: ['user', 'nice', 'system', 'idle', 'iowait', 'irq', 'softirq', 'steal', 'guest', 'guest_nice'],
appUrl: process.env.VUE_APP_URL
}
},
data: function () {
return {
files: ref([]),
rawData: undefined,
// summary
summaryTab: ref('summary-current-run'),
summaryLoaded: false,
summaryStats: [],
summaryStatsAll: [],
summarySeries: [],
summarySeriesAll: [],
// steal
historyTab: ref('history-steal'),
historySeries: {},
report: ref(),
};
},
methods: {
fileToString(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsText(file, "UTF-8");
reader.onload = event => resolve(event.target.result)
reader.onerror = event => reject(event);
});
},
calculateSummaryStats(data) {
const dataTotal = data.reduce((a, b) => a + b, 0);
let stats = [];
this.dataLabels.forEach((label, index) => {
stats[index] = {
name: label,
value: data[index],
percent: (data[index] / dataTotal) * 100
}
});
stats.sort((a, b) => {
return (a.value > b.value) ? -1 : 1;
});
return stats;
},
calculateHistory(rawData, index) {
let history = [];
let lastTotal = 0;
let lastValue = 0;
rawData.forEach(line => {
const date = new Date(line[0]);
const rawValue = parseInt(line[index + 1]);
const value = rawValue - lastValue;
const rawTotal = line.slice(1).map(i => parseInt(i)).reduce((a, b) => a + b, 0);
const total = rawTotal - lastTotal;
history.push([date, ((value / total) * 100)])
lastTotal = rawTotal;
lastValue = rawValue;
})
return history;
}
}
}
</script>

View File

@@ -0,0 +1,53 @@
<template>
<MDBCard>
<MDBCardBody>
<MDBCardTitle class="d-flex">
<span class="me-1">Report from {{ new Date().toLocaleString() }}</span>
<MDBBadge color="primary">OT-v1.0</MDBBadge>
</MDBCardTitle>
<MDBRow>
<MDBCol md="12">
<MDBTable class="mb-0">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Value</th>
</tr>
</thead>
<tbody>
<tr v-for="key in Object.keys(meta)" :key="key">
<th scope="row">{{ key }}</th>
<td>{{ meta[key] }}</td>
</tr>
</tbody>
</MDBTable>
</MDBCol>
</MDBRow>
</MDBCardBody>
</MDBCard>
</template>
<script>
import {MDBBadge, MDBCard, MDBCardBody, MDBCardTitle, MDBTable, MDBRow, MDBCol} from "mdb-vue-ui-kit";
export default {
components: {
MDBCard,
MDBCardBody,
MDBCardTitle,
MDBTable,
MDBBadge,
MDBRow,
MDBCol
},
props: {
meta: Object
}
}
</script>
<style scoped>
table th, table td {
padding: .2em;
}
</style>

View File

@@ -1,5 +1,5 @@
<template>
<apexchart type="line" height="300" :options="chartOptions" :series="[{name: name, data: chartSeries}]"></apexchart>
<apexchart type="line" height="300" :options="chartOptions" :series="chartSeries"></apexchart>
</template>
<script>
@@ -7,20 +7,24 @@ export default {
components: {
},
props: {
name: {
type: String
chartSeries: {
type: Array
},
yFormatter: {
type: Function,
default: function (value) {
return value.toFixed(3) + '%';
}
},
chartOptions: {
type: Object,
default: function () {
default: function (props) {
return {
fill: {type: 'solid'},
xaxis: {type: 'datetime'},
yaxis: {
labels: {
formatter: function (value) {
return value.toFixed(3) + '%';
}
formatter: props.yFormatter
},
},
tooltip: {
@@ -34,9 +38,6 @@ export default {
};
}
},
chartSeries: {
type: Array
}
}
}
</script>

View File

@@ -0,0 +1,90 @@
<template>
<MDBCard>
<MDBCardBody>
<MDBCardTitle class="d-flex">
<span class="me-1">History</span>
<MDBBadge color="primary">/proc/meminfo</MDBBadge>
</MDBCardTitle>
<MDBCardText>
This section contains memory usage changes over time.
</MDBCardText>
<!--suppress RequiredAttributes -->
<MDBTabs v-model="historyTab">
<MDBTabNav tabsClasses="mb-3">
<MDBTabItem tab-id="history-mem" href="history-mem">Memory</MDBTabItem>
<MDBTabItem tab-id="history-swap" href="history-swap">Swap</MDBTabItem>
</MDBTabNav>
<MDBTabContent>
<MDBTabPane tab-id="history-mem">
<ReportHistoryPage :key="historyTab" :chart-series="memSeries" :y-formatter="yFormatter"/>
</MDBTabPane>
<MDBTabPane tab-id="history-swap">
<ReportHistoryPage :key="historyTab" :chart-series="swapSeries" :y-formatter="yFormatter"/>
</MDBTabPane>
</MDBTabContent>
</MDBTabs>
</MDBCardBody>
</MDBCard>
</template>
<script>
import {MDBBadge, MDBCard, MDBCardBody, MDBCardText, MDBCardTitle, MDBTabContent, MDBTabItem, MDBTabNav, MDBTabPane, MDBTabs} from "mdb-vue-ui-kit";
import ReportHistoryPage from "@/components/report/history/ReportHistoryPage";
import {ref} from "vue";
export default {
components: {
ReportHistoryPage,
MDBCard,
MDBCardBody,
MDBCardTitle,
MDBCardText,
MDBBadge,
MDBTabs,
MDBTabNav,
MDBTabItem,
MDBTabContent,
MDBTabPane,
},
props: {
report: {
type: Object
}
},
data: function () {
const memMetrics = ['mem/total', 'mem/free', 'mem/available', 'mem/buffers', 'mem/cached'];
const swapMetrics = ['swap/cached', 'swap/total', 'swap/free'];
const header = this.report.header;
const data = this.report.data;
const timeIndex = header.indexOf("timestamp");
const memSeries = memMetrics.map(metric => {
const index = header.indexOf(metric);
return {name: metric, data: data.map(entry => [new Date(entry[timeIndex] * 1000), entry[index]])}
})
const swapSeries = swapMetrics.map(metric => {
const index = header.indexOf(metric);
return {name: metric, data: data.map(entry => [new Date(entry[timeIndex] * 1000), entry[index]])}
})
return {
historyTab: ref('history-mem'),
memSeries,
swapSeries
}
},
methods: {
yFormatter(value) {
return this.formatBytes(value * 1024);
},
formatBytes(value) {
let b = 0, c = parseInt(value, 10) || 0;
for (; 1024 <= c && ++b;) c /= 1024;
return c.toFixed(10 > c && 0 < b ? 1 : 0) + " " + ["bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"][b]
}
}
}
</script>

View File

@@ -0,0 +1,82 @@
<template>
<MDBCard>
<MDBCardBody>
<MDBCardTitle class="d-flex">
<span class="me-1">History</span>
<MDBBadge color="primary">/proc/stat</MDBBadge>
</MDBCardTitle>
<MDBCardText>
This section contains data (currently <code>{{ historyTab.replace('history-', '') }}</code>) changes over time represented as a relative (percent) share of the total CPU time.
</MDBCardText>
<!--suppress RequiredAttributes -->
<MDBTabs v-model="historyTab">
<MDBTabNav tabsClasses="mb-3">
<MDBTabItem v-for="series in historySeries" :key="series.name" :tabId="`history-${series.name}`" :href="`history-${series.name}`">{{ series.name }}</MDBTabItem>
</MDBTabNav>
<MDBTabContent>
<MDBTabPane v-for="series in historySeries" :key="series.name" :tabId="`history-${series.name}`">
<ReportHistoryPage :key="historyTab" :name="series.name" :chart-series="[series]"/>
</MDBTabPane>
</MDBTabContent>
</MDBTabs>
</MDBCardBody>
</MDBCard>
</template>
<script>
import {MDBBadge, MDBCard, MDBCardBody, MDBCardText, MDBCardTitle, MDBTabContent, MDBTabItem, MDBTabNav, MDBTabPane, MDBTabs} from "mdb-vue-ui-kit";
import ReportHistoryPage from "@/components/report/history/ReportHistoryPage";
import {ref} from "vue";
export default {
components: {
ReportHistoryPage,
MDBCard,
MDBCardBody,
MDBCardTitle,
MDBCardText,
MDBTabs,
MDBTabNav,
MDBTabItem,
MDBTabContent,
MDBTabPane,
MDBBadge,
},
props: {
report: {
type: Object
}
},
data: function() {
const metrics = ['cpu/user', 'cpu/nice', 'cpu/system', 'cpu/idle', 'cpu/iowait', 'cpu/irq', 'cpu/softirq', 'cpu/steal', 'cpu/guest', 'cpu/guest_nice'];
const header = this.report.header;
const data = this.report.data;
const timeIndex = header.indexOf("timestamp");
const cpuDataSums = data
.map(row => this.rowSlice(row, header, metrics))
.map(row => row.reduce((a, b) => a + b, 0))
const series = metrics.map(metric => {
const index = header.indexOf(metric);
return {
name: metric.replace('cpu/', ''),
data: data.map((entry, i) => [new Date(entry[timeIndex] * 1000), (entry[index] / cpuDataSums[i] * 100)])
}
});
return {
historyTab: ref('history-steal'),
historySeries: series
}
},
methods: {
rowSlice(row, header, metrics) {
return row.filter((value, index) => {
return metrics.includes(header[index]);
});
}
}
}
</script>

View File

@@ -0,0 +1,100 @@
<template>
<MDBCard>
<MDBCardBody>
<MDBCardTitle class="d-flex">
<span class="me-1">Summary</span>
<MDBBadge color="primary">/proc/stat</MDBBadge>
</MDBCardTitle>
<MDBCardText>
This section contains raw data from <code>/proc/stat</code>, visualised and with calculated relative (percent) share of the total CPU time.
<ul>
<li><em>Current run</em> represents the data between the start and the end of the report generation.</li>
<li><em>All time</em> represents absolute values from the last system restart until the end of the report generation.</li>
</ul>
</MDBCardText>
<!--suppress RequiredAttributes -->
<MDBTabs v-model="summaryTab">
<MDBTabNav tabsClasses="mb-3">
<MDBTabItem tabId="summary-current-run" href="summary-current-run">Current run</MDBTabItem>
<MDBTabItem tabId="summary-all-time" href="summary-all-time">All time</MDBTabItem>
</MDBTabNav>
<MDBTabContent>
<MDBTabPane tabId="summary-current-run">
<ReportSummaryPage :key="summaryTab" :stats="summaryCurrent.stats" :chart-series="summaryCurrent.series"/>
</MDBTabPane>
<MDBTabPane tabId="summary-all-time">
<ReportSummaryPage :key="summaryTab" :stats="summaryAll.stats" :chart-series="summaryAll.series"/>
</MDBTabPane>
</MDBTabContent>
</MDBTabs>
</MDBCardBody>
</MDBCard>
</template>
<script>
import {MDBBadge, MDBCard, MDBCardBody, MDBCardText, MDBCardTitle, MDBTabContent, MDBTabItem, MDBTabNav, MDBTabPane, MDBTabs} from "mdb-vue-ui-kit";
import ReportSummaryPage from "@/components/report/summary/ReportSummaryPage";
import {ref} from "vue";
export default {
components: {
MDBCard,
MDBCardBody,
MDBCardTitle,
MDBCardText,
MDBTabs,
MDBTabNav,
MDBTabItem,
MDBTabContent,
MDBTabPane,
MDBBadge,
ReportSummaryPage,
},
props: {
report: {
type: Object
}
},
data: function () {
const metrics = ['cpu/user', 'cpu/nice', 'cpu/system', 'cpu/idle', 'cpu/iowait', 'cpu/irq', 'cpu/softirq', 'cpu/steal', 'cpu/guest', 'cpu/guest_nice'];
const header = this.report.header;
const data = this.report.data;
return {
summaryTab: ref('summary-current-run'),
summaryCurrent: this.getSummaryCurrent(metrics, header, data),
summaryAll: this.getSummaryAll(metrics, header, data),
}
},
methods: {
getSummaryCurrent(metrics, header, data) {
const firstRow = data[0];
const lastRow = data[data.length - 1];
const dataRow = lastRow.map((value, index) => value - firstRow[index]);
return this.getSummary(metrics, header, dataRow)
},
getSummaryAll(metrics, header, data) {
const dataRow = data[data.length - 1];
return this.getSummary(metrics, header, dataRow)
},
getSummary(metrics, header, dataRow) {
const stats = metrics.map(metric => {
const name = metric.replace('cpu/', '');
const value = dataRow[header.indexOf(metric)];
const percent = value / this.rowSlice(dataRow, header, metrics).reduce((a, b) => a + b, 0) * 100;
return {name, value, percent: (Number.isNaN(percent) ? 0 : percent)};
});
return {
series: stats.map(stat => stat.value),
stats: stats.sort((a, b) => (a.value > b.value) ? -1 : 1)
}
},
rowSlice(row, header, metrics) {
return row.filter((value, index) => {
return metrics.includes(header[index]);
});
}
}
}
</script>

View File

@@ -4,10 +4,12 @@ import {FontAwesomeIcon, FontAwesomeLayers} from '@fortawesome/vue-fontawesome'
import {faGithub} from "@fortawesome/free-brands-svg-icons/faGithub";
import {faExclamationTriangle} from "@fortawesome/free-solid-svg-icons/faExclamationTriangle";
import {faQuestionCircle} from "@fortawesome/free-solid-svg-icons/faQuestionCircle";
import {faCheck} from "@fortawesome/free-solid-svg-icons/faCheck";
library.add(faGithub);
library.add(faExclamationTriangle);
library.add(faQuestionCircle);
library.add(faCheck);
export default (app) => {
app.component('font-awesome-icon', FontAwesomeIcon);