This commit is contained in:
syuilo 2019-03-10 19:16:33 +09:00
parent 5a0a297634
commit 80a2172715
No known key found for this signature in database
GPG Key ID: BDC4C49D06AB9D69
9 changed files with 249 additions and 0 deletions

View File

@ -26,6 +26,7 @@
<option value="hashtags">{{ $t('@.widgets.hashtags') }}</option> <option value="hashtags">{{ $t('@.widgets.hashtags') }}</option>
<option value="posts-monitor">{{ $t('@.widgets.posts-monitor') }}</option> <option value="posts-monitor">{{ $t('@.widgets.posts-monitor') }}</option>
<option value="server">{{ $t('@.widgets.server') }}</option> <option value="server">{{ $t('@.widgets.server') }}</option>
<option value="queue">{{ $t('@.widgets.queue') }}</option>
<option value="nav">{{ $t('@.widgets.nav') }}</option> <option value="nav">{{ $t('@.widgets.nav') }}</option>
<option value="tips">{{ $t('@.widgets.tips') }}</option> <option value="tips">{{ $t('@.widgets.tips') }}</option>
</select> </select>

View File

@ -31,3 +31,4 @@ Vue.component('mkw-version', wVersion);
Vue.component('mkw-hashtags', wHashtags); Vue.component('mkw-hashtags', wHashtags);
Vue.component('mkw-instance', wInstance); Vue.component('mkw-instance', wInstance);
Vue.component('mkw-post-form', wPostForm); Vue.component('mkw-post-form', wPostForm);
Vue.component('mkw-queue', () => import('./queue.vue').then(m => m.default));

View File

@ -0,0 +1,157 @@
<template>
<div>
<ui-container :show-header="!props.compact">
<template #header><fa :icon="faTasks"/>Queue</template>
<div class="mntrproz">
<div>
<b>In</b>
<span v-if="latestStats">{{ latestStats.inbox.active | number }} / {{ latestStats.inbox.delayed | number }}</span>
<div ref="in"></div>
</div>
<div>
<b>Out</b>
<span v-if="latestStats">{{ latestStats.deliver.active | number }} / {{ latestStats.deliver.delayed | number }}</span>
<div ref="out"></div>
</div>
</div>
</ui-container>
</div>
</template>
<script lang="ts">
import define from '../../define-widget';
import { faTasks } from '@fortawesome/free-solid-svg-icons';
import ApexCharts from 'apexcharts';
export default define({
name: 'queue',
props: () => ({
compact: false
})
}).extend({
data() {
return {
stats: [],
inChart: null,
outChart: null,
faTasks
};
},
watch: {
stats(stats) {
this.inChart.updateSeries([{
data: stats.map((x, i) => ({ x: i, y: x.inbox.active }))
}, {
data: stats.map((x, i) => ({ x: i, y: x.inbox.delayed }))
}]);
this.outChart.updateSeries([{
data: stats.map((x, i) => ({ x: i, y: x.deliver.active }))
}, {
data: stats.map((x, i) => ({ x: i, y: x.deliver.delayed }))
}]);
}
},
computed: {
latestStats(): any {
return this.stats[this.stats.length - 1];
}
},
mounted() {
const chartOpts = {
chart: {
type: 'area',
height: 70,
animations: {
dynamicAnimation: {
enabled: false
}
},
sparkline: {
enabled: true,
}
},
tooltip: {
enabled: false
},
stroke: {
curve: 'straight',
width: 1
},
series: [{
data: [] as any
}, {
data: [] as any
}],
yaxis: {
min: 0,
}
};
this.inChart = new ApexCharts(this.$refs.in, chartOpts);
this.outChart = new ApexCharts(this.$refs.out, chartOpts);
this.inChart.render();
this.outChart.render();
const connection = this.$root.stream.useSharedConnection('queueStats');
connection.on('stats', this.onStats);
connection.on('statsLog', this.onStatsLog);
connection.send('requestLog', {
id: Math.random().toString().substr(2, 8),
length: 50
});
this.$once('hook:beforeDestroy', () => {
connection.dispose();
this.inChart.destroy();
this.outChart.destroy();
});
},
methods: {
func() {
this.props.compact = !this.props.compact;
this.save();
},
onStats(stats) {
this.stats.push(stats);
if (this.stats.length > 50) this.stats.shift();
},
onStatsLog(statsLog) {
for (const stats of statsLog.reverse()) {
this.onStats(stats);
}
}
}
});
</script>
<style lang="stylus" scoped>
.mntrproz
display flex
padding 4px
> div
width 50%
padding 4px
> b
display block
font-size 12px
color var(--text)
> span
position absolute
top 4px
right 4px
opacity 0.7
font-size 12px
color var(--text)
</style>

View File

