From 969b6dbcad813201f15ac25a2e750748a18bad42 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 21 Oct 2018 09:20:11 +0900 Subject: [PATCH] Resolve #2963 --- .../app/desktop/views/components/charts.vue | 46 +- src/remote/activitypub/models/person.ts | 4 +- src/server/api/endpoints/chart.ts | 33 - src/server/api/endpoints/charts/drive.ts | 33 + src/server/api/endpoints/charts/network.ts | 33 + src/server/api/endpoints/charts/notes.ts | 33 + src/server/api/endpoints/charts/users.ts | 33 + src/server/api/private/signup.ts | 4 +- src/server/index.ts | 4 +- src/services/drive/add-file.ts | 4 +- src/services/drive/delete-file.ts | 4 +- src/services/note/create.ts | 4 +- src/services/note/delete.ts | 4 +- src/services/stats.ts | 765 +++++++++--------- 14 files changed, 584 insertions(+), 420 deletions(-) delete mode 100644 src/server/api/endpoints/chart.ts create mode 100644 src/server/api/endpoints/charts/drive.ts create mode 100644 src/server/api/endpoints/charts/network.ts create mode 100644 src/server/api/endpoints/charts/notes.ts create mode 100644 src/server/api/endpoints/charts/users.ts diff --git a/src/client/app/desktop/views/components/charts.vue b/src/client/app/desktop/views/components/charts.vue index ada024da2d..8a6d89d3f7 100644 --- a/src/client/app/desktop/views/components/charts.vue +++ b/src/client/app/desktop/views/components/charts.vue @@ -112,12 +112,42 @@ export default Vue.extend({ } }, - created() { - (this as any).api('chart', { - limit: 35 - }).then(chart => { - this.chart = chart; - }); + async created() { + const limit = 35; + + const [perHour, perDay] = await Promise.all([Promise.all([ + (this as any).api('charts/users', { limit: limit, span: 'hour' }), + (this as any).api('charts/notes', { limit: limit, span: 'hour' }), + (this as any).api('charts/drive', { limit: limit, span: 'hour' }), + (this as any).api('charts/network', { limit: limit, span: 'hour' }) + ]), Promise.all([ + (this as any).api('charts/users', { limit: limit, span: 'day' }), + (this as any).api('charts/notes', { limit: limit, span: 'day' }), + (this as any).api('charts/drive', { limit: limit, span: 'day' }), + (this as any).api('charts/network', { limit: limit, span: 'day' }) + ])]); + + const chart = { + perHour: [], + perDay: [] + }; + + for (let i = 0; i < limit; i++) { + chart.perHour.push({ + users: perHour[0][i], + notes: perHour[1][i], + drive: perHour[2][i], + network: perHour[3][i] + }); + chart.perDay.push({ + users: perDay[0][i], + notes: perDay[1][i], + drive: perDay[2][i], + network: perDay[3][i] + }); + } + + this.chart = chart; }, methods: { @@ -586,7 +616,7 @@ export default Vue.extend({ borderWidth: 2, pointBackgroundColor: '#fff', lineTension: 0, - data: data.map(x => ({ t: x.date, y: x.incomingRequests })) + data: data.map(x => ({ t: x.date, y: x.incoming })) }] }]; }, @@ -594,7 +624,7 @@ export default Vue.extend({ 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, + time: x.network.incomingRequests != 0 ? (x.network.totalTime / x.network.incomingRequests) : 0, })); return [{ diff --git a/src/remote/activitypub/models/person.ts b/src/remote/activitypub/models/person.ts index 244b62f470..845b11861e 100644 --- a/src/remote/activitypub/models/person.ts +++ b/src/remote/activitypub/models/person.ts @@ -10,7 +10,7 @@ import { isCollectionOrOrderedCollection, isCollection, IPerson } from '../type' import { IDriveFile } from '../../../models/drive-file'; import Meta from '../../../models/meta'; import htmlToMFM from '../../../mfm/html-to-mfm'; -import { coreChart } from '../../../services/stats'; +import { usersChart } from '../../../services/stats'; import { URL } from 'url'; import { resolveNote } from './note'; @@ -180,7 +180,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise new Promise(async (res, rej) => { - const [ps, psErr] = getParams(meta, params); - if (psErr) throw psErr; - - const [statsPerDay, statsPerHour] = await Promise.all([ - coreChart.getStats('day', ps.limit), - coreChart.getStats('hour', ps.limit) - ]); - - res({ - perDay: statsPerDay, - perHour: statsPerHour - }); -}); diff --git a/src/server/api/endpoints/charts/drive.ts b/src/server/api/endpoints/charts/drive.ts new file mode 100644 index 0000000000..0572200d6c --- /dev/null +++ b/src/server/api/endpoints/charts/drive.ts @@ -0,0 +1,33 @@ +import $ from 'cafy'; +import getParams from '../../get-params'; +import { driveChart } from '../../../../services/stats'; + +export const meta = { + desc: { + 'ja-JP': 'ドライブの統計を取得します。' + }, + + params: { + span: $.str.or(['day', 'hour']).note({ + desc: { + 'ja-JP': '集計のスパン' + } + }), + + limit: $.num.optional.range(1, 100).note({ + default: 30, + desc: { + 'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。' + } + }), + } +}; + +export default (params: any) => new Promise(async (res, rej) => { + const [ps, psErr] = getParams(meta, params); + if (psErr) throw psErr; + + const stats = await driveChart.getStats(ps.span as any, ps.limit); + + res(stats); +}); diff --git a/src/server/api/endpoints/charts/network.ts b/src/server/api/endpoints/charts/network.ts new file mode 100644 index 0000000000..236674de7c --- /dev/null +++ b/src/server/api/endpoints/charts/network.ts @@ -0,0 +1,33 @@ +import $ from 'cafy'; +import getParams from '../../get-params'; +import { networkChart } from '../../../../services/stats'; + +export const meta = { + desc: { + 'ja-JP': 'ネットワークの統計を取得します。' + }, + + params: { + span: $.str.or(['day', 'hour']).note({ + desc: { + 'ja-JP': '集計のスパン' + } + }), + + limit: $.num.optional.range(1, 100).note({ + default: 30, + desc: { + 'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。' + } + }), + } +}; + +export default (params: any) => new Promise(async (res, rej) => { + const [ps, psErr] = getParams(meta, params); + if (psErr) throw psErr; + + const stats = await networkChart.getStats(ps.span as any, ps.limit); + + res(stats); +}); diff --git a/src/server/api/endpoints/charts/notes.ts b/src/server/api/endpoints/charts/notes.ts new file mode 100644 index 0000000000..1efd587519 --- /dev/null +++ b/src/server/api/endpoints/charts/notes.ts @@ -0,0 +1,33 @@ +import $ from 'cafy'; +import getParams from '../../get-params'; +import { notesChart } from '../../../../services/stats'; + +export const meta = { + desc: { + 'ja-JP': '投稿の統計を取得します。' + }, + + params: { + span: $.str.or(['day', 'hour']).note({ + desc: { + 'ja-JP': '集計のスパン' + } + }), + + limit: $.num.optional.range(1, 100).note({ + default: 30, + desc: { + 'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。' + } + }), + } +}; + +export default (params: any) => new Promise(async (res, rej) => { + const [ps, psErr] = getParams(meta, params); + if (psErr) throw psErr; + + const stats = await notesChart.getStats(ps.span as any, ps.limit); + + res(stats); +}); diff --git a/src/server/api/endpoints/charts/users.ts b/src/server/api/endpoints/charts/users.ts new file mode 100644 index 0000000000..21f5e03523 --- /dev/null +++ b/src/server/api/endpoints/charts/users.ts @@ -0,0 +1,33 @@ +import $ from 'cafy'; +import getParams from '../../get-params'; +import { usersChart } from '../../../../services/stats'; + +export const meta = { + desc: { + 'ja-JP': 'ユーザーの統計を取得します。' + }, + + params: { + span: $.str.or(['day', 'hour']).note({ + desc: { + 'ja-JP': '集計のスパン' + } + }), + + limit: $.num.optional.range(1, 100).note({ + default: 30, + desc: { + 'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。' + } + }), + } +}; + +export default (params: any) => new Promise(async (res, rej) => { + const [ps, psErr] = getParams(meta, params); + if (psErr) throw psErr; + + const stats = await usersChart.getStats(ps.span as any, ps.limit); + + res(stats); +}); diff --git a/src/server/api/private/signup.ts b/src/server/api/private/signup.ts index 11a599b2b2..99ad9cbccd 100644 --- a/src/server/api/private/signup.ts +++ b/src/server/api/private/signup.ts @@ -7,7 +7,7 @@ import generateUserToken from '../common/generate-native-user-token'; import config from '../../../config'; import Meta from '../../../models/meta'; import RegistrationTicket from '../../../models/registration-tickets'; -import { coreChart } from '../../../services/stats'; +import { usersChart } from '../../../services/stats'; if (config.recaptcha) { recaptcha.init({ @@ -130,7 +130,7 @@ export default async (ctx: Koa.Context) => { }, { upsert: true }); //#endregion - coreChart.updateUserStats(account, true); + usersChart.update(account, true); const res = await pack(account, account, { detail: true, diff --git a/src/server/index.ts b/src/server/index.ts index f547668e90..ae989caff5 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -17,7 +17,7 @@ const requestStats = require('request-stats'); import activityPub from './activitypub'; import webFinger from './webfinger'; import config from '../config'; -import { coreChart } from '../services/stats'; +import { networkChart } from '../services/stats'; import apiServer from './api'; // Init app @@ -104,7 +104,7 @@ export default () => new Promise(resolve => { const outgoingBytes = queue.reduce((a, b) => a + b.res.bytes, 0); queue = []; - coreChart.updateNetworkStats(requests, time, incomingBytes, outgoingBytes); + networkChart.update(requests, time, incomingBytes, outgoingBytes); }, 5000); //#endregion }); diff --git a/src/services/drive/add-file.ts b/src/services/drive/add-file.ts index a50f518c2b..e2f48e0d91 100644 --- a/src/services/drive/add-file.ts +++ b/src/services/drive/add-file.ts @@ -17,7 +17,7 @@ import { isLocalUser, IUser, IRemoteUser } from '../../models/user'; import delFile from './delete-file'; import config from '../../config'; import { getDriveFileThumbnailBucket } from '../../models/drive-file-thumbnail'; -import { coreChart } from '../stats'; +import { driveChart } from '../stats'; const log = debug('misskey:drive:add-file'); @@ -389,7 +389,7 @@ export default async function( }); // 統計を更新 - coreChart.updateDriveStats(driveFile, true); + driveChart.update(driveFile, true); return driveFile; } diff --git a/src/services/drive/delete-file.ts b/src/services/drive/delete-file.ts index 1829705bcd..96083a29e1 100644 --- a/src/services/drive/delete-file.ts +++ b/src/services/drive/delete-file.ts @@ -2,7 +2,7 @@ import * as Minio from 'minio'; import DriveFile, { DriveFileChunk, IDriveFile } from '../../models/drive-file'; import DriveFileThumbnail, { DriveFileThumbnailChunk } from '../../models/drive-file-thumbnail'; import config from '../../config'; -import { coreChart } from '../stats'; +import { driveChart } from '../stats'; export default async function(file: IDriveFile, isExpired = false) { if (file.metadata.storage == 'minio') { @@ -48,5 +48,5 @@ export default async function(file: IDriveFile, isExpired = false) { //#endregion // 統計を更新 - coreChart.updateDriveStats(file, false); + driveChart.update(file, false); } diff --git a/src/services/note/create.ts b/src/services/note/create.ts index 0f844d4e35..a6ccc6cec0 100644 --- a/src/services/note/create.ts +++ b/src/services/note/create.ts @@ -23,7 +23,7 @@ import registerHashtag from '../register-hashtag'; import isQuote from '../../misc/is-quote'; import { TextElementMention } from '../../mfm/parse/elements/mention'; import { TextElementHashtag } from '../../mfm/parse/elements/hashtag'; -import { coreChart } from '../stats'; +import { notesChart } from '../stats'; import { erase, unique } from '../../prelude/array'; import insertNoteUnread from './unread'; @@ -165,7 +165,7 @@ export default async (user: IUser, data: Option, silent = false) => new Promise< } // 統計を更新 - coreChart.updateNoteStats(note, true); + notesChart.update(note, true); // ハッシュタグ登録 tags.map(tag => registerHashtag(user, tag)); diff --git a/src/services/note/delete.ts b/src/services/note/delete.ts index a0a86cad25..4041b28d58 100644 --- a/src/services/note/delete.ts +++ b/src/services/note/delete.ts @@ -6,7 +6,7 @@ import pack from '../../remote/activitypub/renderer'; import { deliver } from '../../queue'; import Following from '../../models/following'; import renderTombstone from '../../remote/activitypub/renderer/tombstone'; -import { coreChart } from '../stats'; +import { notesChart } from '../stats'; import config from '../../config'; import NoteUnread from '../../models/note-unread'; import read from './read'; @@ -63,5 +63,5 @@ export default async function(user: IUser, note: INote) { //#endregion // 統計を更新 - coreChart.updateNoteStats(note, false); + notesChart.update(note, false); } diff --git a/src/services/stats.ts b/src/services/stats.ts index 9e471a02e9..0b4106e28c 100644 --- a/src/services/stats.ts +++ b/src/services/stats.ts @@ -16,6 +16,11 @@ type Span = 'day' | 'hour'; type ChartDocument = { _id: mongo.ObjectID; + /** + * 集計のグループ + */ + group?: any; + /** * 集計日時 */ @@ -40,6 +45,7 @@ abstract class Chart { constructor(dbCollectionName: string) { this.collection = db.get>(dbCollectionName); this.collection.createIndex({ span: -1, date: -1 }, { unique: true }); + this.collection.createIndex('group'); } protected async getCurrentStats(span: Span, group?: Obj): Promise> { @@ -55,10 +61,11 @@ abstract class Chart { null; // 現在(今日または今のHour)の統計 - const currentStats = await this.collection.findOne(Object.assign({}, { + const currentStats = await this.collection.findOne({ + group: group, span: span, date: current - }, group)); + }); if (currentStats) { return currentStats; @@ -69,9 +76,10 @@ abstract class Chart { // * 昨日何もチャートを更新するような出来事がなかった場合は、 // * 統計がそもそも作られずドキュメントが存在しないということがあり得るため、 // * 「昨日の」と決め打ちせずに「もっとも最近の」とします - const mostRecentStats = await this.collection.findOne(Object.assign({}, { + const mostRecentStats = await this.collection.findOne({ + group: group, span: span - }, group), { + }, { sort: { date: -1 } @@ -81,11 +89,12 @@ abstract class Chart { // 現在の統計を初期挿入 const data = this.generateEmptyStats(mostRecentStats.data); - const stats = await this.collection.insert(Object.assign({}, { + const stats = await this.collection.insert({ + group: group, span: span, date: current, data: data - }, group)); + }); return stats; } else { @@ -95,18 +104,19 @@ abstract class Chart { // 空の統計を作成 const data = this.generateInitialStats(); - const stats = await this.collection.insert(Object.assign({}, { + const stats = await this.collection.insert({ + group: group, span: span, date: current, data: data - }, group)); + }); return stats; } } } - protected update(inc: Partial, group?: Obj): void { + protected inc(inc: Partial, group?: Obj): void { const query: Obj = {}; const dive = (path: string, x: Obj) => { @@ -151,12 +161,13 @@ abstract class Chart { span == 'day' ? new Date(y, m, d - range) : span == 'hour' ? new Date(y, m, d, h - range) : null; - const stats = await this.collection.find(Object.assign({ + const stats = await this.collection.find({ + group: group, span: span, date: { $gt: gt } - }, group), { + }, { sort: { date: -1 }, @@ -189,356 +200,82 @@ abstract class Chart { } } -type CoreStats = { - /** - * ユーザーに関する統計 - */ - users: { - local: { - /** - * 集計期間時点での、全ユーザー数 (ローカル) - */ - total: number; +//#region Users stats +/** + * ユーザーに関する統計 + */ +type UsersStats = { + local: { + /** + * 集計期間時点での、全ユーザー数 (ローカル) + */ + total: number; - /** - * 増加したユーザー数 (ローカル) - */ - inc: number; + /** + * 増加したユーザー数 (ローカル) + */ + inc: number; - /** - * 減少したユーザー数 (ローカル) - */ - dec: number; - }; - - remote: { - /** - * 集計期間時点での、全ユーザー数 (リモート) - */ - total: number; - - /** - * 増加したユーザー数 (リモート) - */ - inc: number; - - /** - * 減少したユーザー数 (リモート) - */ - dec: number; - }; + /** + * 減少したユーザー数 (ローカル) + */ + dec: number; }; - /** - * 投稿に関する統計 - */ - notes: { - local: { - /** - * 集計期間時点での、全投稿数 (ローカル) - */ - total: number; - - /** - * 増加した投稿数 (ローカル) - */ - inc: number; - - /** - * 減少した投稿数 (ローカル) - */ - dec: number; - - diffs: { - /** - * 通常の投稿数の差分 (ローカル) - */ - normal: number; - - /** - * リプライの投稿数の差分 (ローカル) - */ - reply: number; - - /** - * Renoteの投稿数の差分 (ローカル) - */ - renote: number; - }; - }; - - remote: { - /** - * 集計期間時点での、全投稿数 (リモート) - */ - total: number; - - /** - * 増加した投稿数 (リモート) - */ - inc: number; - - /** - * 減少した投稿数 (リモート) - */ - dec: number; - - diffs: { - /** - * 通常の投稿数の差分 (リモート) - */ - normal: number; - - /** - * リプライの投稿数の差分 (リモート) - */ - reply: number; - - /** - * Renoteの投稿数の差分 (リモート) - */ - renote: number; - }; - }; - }; - - /** - * ドライブ(のファイル)に関する統計 - */ - drive: { - local: { - /** - * 集計期間時点での、全ドライブファイル数 (ローカル) - */ - totalCount: number; - - /** - * 集計期間時点での、全ドライブファイルの合計サイズ (ローカル) - */ - totalSize: number; - - /** - * 増加したドライブファイル数 (ローカル) - */ - incCount: number; - - /** - * 増加したドライブ使用量 (ローカル) - */ - incSize: number; - - /** - * 減少したドライブファイル数 (ローカル) - */ - decCount: number; - - /** - * 減少したドライブ使用量 (ローカル) - */ - decSize: number; - }; - - remote: { - /** - * 集計期間時点での、全ドライブファイル数 (リモート) - */ - totalCount: number; - - /** - * 集計期間時点での、全ドライブファイルの合計サイズ (リモート) - */ - totalSize: number; - - /** - * 増加したドライブファイル数 (リモート) - */ - incCount: number; - - /** - * 増加したドライブ使用量 (リモート) - */ - incSize: number; - - /** - * 減少したドライブファイル数 (リモート) - */ - decCount: number; - - /** - * 減少したドライブ使用量 (リモート) - */ - decSize: number; - }; - }; - - /** - * ネットワークに関する統計 - */ - network: { + remote: { /** - * 受信したリクエスト数 + * 集計期間時点での、全ユーザー数 (リモート) */ - incomingRequests: number; + total: number; /** - * 送信したリクエスト数 + * 増加したユーザー数 (リモート) */ - outgoingRequests: number; + inc: number; /** - * 応答時間の合計 - * TIP: (totalTime / incomingRequests) でひとつのリクエストに平均でどれくらいの時間がかかったか知れる + * 減少したユーザー数 (リモート) */ - totalTime: number; - - /** - * 合計受信データ量 - */ - incomingBytes: number; - - /** - * 合計送信データ量 - */ - outgoingBytes: number; + dec: number; }; }; -class CoreChart extends Chart { +class UsersChart extends Chart { constructor() { - super('coreStats'); + super('usersStats'); } - protected generateInitialStats(): CoreStats { + protected generateInitialStats(): UsersStats { return { - users: { - local: { - total: 0, - inc: 0, - dec: 0 - }, - remote: { - total: 0, - inc: 0, - dec: 0 - } + local: { + total: 0, + inc: 0, + dec: 0 }, - notes: { - local: { - total: 0, - inc: 0, - dec: 0, - diffs: { - normal: 0, - reply: 0, - renote: 0 - } - }, - remote: { - total: 0, - inc: 0, - dec: 0, - diffs: { - normal: 0, - reply: 0, - renote: 0 - } - } - }, - drive: { - local: { - totalCount: 0, - totalSize: 0, - incCount: 0, - incSize: 0, - decCount: 0, - decSize: 0 - }, - remote: { - totalCount: 0, - totalSize: 0, - incCount: 0, - incSize: 0, - decCount: 0, - decSize: 0 - } - }, - network: { - incomingRequests: 0, - outgoingRequests: 0, - totalTime: 0, - incomingBytes: 0, - outgoingBytes: 0 + remote: { + total: 0, + inc: 0, + dec: 0 } }; } - protected generateEmptyStats(mostRecentStats: CoreStats): CoreStats { + protected generateEmptyStats(mostRecentStats: UsersStats): UsersStats { return { - users: { - local: { - total: mostRecentStats.users.local.total, - inc: 0, - dec: 0 - }, - remote: { - total: mostRecentStats.users.remote.total, - inc: 0, - dec: 0 - } + local: { + total: mostRecentStats.local.total, + inc: 0, + dec: 0 }, - notes: { - local: { - total: mostRecentStats.notes.local.total, - inc: 0, - dec: 0, - diffs: { - normal: 0, - reply: 0, - renote: 0 - } - }, - remote: { - total: mostRecentStats.notes.remote.total, - inc: 0, - dec: 0, - diffs: { - normal: 0, - reply: 0, - renote: 0 - } - } - }, - drive: { - local: { - totalCount: mostRecentStats.drive.local.totalCount, - totalSize: mostRecentStats.drive.local.totalSize, - incCount: 0, - incSize: 0, - decCount: 0, - decSize: 0 - }, - remote: { - totalCount: mostRecentStats.drive.remote.totalCount, - totalSize: mostRecentStats.drive.remote.totalSize, - incCount: 0, - incSize: 0, - decCount: 0, - decSize: 0 - } - }, - network: { - incomingRequests: 0, - outgoingRequests: 0, - totalTime: 0, - incomingBytes: 0, - outgoingBytes: 0 + remote: { + total: mostRecentStats.remote.total, + inc: 0, + dec: 0 } }; } - public async updateUserStats(user: IUser, isAdditional: boolean) { - const origin = isLocalUser(user) ? 'local' : 'remote'; - + public async update(user: IUser, isAdditional: boolean) { const update: Obj = {}; update.total = isAdditional ? 1 : -1; @@ -548,18 +285,145 @@ class CoreChart extends Chart { update.dec = 1; } - const inc: Obj = { - users: {} + await this.inc({ + [isLocalUser(user) ? 'local' : 'remote']: update + }); + } +} + +export const usersChart = new UsersChart(); +//#endregion + +//#region Notes stats +/** + * 投稿に関する統計 + */ +type NotesStats = { + local: { + /** + * 集計期間時点での、全投稿数 (ローカル) + */ + total: number; + + /** + * 増加した投稿数 (ローカル) + */ + inc: number; + + /** + * 減少した投稿数 (ローカル) + */ + dec: number; + + diffs: { + /** + * 通常の投稿数の差分 (ローカル) + */ + normal: number; + + /** + * リプライの投稿数の差分 (ローカル) + */ + reply: number; + + /** + * Renoteの投稿数の差分 (ローカル) + */ + renote: number; }; + }; - inc.users[origin] = update; + remote: { + /** + * 集計期間時点での、全投稿数 (リモート) + */ + total: number; - await this.update(inc); + /** + * 増加した投稿数 (リモート) + */ + inc: number; + + /** + * 減少した投稿数 (リモート) + */ + dec: number; + + diffs: { + /** + * 通常の投稿数の差分 (リモート) + */ + normal: number; + + /** + * リプライの投稿数の差分 (リモート) + */ + reply: number; + + /** + * Renoteの投稿数の差分 (リモート) + */ + renote: number; + }; + }; +}; + +class NotesChart extends Chart { + constructor() { + super('notesStats'); } - public async updateNoteStats(note: INote, isAdditional: boolean) { - const origin = isLocalUser(note._user) ? 'local' : 'remote'; + protected generateInitialStats(): NotesStats { + return { + local: { + total: 0, + inc: 0, + dec: 0, + diffs: { + normal: 0, + reply: 0, + renote: 0 + } + }, + remote: { + total: 0, + inc: 0, + dec: 0, + diffs: { + normal: 0, + reply: 0, + renote: 0 + } + } + }; + } + protected generateEmptyStats(mostRecentStats: NotesStats): NotesStats { + return { + local: { + total: mostRecentStats.local.total, + inc: 0, + dec: 0, + diffs: { + normal: 0, + reply: 0, + renote: 0 + } + }, + remote: { + total: mostRecentStats.remote.total, + inc: 0, + dec: 0, + diffs: { + normal: 0, + reply: 0, + renote: 0 + } + } + }; + } + + public async update(note: INote, isAdditional: boolean) { const update: Obj = {}; update.total = isAdditional ? 1 : -1; @@ -578,18 +442,133 @@ class CoreChart extends Chart { update.diffs.normal = isAdditional ? 1 : -1; } - const inc: Obj = { - notes: {} - }; + await this.inc({ + [isLocalUser(note._user) ? 'local' : 'remote']: update + }); + } +} - inc.notes[origin] = update; +export const notesChart = new NotesChart(); +//#endregion - await this.update(inc); +//#region Drive stats +/** + * ドライブに関する統計 + */ +type DriveStats = { + local: { + /** + * 集計期間時点での、全ドライブファイル数 (ローカル) + */ + totalCount: number; + + /** + * 集計期間時点での、全ドライブファイルの合計サイズ (ローカル) + */ + totalSize: number; + + /** + * 増加したドライブファイル数 (ローカル) + */ + incCount: number; + + /** + * 増加したドライブ使用量 (ローカル) + */ + incSize: number; + + /** + * 減少したドライブファイル数 (ローカル) + */ + decCount: number; + + /** + * 減少したドライブ使用量 (ローカル) + */ + decSize: number; + }; + + remote: { + /** + * 集計期間時点での、全ドライブファイル数 (リモート) + */ + totalCount: number; + + /** + * 集計期間時点での、全ドライブファイルの合計サイズ (リモート) + */ + totalSize: number; + + /** + * 増加したドライブファイル数 (リモート) + */ + incCount: number; + + /** + * 増加したドライブ使用量 (リモート) + */ + incSize: number; + + /** + * 減少したドライブファイル数 (リモート) + */ + decCount: number; + + /** + * 減少したドライブ使用量 (リモート) + */ + decSize: number; + }; +}; + +class DriveChart extends Chart { + constructor() { + super('driveStats'); } - public async updateDriveStats(file: IDriveFile, isAdditional: boolean) { - const origin = isLocalUser(file.metadata._user) ? 'local' : 'remote'; + protected generateInitialStats(): DriveStats { + return { + local: { + totalCount: 0, + totalSize: 0, + incCount: 0, + incSize: 0, + decCount: 0, + decSize: 0 + }, + remote: { + totalCount: 0, + totalSize: 0, + incCount: 0, + incSize: 0, + decCount: 0, + decSize: 0 + } + }; + } + protected generateEmptyStats(mostRecentStats: DriveStats): DriveStats { + return { + local: { + totalCount: mostRecentStats.local.totalCount, + totalSize: mostRecentStats.local.totalSize, + incCount: 0, + incSize: 0, + decCount: 0, + decSize: 0 + }, + remote: { + totalCount: mostRecentStats.remote.totalCount, + totalSize: mostRecentStats.remote.totalSize, + incCount: 0, + incSize: 0, + decCount: 0, + decSize: 0 + } + }; + } + + public async update(file: IDriveFile, isAdditional: boolean) { const update: Obj = {}; update.totalCount = isAdditional ? 1 : -1; @@ -602,27 +581,83 @@ class CoreChart extends Chart { update.decSize = file.length; } - const inc: Obj = { - drive: {} - }; - - inc.drive[origin] = update; - - await this.update(inc); - } - - public async updateNetworkStats(incomingRequests: number, time: number, incomingBytes: number, outgoingBytes: number) { - const inc: Partial = { - network: { - incomingRequests: incomingRequests, - totalTime: time, - incomingBytes: incomingBytes, - outgoingBytes: outgoingBytes - } - }; - - await this.update(inc); + await this.inc({ + [isLocalUser(file.metadata._user) ? 'local' : 'remote']: update + }); } } -export const coreChart = new CoreChart(); +export const driveChart = new DriveChart(); +//#endregion + +//#region Network stats +/** + * ネットワークに関する統計 + */ +type NetworkStats = { + /** + * 受信したリクエスト数 + */ + incomingRequests: number; + + /** + * 送信したリクエスト数 + */ + outgoingRequests: number; + + /** + * 応答時間の合計 + * TIP: (totalTime / incomingRequests) でひとつのリクエストに平均でどれくらいの時間がかかったか知れる + */ + totalTime: number; + + /** + * 合計受信データ量 + */ + incomingBytes: number; + + /** + * 合計送信データ量 + */ + outgoingBytes: number; +}; + +class NetworkChart extends Chart { + constructor() { + super('networkStats'); + } + + protected generateInitialStats(): NetworkStats { + return { + incomingRequests: 0, + outgoingRequests: 0, + totalTime: 0, + incomingBytes: 0, + outgoingBytes: 0 + }; + } + + protected generateEmptyStats(mostRecentStats: NetworkStats): NetworkStats { + return { + incomingRequests: 0, + outgoingRequests: 0, + totalTime: 0, + incomingBytes: 0, + outgoingBytes: 0 + }; + } + + public async update(incomingRequests: number, time: number, incomingBytes: number, outgoingBytes: number) { + const inc: Partial = { + incomingRequests: incomingRequests, + totalTime: time, + incomingBytes: incomingBytes, + outgoingBytes: outgoingBytes + }; + + await this.inc(inc); + } +} + +export const networkChart = new NetworkChart(); +//#endregion