diff --git a/src/client/app/common/views/deck/deck.widgets-column.vue b/src/client/app/common/views/deck/deck.widgets-column.vue
index 47a584a53a..fcf3afd3f7 100644
--- a/src/client/app/common/views/deck/deck.widgets-column.vue
+++ b/src/client/app/common/views/deck/deck.widgets-column.vue
@@ -26,6 +26,7 @@
+
diff --git a/src/client/app/common/views/widgets/index.ts b/src/client/app/common/views/widgets/index.ts
index 05aa08375b..d923a01941 100644
--- a/src/client/app/common/views/widgets/index.ts
+++ b/src/client/app/common/views/widgets/index.ts
@@ -31,3 +31,4 @@ Vue.component('mkw-version', wVersion);
Vue.component('mkw-hashtags', wHashtags);
Vue.component('mkw-instance', wInstance);
Vue.component('mkw-post-form', wPostForm);
+Vue.component('mkw-queue', () => import('./queue.vue').then(m => m.default));
diff --git a/src/client/app/common/views/widgets/queue.vue b/src/client/app/common/views/widgets/queue.vue
new file mode 100644
index 0000000000..18bfeb3ba9
--- /dev/null
+++ b/src/client/app/common/views/widgets/queue.vue
@@ -0,0 +1,157 @@
+
+
+
+ Queue
+
+
+
+
In
+
{{ latestStats.inbox.active | number }} / {{ latestStats.inbox.delayed | number }}
+
+
+
+
Out
+
{{ latestStats.deliver.active | number }} / {{ latestStats.deliver.delayed | number }}
+
+
+
+
+
+
+
+
+
+
diff --git a/src/client/app/desktop/views/home/home.vue b/src/client/app/desktop/views/home/home.vue
index 740aa1289d..fb7af5a9ad 100644
--- a/src/client/app/desktop/views/home/home.vue
+++ b/src/client/app/desktop/views/home/home.vue
@@ -27,6 +27,7 @@
+
diff --git a/src/client/app/mobile/views/pages/widgets.vue b/src/client/app/mobile/views/pages/widgets.vue
index 7722104aff..96dcb977fa 100644
--- a/src/client/app/mobile/views/pages/widgets.vue
+++ b/src/client/app/mobile/views/pages/widgets.vue
@@ -19,6 +19,7 @@
+
diff --git a/src/daemons/queue-stats.ts b/src/daemons/queue-stats.ts
new file mode 100644
index 0000000000..26f2bf7c03
--- /dev/null
+++ b/src/daemons/queue-stats.ts
@@ -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();
+
+ 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);
+}
diff --git a/src/index.ts b/src/index.ts
index 0df38b5966..e55ba5115d 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -16,6 +16,7 @@ import Xev from 'xev';
import Logger from './services/logger';
import serverStats from './daemons/server-stats';
import notesStats from './daemons/notes-stats';
+import queueStats from './daemons/queue-stats';
import loadConfig from './config/load';
import { Config } from './config/types';
import { lessThan } from './prelude/array';
@@ -50,6 +51,7 @@ function main() {
if (program.daemons) {
serverStats();
notesStats();
+ queueStats();
}
}
diff --git a/src/server/api/stream/channels/index.ts b/src/server/api/stream/channels/index.ts
index 02f71b5851..4527fb1e46 100644
--- a/src/server/api/stream/channels/index.ts
+++ b/src/server/api/stream/channels/index.ts
@@ -5,6 +5,7 @@ import hybridTimeline from './hybrid-timeline';
import globalTimeline from './global-timeline';
import notesStats from './notes-stats';
import serverStats from './server-stats';
+import queueStats from './queue-stats';
import userList from './user-list';
import messaging from './messaging';
import messagingIndex from './messaging-index';
@@ -23,6 +24,7 @@ export default {
globalTimeline,
notesStats,
serverStats,
+ queueStats,
userList,
messaging,
messagingIndex,
diff --git a/src/server/api/stream/channels/queue-stats.ts b/src/server/api/stream/channels/queue-stats.ts
new file mode 100644
index 0000000000..0bda0cfcb9
--- /dev/null
+++ b/src/server/api/stream/channels/queue-stats.ts
@@ -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);
+ }
+}