@ -27,6 +27,7 @@
<option value="hashtags">{{ $t('@.widgets.hashtags') }}</option> <option value="hashtags">{{ $t('@.widgets.hashtags') }}</option>
<option value="posts-monitor">{{ $t('@.widgets.posts-monitor') }}</option> <option value="posts-monitor">{{ $t('@.widgets.posts-monitor') }}</option>
<option value="server">{{ $t('@.widgets.server') }}</option> <option value="server">{{ $t('@.widgets.server') }}</option>
<option value="queue">{{ $t('@.widgets.queue') }}</option>
<option value="nav">{{ $t('@.widgets.nav') }}</option> <option value="nav">{{ $t('@.widgets.nav') }}</option>
<option value="tips">{{ $t('@.widgets.tips') }}</option> <option value="tips">{{ $t('@.widgets.tips') }}</option>
</select> </select>

View File

@ -19,6 +19,7 @@
<option value="posts-monitor">{{ $t('@.widgets.posts-monitor') }}</option> <option value="posts-monitor">{{ $t('@.widgets.posts-monitor') }}</option>
<option value="version">{{ $t('@.widgets.version') }}</option> <option value="version">{{ $t('@.widgets.version') }}</option>
<option value="server">{{ $t('@.widgets.server') }}</option> <option value="server">{{ $t('@.widgets.server') }}</option>
<option value="queue">{{ $t('@.widgets.queue') }}</option>
<option value="memo">{{ $t('@.widgets.memo') }}</option> <option value="memo">{{ $t('@.widgets.memo') }}</option>
<option value="nav">{{ $t('@.widgets.nav') }}</option> <option value="nav">{{ $t('@.widgets.nav') }}</option>
<option value="tips">{{ $t('@.widgets.tips') }}</option> <option value="tips">{{ $t('@.widgets.tips') }}</option>

View File

@ -0,0 +1,43 @@
import * as Deque from 'double-ended-queue';
import Xev from 'xev';
import { deliverQueue, inboxQueue } from '../queue';
const ev = new Xev();
const interval = 1000;
/**
* Report queue stats regularly
*/
export default function() {
const log = new Deque<any>();
ev.on('requestQueueStatsLog', x => {
ev.emit(`queueStatsLog:${x.id}`, log.toArray().slice(0, x.length || 50));
});
async function tick() {
const deliverJobCounts = await deliverQueue.getJobCounts();
const inboxJobCounts = await inboxQueue.getJobCounts();
const stats = {
deliver: {
active: Math.floor(Math.random() * 100),
delayed: Math.floor(Math.random() * 1000),
},
inbox: {
active: Math.floor(Math.random() * 100),
delayed: Math.floor(Math.random() * 1000),
}
};
ev.emit('queueStats', stats);
log.unshift(stats);
if (log.length > 200) log.pop();
}
tick();
setInterval(tick, interval);
}

View File

@ -16,6 +16,7 @@ import Xev from 'xev';
import Logger from './services/logger'; import Logger from './services/logger';
import serverStats from './daemons/server-stats'; import serverStats from './daemons/server-stats';
import notesStats from './daemons/notes-stats'; import notesStats from './daemons/notes-stats';
import queueStats from './daemons/queue-stats';
import loadConfig from './config/load'; import loadConfig from './config/load';
import { Config } from './config/types'; import { Config } from './config/types';
import { lessThan } from './prelude/array'; import { lessThan } from './prelude/array';
@ -50,6 +51,7 @@ function main() {
if (program.daemons) { if (program.daemons) {
serverStats(); serverStats();
notesStats(); notesStats();
queueStats();
} }
} }

View File

@ -5,6 +5,7 @@ import hybridTimeline from './hybrid-timeline';
import globalTimeline from './global-timeline'; import globalTimeline from './global-timeline';
import notesStats from './notes-stats'; import notesStats from './notes-stats';
import serverStats from './server-stats'; import serverStats from './server-stats';
import queueStats from './queue-stats';
import userList from './user-list'; import userList from './user-list';
import messaging from './messaging'; import messaging from './messaging';
import messagingIndex from './messaging-index'; import messagingIndex from './messaging-index';
@ -23,6 +24,7 @@ export default {
globalTimeline, globalTimeline,
notesStats, notesStats,
serverStats, serverStats,
queueStats,
userList, userList,
messaging, messaging,
messagingIndex, messagingIndex,

View File

@ -0,0 +1,41 @@
import autobind from 'autobind-decorator';
import Xev from 'xev';
import Channel from '../channel';
const ev = new Xev();
export default class extends Channel {
public readonly chName = 'queueStats';
public static shouldShare = true;
public static requireCredential = false;
@autobind
public async init(params: any) {
ev.addListener('queueStats', this.onStats);
}
@autobind
private onStats(stats: any) {
this.send('stats', stats);
}
@autobind
public onMessage(type: string, body: any) {
switch (type) {
case 'requestLog':
ev.once(`queueStatsLog:${body.id}`, statsLog => {
this.send('statsLog', statsLog);
});
ev.emit('requestQueueStatsLog', {
id: body.id,
length: body.length
});
break;
}
}
@autobind
public dispose() {
ev.removeListener('queueStats', this.onStats);
}
}