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!" echo "[$(date '+%Y/%m/%d %H:%M:%S')] Done!"
fi fi

View File

@@ -15,7 +15,7 @@
<MDBContainer class="my-5"> <MDBContainer class="my-5">
<MDBRow class="gy-4"> <MDBRow class="gy-4">
<MDBCol md="12" v-if="!summaryLoaded"> <MDBCol md="12" v-if="!report">
<MDBCard> <MDBCard>
<MDBCardBody> <MDBCardBody>
<MDBCardTitle>1. Generate report</MDBCardTitle> <MDBCardTitle>1. Generate report</MDBCardTitle>
@@ -30,7 +30,7 @@
</MDBCard> </MDBCard>
</MDBCol> </MDBCol>
<MDBCol md="12" v-if="!summaryLoaded"> <MDBCol md="12" v-if="!report">
<MDBCard> <MDBCard>
<MDBCardBody> <MDBCardBody>
<MDBCardTitle>2. Upload report</MDBCardTitle> <MDBCardTitle>2. Upload report</MDBCardTitle>
@@ -39,75 +39,20 @@
</MDBCard> </MDBCard>
</MDBCol> </MDBCol>
<MDBCol md="12" v-if="summaryLoaded"> <MDBCol md="12" v-if="report">
<MDBCard> <ReportDetails :meta="report.meta"/>
<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> </MDBCol>
<MDBCol md="12" v-if="summaryLoaded"> <MDBCol md="12" v-if="report">
<MDBCard> <ReportSummary :report="report"/>
<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> </MDBCol>
<MDBCol md="12" v-if="summaryLoaded"> <MDBCol md="12" v-if="report">
<MDBCard> <ReportHistoryProcStat :report="report"/>
<MDBCardBody> </MDBCol>
<MDBCardTitle class="d-flex">
<span class="me-1">History</span> <MDBCol md="12" v-if="report">
<MDBBadge color="primary">/proc/meminfo</MDBBadge> <ReportHistoryProcMeminfo :report="report"/>
</MDBCardTitle>
<MDBCardText>
This section contains memory usage changes over time.
</MDBCardText>
<code>TODO</code>
</MDBCardBody>
</MDBCard>
</MDBCol> </MDBCol>
</MDBRow> </MDBRow>
@@ -116,33 +61,19 @@
</template> </template>
<script> <script>
import { import {MDBCard, MDBCardBody, MDBCardText, MDBCardTitle, MDBCol, MDBContainer, MDBFile, MDBNavbar, MDBNavbarItem, MDBNavbarNav, MDBRow} from 'mdb-vue-ui-kit';
MDBBadge, import ReportSummary from "@/components/report/summary/ReportSummary";
MDBCard, import ReportHistoryProcMeminfo from "@/components/report/history/ReportHistoryProcMeminfo";
MDBCardBody, import ReportHistoryProcStat from "@/components/report/history/ReportHistoryProcStat";
MDBCardText, import ReportDetails from "@/components/report/ReportDetails";
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 {ref} from "vue"; import {ref} from "vue";
export default { export default {
components: { components: {
History, ReportDetails,
Summary, ReportHistoryProcStat,
ReportHistoryProcMeminfo,
ReportSummary,
MDBNavbar, MDBNavbar,
MDBNavbarNav, MDBNavbarNav,
MDBNavbarItem, MDBNavbarItem,
@@ -153,100 +84,30 @@ export default {
MDBFile, MDBFile,
MDBRow, MDBRow,
MDBCol, MDBCol,
MDBTabs, MDBCardText
MDBTabNav,
MDBTabItem,
MDBTabContent,
MDBTabPane,
MDBCardText,
MDBBadge
}, },
props: { props: {
msg: String msg: String
}, },
watch: { watch: {
files: async function (value) { files: async function (value) {
this.rawData = (await this.fileToString(value[0])).split(/\r?\n/).map(l => l.split(" ")); let formData = new FormData();
const firstLineData = this.rawData[0].slice(1).map(i => parseInt(i)); formData.append('file', value[0]);
const lastLineData = this.rawData[this.rawData.length - 1].slice(1).map(i => parseInt(i)); this.report = await this.axios.post('/v1/parse', formData, {headers: {'Content-Type': 'multipart/form-data'}})
// update summary .then((response) => response.data)
this.summarySeriesAll = lastLineData; .catch((error) => console.log(error));
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);
} }
}, },
setup: function () { setup: function () {
return { return {
dataLabels: ['user', 'nice', 'system', 'idle', 'iowait', 'irq', 'softirq', 'steal', 'guest', 'guest_nice'],
appUrl: process.env.VUE_APP_URL appUrl: process.env.VUE_APP_URL
} }
}, },
data: function () { data: function () {
return { return {
files: ref([]), files: ref([]),
rawData: undefined, report: ref(),
// summary
summaryTab: ref('summary-current-run'),
summaryLoaded: false,
summaryStats: [],
summaryStatsAll: [],
summarySeries: [],
summarySeriesAll: [],
// steal
historyTab: ref('history-steal'),
historySeries: {},
}; };
},
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> </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> <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> </template>
<script> <script>
@@ -7,20 +7,24 @@ export default {
components: { components: {
}, },
props: { props: {
name: { chartSeries: {
type: String type: Array
},
yFormatter: {
type: Function,
default: function (value) {
return value.toFixed(3) + '%';
}
}, },
chartOptions: { chartOptions: {
type: Object, type: Object,
default: function () { default: function (props) {
return { return {
fill: {type: 'solid'}, fill: {type: 'solid'},
xaxis: {type: 'datetime'}, xaxis: {type: 'datetime'},
yaxis: { yaxis: {
labels: { labels: {
formatter: function (value) { formatter: props.yFormatter
return value.toFixed(3) + '%';
}
}, },
}, },
tooltip: { tooltip: {
@@ -34,9 +38,6 @@ export default {
}; };
} }
}, },
chartSeries: {
type: Array
}
} }
} }
</script> </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 {faGithub} from "@fortawesome/free-brands-svg-icons/faGithub";
import {faExclamationTriangle} from "@fortawesome/free-solid-svg-icons/faExclamationTriangle"; import {faExclamationTriangle} from "@fortawesome/free-solid-svg-icons/faExclamationTriangle";
import {faQuestionCircle} from "@fortawesome/free-solid-svg-icons/faQuestionCircle"; import {faQuestionCircle} from "@fortawesome/free-solid-svg-icons/faQuestionCircle";
import {faCheck} from "@fortawesome/free-solid-svg-icons/faCheck";
library.add(faGithub); library.add(faGithub);
library.add(faExclamationTriangle); library.add(faExclamationTriangle);
library.add(faQuestionCircle); library.add(faQuestionCircle);
library.add(faCheck);
export default (app) => { export default (app) => {
app.component('font-awesome-icon', FontAwesomeIcon); app.component('font-awesome-icon', FontAwesomeIcon);