diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 5257d616c..72dc88b7b 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -518,6 +518,7 @@ desktop/views/components/charts.vue: notes: "投稿" users: "ユーザー" drive: "ドライブ" + network: "ネットワーク" charts: notes: "投稿の増減 (統合)" local-notes: "投稿の増減 (ローカル)" @@ -529,6 +530,9 @@ desktop/views/components/charts.vue: drive-total: "ドライブ使用量の累計" drive-files: "ドライブのファイル数の増減" drive-files-total: "ドライブのファイル数の累計" + network-requests: "リクエスト" + network-time: "応答時間" + network-usage: "通信量" desktop/views/components/choose-file-from-drive-window.vue: choose-file: "ファイル選択中" diff --git a/package.json b/package.json index 4d2a4b285..4b9eaf9e2 100644 --- a/package.json +++ b/package.json @@ -179,6 +179,7 @@ "redis": "2.8.0", "request": "2.88.0", "request-promise-native": "1.0.5", + "request-stats": "3.0.0", "rimraf": "2.6.2", "rndstr": "1.0.0", "s-age": "1.1.2", diff --git a/src/client/app/desktop/views/components/charts.vue b/src/client/app/desktop/views/components/charts.vue index c4e92e429..6514cdf78 100644 --- a/src/client/app/desktop/views/components/charts.vue +++ b/src/client/app/desktop/views/components/charts.vue @@ -19,6 +19,11 @@ + + + + +
%i18n:@per-day% | %i18n:@per-hour% @@ -41,7 +46,10 @@ const colors = { localPlus: 'rgb(52, 178, 118)', remotePlus: 'rgb(158, 255, 209)', localMinus: 'rgb(255, 97, 74)', - remoteMinus: 'rgb(255, 149, 134)' + remoteMinus: 'rgb(255, 149, 134)', + + incoming: 'rgb(52, 178, 118)', + outgoing: 'rgb(255, 97, 74)', }; const rgba = (color: string): string => { @@ -75,6 +83,9 @@ export default Vue.extend({ case 'drive-total': return this.driveTotalChart(); case 'drive-files': return this.driveFilesChart(); case 'drive-files-total': return this.driveFilesTotalChart(); + case 'network-requests': return this.networkRequestsChart(); + case 'network-time': return this.networkTimeChart(); + case 'network-usage': return this.networkUsageChart(); } }, @@ -544,7 +555,95 @@ export default Vue.extend({ } } }]; - } + }, + + networkRequestsChart(): any { + const data = this.stats.slice().reverse().map(x => ({ + date: new Date(x.date), + requests: x.network.requests + })); + + return [{ + datasets: [{ + label: 'Requests', + fill: true, + backgroundColor: rgba(colors.localPlus), + borderColor: colors.localPlus, + borderWidth: 2, + pointBackgroundColor: '#fff', + lineTension: 0, + data: data.map(x => ({ t: x.date, y: x.requests })) + }] + }]; + }, + + networkTimeChart(): any { + const data = this.stats.slice().reverse().map(x => ({ + date: new Date(x.date), + time: x.network.requests != 0 ? (x.network.totalTime / x.network.requests) : 0, + })); + + return [{ + datasets: [{ + label: 'Avg time (ms)', + fill: true, + backgroundColor: rgba(colors.localPlus), + borderColor: colors.localPlus, + borderWidth: 2, + pointBackgroundColor: '#fff', + lineTension: 0, + data: data.map(x => ({ t: x.date, y: x.time })) + }] + }]; + }, + + networkUsageChart(): any { + const data = this.stats.slice().reverse().map(x => ({ + date: new Date(x.date), + incoming: x.network.incomingBytes, + outgoing: x.network.outgoingBytes + })); + + return [{ + datasets: [{ + label: 'Incoming', + fill: true, + backgroundColor: rgba(colors.incoming), + borderColor: colors.incoming, + borderWidth: 2, + pointBackgroundColor: '#fff', + lineTension: 0, + data: data.map(x => ({ t: x.date, y: x.incoming })) + }, { + label: 'Outgoing', + fill: true, + backgroundColor: rgba(colors.outgoing), + borderColor: colors.outgoing, + borderWidth: 2, + pointBackgroundColor: '#fff', + lineTension: 0, + data: data.map(x => ({ t: x.date, y: x.outgoing })) + }] + }, { + scales: { + yAxes: [{ + ticks: { + callback: value => { + return Vue.filter('bytes')(value, 1); + } + } + }] + }, + tooltips: { + callbacks: { + label: (tooltipItem, data) => { + const label = data.datasets[tooltipItem.datasetIndex].label || ''; + return `${label}: ${Vue.filter('bytes')(tooltipItem.yLabel, 1)}`; + } + } + } + }]; + }, } }); diff --git a/src/models/stats.ts b/src/models/stats.ts index d496f2c48..c4c838cae 100644 --- a/src/models/stats.ts +++ b/src/models/stats.ts @@ -204,4 +204,30 @@ export interface IStats { decSize: number; }; }; + + /** + * ネットワークに関する統計 + */ + network: { + /** + * サーバーへのリクエスト数 + */ + requests: number; + + /** + * 応答時間の合計 + * TIP: (totalTime / requests) でひとつのリクエストに平均でどれくらいの時間がかかったか知れる + */ + totalTime: number; + + /** + * 合計受信データ量 + */ + incomingBytes: number; + + /** + * 合計送信データ量 + */ + outgoingBytes: number; + }; } diff --git a/src/server/api/endpoints/chart.ts b/src/server/api/endpoints/chart.ts index 7da970131..3b1a3b56f 100644 --- a/src/server/api/endpoints/chart.ts +++ b/src/server/api/endpoints/chart.ts @@ -6,6 +6,15 @@ type Omit = Pick>; function migrateStats(stats: IStats[]) { stats.forEach(stat => { + if (stat.network == null) { + stat.network = { + requests: 0, + totalTime: 0, + incomingBytes: 0, + outgoingBytes: 0 + }; + } + const isOldData = stat.users.local.inc == null || stat.users.local.dec == null || @@ -180,6 +189,12 @@ export default (params: any) => new Promise(async (res, rej) => { decCount: 0, decSize: 0 } + }, + network: { + requests: 0, + totalTime: 0, + incomingBytes: 0, + outgoingBytes: 0 } }); } else { @@ -236,6 +251,12 @@ export default (params: any) => new Promise(async (res, rej) => { decCount: 0, decSize: 0 } + }, + network: { + requests: 0, + totalTime: 0, + incomingBytes: 0, + outgoingBytes: 0 } }); } diff --git a/src/server/index.ts b/src/server/index.ts index f1fcf58c8..dc60b0d9e 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -11,11 +11,13 @@ import * as Router from 'koa-router'; import * as mount from 'koa-mount'; import * as compress from 'koa-compress'; import * as logger from 'koa-logger'; +const requestStats = require('request-stats'); //const slow = require('koa-slow'); import activityPub from './activitypub'; import webFinger from './webfinger'; import config from '../config'; +import { updateNetworkStats } from '../services/update-chart'; // Init app const app = new Koa(); @@ -81,4 +83,27 @@ export default () => new Promise(resolve => { // Listen server.listen(config.port, resolve); + + //#region Network stats + let queue: any[] = []; + + requestStats(server, (stats: any) => { + if (stats.ok) { + queue.push(stats); + } + }); + + // Bulk write + setInterval(() => { + if (queue.length == 0) return; + + const requests = queue.length; + const time = queue.reduce((a, b) => a + b.time, 0); + const incomingBytes = queue.reduce((a, b) => a + b.req.bytes, 0); + const outgoingBytes = queue.reduce((a, b) => a + b.res.bytes, 0); + queue = []; + + updateNetworkStats(requests, time, incomingBytes, outgoingBytes); + }, 5000); + //#endregion }); diff --git a/src/services/update-chart.ts b/src/services/update-chart.ts index 1f8da6be9..78834ba60 100644 --- a/src/services/update-chart.ts +++ b/src/services/update-chart.ts @@ -96,6 +96,12 @@ async function getCurrentStats(span: 'day' | 'hour'): Promise { decCount: 0, decSize: 0 } + }, + network: { + requests: 0, + totalTime: 0, + incomingBytes: 0, + outgoingBytes: 0 } }; @@ -161,6 +167,12 @@ async function getCurrentStats(span: 'day' | 'hour'): Promise { decCount: 0, decSize: 0 } + }, + network: { + requests: 0, + totalTime: 0, + incomingBytes: 0, + outgoingBytes: 0 } }; @@ -243,3 +255,13 @@ export async function updateDriveStats(file: IDriveFile, isAdditional: boolean) await update(inc); } + +export async function updateNetworkStats(requests: number, time: number, incomingBytes: number, outgoingBytes: number) { + const inc = {} as any; + inc['network.requests'] = requests; + inc['network.totalTime'] = time; + inc['network.incomingBytes'] = incomingBytes; + inc['network.outgoingBytes'] = outgoingBytes; + + await update(inc); +}