diff --git a/src/client/app/admin/views/queue.chart.vue b/src/client/app/admin/views/queue.chart.vue new file mode 100644 index 0000000000..c3d68d75af --- /dev/null +++ b/src/client/app/admin/views/queue.chart.vue @@ -0,0 +1,181 @@ +<template> +<div> + <ui-info warn v-if="latestStats && latestStats.waiting > 0">The queue is jammed.</ui-info> + <ui-horizon-group inputs v-if="latestStats" class="fit-bottom"> + <ui-input :value="latestStats.activeSincePrevTick | number" type="text" readonly> + <span>Process</span> + <template #prefix><fa :icon="fasPlayCircle"/></template> + <template #suffix>jobs/tick</template> + </ui-input> + <ui-input :value="latestStats.active | number" type="text" readonly> + <span>Active</span> + <template #prefix><fa :icon="farPlayCircle"/></template> + <template #suffix>jobs</template> + </ui-input> + <ui-input :value="latestStats.waiting | number" type="text" readonly> + <span>Waiting</span> + <template #prefix><fa :icon="faStopCircle"/></template> + <template #suffix>jobs</template> + </ui-input> + <ui-input :value="latestStats.delayed | number" type="text" readonly> + <span>Delayed</span> + <template #prefix><fa :icon="faStopwatch"/></template> + <template #suffix>jobs</template> + </ui-input> + </ui-horizon-group> + <div ref="chart" class="wptihjuy"></div> +</div> +</template> + +<script lang="ts"> +import Vue from 'vue'; +import i18n from '../../i18n'; +import ApexCharts from 'apexcharts'; +import * as tinycolor from 'tinycolor2'; +import { faStopwatch, faPlayCircle as fasPlayCircle } from '@fortawesome/free-solid-svg-icons'; +import { faStopCircle, faPlayCircle as farPlayCircle } from '@fortawesome/free-regular-svg-icons'; + +export default Vue.extend({ + i18n: i18n('admin/views/queue.vue'), + + props: { + type: { + type: String, + required: true + }, + connection: { + required: true + }, + limit: { + type: Number, + required: true + } + }, + + data() { + return { + stats: [], + chart: null, + faStopwatch, faStopCircle, farPlayCircle, fasPlayCircle + }; + }, + + computed: { + latestStats(): any { + return this.stats.length > 0 ? this.stats[this.stats.length - 1][this.type] : null; + } + }, + + watch: { + stats(stats) { + this.chart.updateSeries([{ + name: 'Process', + type: 'area', + data: stats.map((x, i) => ({ x: i, y: x[this.type].activeSincePrevTick })) + }, { + name: 'Active', + type: 'area', + data: stats.map((x, i) => ({ x: i, y: x[this.type].active })) + }, { + name: 'Waiting', + type: 'line', + data: stats.map((x, i) => ({ x: i, y: x[this.type].waiting })) + }, { + name: 'Delayed', + type: 'line', + data: stats.map((x, i) => ({ x: i, y: x[this.type].delayed })) + }]); + }, + }, + + mounted() { + this.chart = new ApexCharts(this.$refs.chart, { + chart: { + id: this.type, + group: 'queue', + type: 'area', + height: 200, + animations: { + dynamicAnimation: { + enabled: false + } + }, + toolbar: { + show: false + }, + zoom: { + enabled: false + } + }, + dataLabels: { + enabled: false + }, + grid: { + clipMarkers: false, + borderColor: 'rgba(0, 0, 0, 0.1)', + xaxis: { + lines: { + show: true, + } + }, + }, + stroke: { + curve: 'straight', + width: 2 + }, + tooltip: { + enabled: false + }, + legend: { + labels: { + colors: tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--text')).toRgbString() + }, + }, + series: [] as any, + colors: ['#00E396', '#00BCD4', '#FFB300', '#e53935'], + xaxis: { + type: 'numeric', + labels: { + show: false + }, + tooltip: { + enabled: false + } + }, + yaxis: { + show: false, + min: 0, + } + }); + + this.chart.render(); + + this.connection.on('stats', this.onStats); + this.connection.on('statsLog', this.onStatsLog); + + this.$once('hook:beforeDestroy', () => { + if (this.chart) this.chart.destroy(); + }); + }, + + methods: { + onStats(stats) { + this.stats.push(stats); + if (this.stats.length > this.limit) this.stats.shift(); + }, + + onStatsLog(statsLog) { + for (const stats of statsLog.reverse()) { + this.onStats(stats); + } + }, + } +}); +</script> + +<style lang="stylus" scoped> +.wptihjuy + min-height 200px !important + margin 0 -8px -8px -8px + +</style> diff --git a/src/client/app/admin/views/queue.vue b/src/client/app/admin/views/queue.vue index b379ba1850..358ed6cf74 100644 --- a/src/client/app/admin/views/queue.vue +++ b/src/client/app/admin/views/queue.vue @@ -4,57 +4,11 @@ <template #title><fa :icon="faChartBar"/> {{ $t('title') }}</template> <section class="wptihjuy"> <header><fa :icon="faPaperPlane"/> Deliver</header> - <ui-info warn v-if="latestStats && latestStats.deliver.waiting > 0">The queue is jammed.</ui-info> - <ui-horizon-group inputs v-if="latestStats" class="fit-bottom"> - <ui-input :value="latestStats.deliver.activeSincePrevTick | number" type="text" readonly> - <span>Process</span> - <template #prefix><fa :icon="fasPlayCircle"/></template> - <template #suffix>jobs/tick</template> - </ui-input> - <ui-input :value="latestStats.deliver.active | number" type="text" readonly> - <span>Active</span> - <template #prefix><fa :icon="farPlayCircle"/></template> - <template #suffix>jobs</template> - </ui-input> - <ui-input :value="latestStats.deliver.waiting | number" type="text" readonly> - <span>Waiting</span> - <template #prefix><fa :icon="faStopCircle"/></template> - <template #suffix>jobs</template> - </ui-input> - <ui-input :value="latestStats.deliver.delayed | number" type="text" readonly> - <span>Delayed</span> - <template #prefix><fa :icon="faStopwatch"/></template> - <template #suffix>jobs</template> - </ui-input> - </ui-horizon-group> - <div ref="deliverChart" class="chart"></div> + <x-chart v-if="connection" :connection="connection" :limit="chartLimit" type="deliver"/> </section> <section class="wptihjuy"> <header><fa :icon="faInbox"/> Inbox</header> - <ui-info warn v-if="latestStats && latestStats.inbox.waiting > 0">The queue is jammed.</ui-info> - <ui-horizon-group inputs v-if="latestStats" class="fit-bottom"> - <ui-input :value="latestStats.inbox.activeSincePrevTick | number" type="text" readonly> - <span>Process</span> - <template #prefix><fa :icon="fasPlayCircle"/></template> - <template #suffix>jobs/tick</template> - </ui-input> - <ui-input :value="latestStats.inbox.active | number" type="text" readonly> - <span>Active</span> - <template #prefix><fa :icon="farPlayCircle"/></template> - <template #suffix>jobs</template> - </ui-input> - <ui-input :value="latestStats.inbox.waiting | number" type="text" readonly> - <span>Waiting</span> - <template #prefix><fa :icon="faStopCircle"/></template> - <template #suffix>jobs</template> - </ui-input> - <ui-input :value="latestStats.inbox.delayed | number" type="text" readonly> - <span>Delayed</span> - <template #prefix><fa :icon="faStopwatch"/></template> - <template #suffix>jobs</template> - </ui-input> - </ui-horizon-group> - <div ref="inboxChart" class="chart"></div> + <x-chart v-if="connection" :connection="connection" :limit="chartLimit" type="inbox"/> </section> <section> <ui-button @click="removeAllJobs">{{ $t('remove-all-jobs') }}</ui-button> @@ -94,74 +48,31 @@ <script lang="ts"> import Vue from 'vue'; +import { faTasks, faInbox } from '@fortawesome/free-solid-svg-icons'; +import { faPaperPlane, faChartBar } from '@fortawesome/free-regular-svg-icons'; import i18n from '../../i18n'; -import ApexCharts from 'apexcharts'; -import * as tinycolor from 'tinycolor2'; -import { faTasks, faInbox, faStopwatch, faPlayCircle as fasPlayCircle } from '@fortawesome/free-solid-svg-icons'; -import { faPaperPlane, faStopCircle, faPlayCircle as farPlayCircle, faChartBar } from '@fortawesome/free-regular-svg-icons'; - -const limit = 200; +import XChart from './queue.chart.vue'; export default Vue.extend({ i18n: i18n('admin/views/queue.vue'), + components: { + XChart + }, + data() { return { - stats: [], - deliverChart: null, - inboxChart: null, + connection: null, + chartLimit: 200, jobs: [], jobsLimit: 50, domain: 'deliver', state: 'delayed', - faTasks, faPaperPlane, faInbox, faStopwatch, faStopCircle, farPlayCircle, fasPlayCircle, faChartBar + faTasks, faPaperPlane, faInbox, faChartBar }; }, - computed: { - latestStats(): any { - return this.stats[this.stats.length - 1]; - } - }, - watch: { - stats(stats) { - this.inboxChart.updateSeries([{ - name: 'Process', - type: 'area', - data: stats.map((x, i) => ({ x: i, y: x.inbox.activeSincePrevTick })) - }, { - name: 'Active', - type: 'area', - data: stats.map((x, i) => ({ x: i, y: x.inbox.active })) - }, { - name: 'Waiting', - type: 'line', - data: stats.map((x, i) => ({ x: i, y: x.inbox.waiting })) - }, { - name: 'Delayed', - type: 'line', - data: stats.map((x, i) => ({ x: i, y: x.inbox.delayed })) - }]); - this.deliverChart.updateSeries([{ - name: 'Process', - type: 'area', - data: stats.map((x, i) => ({ x: i, y: x.deliver.activeSincePrevTick })) - }, { - name: 'Active', - type: 'area', - data: stats.map((x, i) => ({ x: i, y: x.deliver.active })) - }, { - name: 'Waiting', - type: 'line', - data: stats.map((x, i) => ({ x: i, y: x.deliver.waiting })) - }, { - name: 'Delayed', - type: 'line', - data: stats.map((x, i) => ({ x: i, y: x.deliver.delayed })) - }]); - }, - domain() { this.jobs = []; this.fetchJobs(); @@ -176,83 +87,14 @@ export default Vue.extend({ mounted() { this.fetchJobs(); - const chartOpts = id => ({ - chart: { - id, - group: 'queue', - type: 'area', - height: 200, - animations: { - dynamicAnimation: { - enabled: false - } - }, - toolbar: { - show: false - }, - zoom: { - enabled: false - } - }, - dataLabels: { - enabled: false - }, - grid: { - clipMarkers: false, - borderColor: 'rgba(0, 0, 0, 0.1)', - xaxis: { - lines: { - show: true, - } - }, - }, - stroke: { - curve: 'straight', - width: 2 - }, - tooltip: { - enabled: false - }, - legend: { - labels: { - colors: tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--text')).toRgbString() - }, - }, - series: [] as any, - colors: ['#00E396', '#00BCD4', '#FFB300', '#e53935'], - xaxis: { - type: 'numeric', - labels: { - show: false - }, - tooltip: { - enabled: false - } - }, - yaxis: { - show: false, - min: 0, - } - }); - - this.inboxChart = new ApexCharts(this.$refs.inboxChart, chartOpts('a')); - this.deliverChart = new ApexCharts(this.$refs.deliverChart, chartOpts('b')); - - this.inboxChart.render(); - this.deliverChart.render(); - - const connection = this.$root.stream.useSharedConnection('queueStats'); - connection.on('stats', this.onStats); - connection.on('statsLog', this.onStatsLog); - connection.send('requestLog', { + this.connection = this.$root.stream.useSharedConnection('queueStats'); + this.connection.send('requestLog', { id: Math.random().toString().substr(2, 8), - length: limit + length: this.chartLimit }); this.$once('hook:beforeDestroy', () => { - connection.dispose(); - this.inboxChart.destroy(); - this.deliverChart.destroy(); + this.connection.dispose(); }); }, @@ -274,17 +116,6 @@ export default Vue.extend({ }); }, - onStats(stats) { - this.stats.push(stats); - if (this.stats.length > limit) this.stats.shift(); - }, - - onStatsLog(statsLog) { - for (const stats of statsLog.reverse()) { - this.onStats(stats); - } - }, - fetchJobs() { this.$root.api('admin/queue/jobs', { domain: this.domain, @@ -299,11 +130,6 @@ export default Vue.extend({ </script> <style lang="stylus" scoped> -.wptihjuy - > .chart - min-height 200px !important - margin 0 -8px - .xvvuvgsv > b margin-right 16px diff --git a/src/daemons/queue-stats.ts b/src/daemons/queue-stats.ts index f9ba90a0b7..e560354c74 100644 --- a/src/daemons/queue-stats.ts +++ b/src/daemons/queue-stats.ts @@ -1,6 +1,6 @@ import * as Deque from 'double-ended-queue'; import Xev from 'xev'; -import { deliverQueue, inboxQueue } from '../queue'; +import { deliverQueue, inboxQueue, dbQueue, objectStorageQueue } from '../queue'; const ev = new Xev(); @@ -18,6 +18,8 @@ export default function() { let activeDeliverJobs = 0; let activeInboxJobs = 0; + let activeDbJobs = 0; + let activeObjectStorageJobs = 0; deliverQueue.on('global:active', () => { activeDeliverJobs++; @@ -27,9 +29,19 @@ export default function() { activeInboxJobs++; }); + dbQueue.on('global:active', () => { + activeDbJobs++; + }); + + objectStorageQueue.on('global:active', () => { + activeObjectStorageJobs++; + }); + async function tick() { const deliverJobCounts = await deliverQueue.getJobCounts(); const inboxJobCounts = await inboxQueue.getJobCounts(); + const dbJobCounts = await dbQueue.getJobCounts(); + const objectStorageJobCounts = await objectStorageQueue.getJobCounts(); const stats = { deliver: { @@ -43,7 +55,19 @@ export default function() { active: inboxJobCounts.active, waiting: inboxJobCounts.waiting, delayed: inboxJobCounts.delayed - } + }, + db: { + activeSincePrevTick: activeDbJobs, + active: dbJobCounts.active, + waiting: dbJobCounts.waiting, + delayed: dbJobCounts.delayed + }, + objectStorage: { + activeSincePrevTick: activeObjectStorageJobs, + active: objectStorageJobCounts.active, + waiting: objectStorageJobCounts.waiting, + delayed: objectStorageJobCounts.delayed + }, }; ev.emit('queueStats', stats); @@ -53,6 +77,8 @@ export default function() { activeDeliverJobs = 0; activeInboxJobs = 0; + activeDbJobs = 0; + activeObjectStorageJobs = 0; } tick();