diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2019-02-08 16:58:57 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-02-08 16:58:57 +0900 |
| commit | 56275bcfcbd1366b0e96b79915e810baed0548bb (patch) | |
| tree | b35c8a6bd15ef8046c00119f0271808970f4f939 /src | |
| parent | Supress logs during test (diff) | |
| download | sharkey-56275bcfcbd1366b0e96b79915e810baed0548bb.tar.gz sharkey-56275bcfcbd1366b0e96b79915e810baed0548bb.tar.bz2 sharkey-56275bcfcbd1366b0e96b79915e810baed0548bb.zip | |
Introduce per-instance chart (#4183)
* Introduce per-instance chart
* Implement chart view in client
* Handle note deleting
* More chart srcs
* Add drive stats
* Improve drive stats
* Fix bug
* Add icon
Diffstat (limited to 'src')
| -rw-r--r-- | src/client/app/admin/views/federation.vue | 275 | ||||
| -rw-r--r-- | src/models/drive-file.ts | 1 | ||||
| -rw-r--r-- | src/models/instance.ts | 10 | ||||
| -rw-r--r-- | src/models/user.ts | 1 | ||||
| -rw-r--r-- | src/queue/processors/http/deliver.ts | 5 | ||||
| -rw-r--r-- | src/queue/processors/http/process-inbox.ts | 3 | ||||
| -rw-r--r-- | src/remote/activitypub/models/person.ts | 4 | ||||
| -rw-r--r-- | src/server/api/endpoints/charts/instance.ts | 42 | ||||
| -rw-r--r-- | src/server/api/endpoints/federation/instances.ts | 16 | ||||
| -rw-r--r-- | src/services/chart/instance.ts | 302 | ||||
| -rw-r--r-- | src/services/drive/add-file.ts | 13 | ||||
| -rw-r--r-- | src/services/drive/delete-file.ts | 12 | ||||
| -rw-r--r-- | src/services/following/create.ts | 7 | ||||
| -rw-r--r-- | src/services/following/delete.ts | 27 | ||||
| -rw-r--r-- | src/services/note/create.ts | 4 | ||||
| -rw-r--r-- | src/services/note/delete.ts | 17 |
16 files changed, 726 insertions, 13 deletions
diff --git a/src/client/app/admin/views/federation.vue b/src/client/app/admin/views/federation.vue index dd8567243a..8b0e9ba45a 100644 --- a/src/client/app/admin/views/federation.vue +++ b/src/client/app/admin/views/federation.vue @@ -41,6 +41,29 @@ </ui-input> <ui-switch v-model="instance.isBlocked" @change="updateInstance()">{{ $t('block') }}</ui-switch> <details> + <summary>{{ $t('charts') }}</summary> + <ui-horizon-group inputs> + <ui-select v-model="chartSrc"> + <option value="requests">{{ $t('chart-srcs.requests') }}</option> + <option value="users">{{ $t('chart-srcs.users') }}</option> + <option value="users-total">{{ $t('chart-srcs.users-total') }}</option> + <option value="notes">{{ $t('chart-srcs.notes') }}</option> + <option value="notes-total">{{ $t('chart-srcs.notes-total') }}</option> + <option value="ff">{{ $t('chart-srcs.ff') }}</option> + <option value="ff-total">{{ $t('chart-srcs.ff-total') }}</option> + <option value="drive-usage">{{ $t('chart-srcs.drive-usage') }}</option> + <option value="drive-usage-total">{{ $t('chart-srcs.drive-usage-total') }}</option> + <option value="drive-files">{{ $t('chart-srcs.drive-files') }}</option> + <option value="drive-files-total">{{ $t('chart-srcs.drive-files-total') }}</option> + </ui-select> + <ui-select v-model="chartSpan"> + <option value="hour">{{ $t('chart-spans.hour') }}</option> + <option value="day">{{ $t('chart-spans.day') }}</option> + </ui-select> + </ui-horizon-group> + <div ref="chart"></div> + </details> + <details> <summary>{{ $t('remove-all-following') }}</summary> <ui-button @click="removeAllFollowing()" style="margin-top: 16px;"><fa :icon="faMinusCircle"/> {{ $t('remove-all-following') }}</ui-button> <ui-info warn>{{ $t('remove-all-following-info', { host: instance.host }) }}</ui-info> @@ -50,7 +73,7 @@ </ui-card> <ui-card> - <div slot="title"><fa :icon="faUsers"/> {{ $t('instances') }}</div> + <div slot="title"><fa :icon="faServer"/> {{ $t('instances') }}</div> <section class="fit-top"> <ui-horizon-group inputs> <ui-select v-model="sort"> @@ -65,6 +88,10 @@ <option value="+following">{{ $t('sorts.followingDesc') }}</option> <option value="-followers">{{ $t('sorts.followersAsc') }}</option> <option value="+followers">{{ $t('sorts.followersDesc') }}</option> + <option value="-driveUsage">{{ $t('sorts.driveUsageAsc') }}</option> + <option value="+driveUsage">{{ $t('sorts.driveUsageDesc') }}</option> + <option value="-driveFiles">{{ $t('sorts.driveFilesAsc') }}</option> + <option value="+driveFiles">{{ $t('sorts.driveFilesDesc') }}</option> </ui-select> <ui-select v-model="state"> <span slot="label">{{ $t('state') }}</span> @@ -101,7 +128,13 @@ <script lang="ts"> import Vue from 'vue'; import i18n from '../../i18n'; -import { faGlobe, faTerminal, faSearch, faMinusCircle } from '@fortawesome/free-solid-svg-icons'; +import { faGlobe, faTerminal, faSearch, faMinusCircle, faServer } from '@fortawesome/free-solid-svg-icons'; +import ApexCharts from 'apexcharts'; +import * as tinycolor from 'tinycolor2'; + +const chartLimit = 90; +const sum = (...arr) => arr.reduce((r, a) => r.map((b, i) => a[i] + b)); +const negate = arr => arr.map(x => -x); export default Vue.extend({ i18n: i18n('admin/views/federation.vue'), @@ -114,10 +147,42 @@ export default Vue.extend({ state: 'all', limit: 50, instances: [], - faGlobe, faTerminal, faSearch, faMinusCircle + chart: null, + chartSrc: 'requests', + chartSpan: 'hour', + chartInstance: null, + faGlobe, faTerminal, faSearch, faMinusCircle, faServer }; }, + computed: { + data(): any { + if (this.chart == null) return null; + switch (this.chartSrc) { + case 'requests': return this.requestsChart(); + case 'users': return this.usersChart(false); + case 'users-total': return this.usersChart(true); + case 'notes': return this.notesChart(false); + case 'notes-total': return this.notesChart(true); + case 'ff': return this.ffChart(false); + case 'ff-total': return this.ffChart(true); + case 'drive-usage': return this.driveUsageChart(false); + case 'drive-usage-total': return this.driveUsageChart(true); + case 'drive-files': return this.driveFilesChart(false); + case 'drive-files-total': return this.driveFilesChart(true); + } + }, + + stats(): any[] { + const stats = + this.chartSpan == 'day' ? this.chart.perDay : + this.chartSpan == 'hour' ? this.chart.perHour : + null; + + return stats; + } + }, + watch: { sort() { this.fetchInstances(); @@ -126,12 +191,42 @@ export default Vue.extend({ state() { this.fetchInstances(); }, + + async instance() { + this.now = new Date(); + + const [perHour, perDay] = await Promise.all([ + this.$root.api('charts/instance', { host: this.instance.host, limit: chartLimit, span: 'hour' }), + this.$root.api('charts/instance', { host: this.instance.host, limit: chartLimit, span: 'day' }), + ]); + + const chart = { + perHour: perHour, + perDay: perDay + }; + + this.chart = chart; + + this.renderChart(); + }, + + chartSrc() { + this.renderChart(); + }, + + chartSpan() { + this.renderChart(); + } }, mounted() { this.fetchInstances(); }, + beforeDestroy() { + this.chartInstance.destroy(); + }, + methods: { showInstance() { this.$root.api('federation/show-instance', { @@ -177,6 +272,180 @@ export default Vue.extend({ isBlocked: this.instance.isBlocked, }); }, + + setSrc(src) { + this.chartSrc = src; + }, + + renderChart() { + if (this.chartInstance) { + this.chartInstance.destroy(); + } + + this.chartInstance = new ApexCharts(this.$refs.chart, { + chart: { + type: 'area', + height: 300, + animations: { + dynamicAnimation: { + enabled: false + } + }, + toolbar: { + show: false + }, + zoom: { + enabled: false + } + }, + dataLabels: { + enabled: false + }, + grid: { + clipMarkers: false, + borderColor: 'rgba(0, 0, 0, 0.1)' + }, + stroke: { + curve: 'straight', + width: 2 + }, + legend: { + labels: { + colors: tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--text')).toRgbString() + }, + }, + xaxis: { + type: 'datetime', + labels: { + style: { + colors: tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--text')).toRgbString() + } + }, + axisBorder: { + color: 'rgba(0, 0, 0, 0.1)' + }, + axisTicks: { + color: 'rgba(0, 0, 0, 0.1)' + }, + }, + yaxis: { + labels: { + formatter: this.data.bytes ? v => Vue.filter('bytes')(v, 0) : v => Vue.filter('number')(v), + style: { + color: tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--text')).toRgbString() + } + } + }, + series: this.data.series + }); + + this.chartInstance.render(); + }, + + getDate(i: number) { + const y = this.now.getFullYear(); + const m = this.now.getMonth(); + const d = this.now.getDate(); + const h = this.now.getHours(); + + return ( + this.chartSpan == 'day' ? new Date(y, m, d - i) : + this.chartSpan == 'hour' ? new Date(y, m, d, h - i) : + null + ); + }, + + format(arr) { + return arr.map((v, i) => ({ x: this.getDate(i).getTime(), y: v })); + }, + + requestsChart(): any { + return { + series: [{ + name: 'Incoming', + data: this.format(this.stats.requests.received) + }, { + name: 'Outgoing (succeeded)', + data: this.format(this.stats.requests.succeeded) + }, { + name: 'Outgoing (failed)', + data: this.format(this.stats.requests.failed) + }] + }; + }, + + usersChart(total: boolean): any { + return { + series: [{ + name: 'Users', + type: 'area', + data: this.format(total + ? this.stats.users.total + : sum(this.stats.users.inc, negate(this.stats.users.dec)) + ) + }] + }; + }, + + notesChart(total: boolean): any { + return { + series: [{ + name: 'Notes', + type: 'area', + data: this.format(total + ? this.stats.notes.total + : sum(this.stats.notes.inc, negate(this.stats.notes.dec)) + ) + }] + }; + }, + + ffChart(total: boolean): any { + return { + series: [{ + name: 'Following', + type: 'area', + data: this.format(total + ? this.stats.following.total + : sum(this.stats.following.inc, negate(this.stats.following.dec)) + ) + }, { + name: 'Followers', + type: 'area', + data: this.format(total + ? this.stats.followers.total + : sum(this.stats.followers.inc, negate(this.stats.followers.dec)) + ) + }] + }; + }, + + driveUsageChart(total: boolean): any { + return { + bytes: true, + series: [{ + name: 'Drive usage', + type: 'area', + data: this.format(total + ? this.stats.drive.totalUsage + : sum(this.stats.drive.incUsage, negate(this.stats.drive.decUsage)) + ) + }] + }; + }, + + driveFilesChart(total: boolean): any { + return { + series: [{ + name: 'Drive files', + type: 'area', + data: this.format(total + ? this.stats.drive.totalFiles + : sum(this.stats.drive.incFiles, negate(this.stats.drive.decFiles)) + ) + }] + }; + }, } }); </script> diff --git a/src/models/drive-file.ts b/src/models/drive-file.ts index 62a544c214..f3e21f209b 100644 --- a/src/models/drive-file.ts +++ b/src/models/drive-file.ts @@ -11,6 +11,7 @@ DriveFile.createIndex('md5'); DriveFile.createIndex('metadata.uri'); DriveFile.createIndex('metadata.userId'); DriveFile.createIndex('metadata.folderId'); +DriveFile.createIndex('metadata._user.host'); export default DriveFile; export const DriveFileChunk = monkDb.get('driveFiles.chunks'); diff --git a/src/models/instance.ts b/src/models/instance.ts index 242e80f300..985564f8de 100644 --- a/src/models/instance.ts +++ b/src/models/instance.ts @@ -44,6 +44,16 @@ export interface IInstance { followersCount: number; /** + * ドライブ使用量 + */ + driveUsage: number; + + /** + * ドライブのファイル数 + */ + driveFiles: number; + + /** * 直近のリクエスト送信日時 */ latestRequestSentAt?: Date; diff --git a/src/models/user.ts b/src/models/user.ts index 2453a2ed15..ce0d17a04e 100644 --- a/src/models/user.ts +++ b/src/models/user.ts @@ -17,6 +17,7 @@ const User = db.get<IUser>('users'); User.createIndex('username'); User.createIndex('usernameLower'); +User.createIndex('host'); User.createIndex(['username', 'host'], { unique: true }); User.createIndex(['usernameLower', 'host'], { unique: true }); User.createIndex('token', { sparse: true, unique: true }); diff --git a/src/queue/processors/http/deliver.ts b/src/queue/processors/http/deliver.ts index 82dcf06ad4..6d24cd2634 100644 --- a/src/queue/processors/http/deliver.ts +++ b/src/queue/processors/http/deliver.ts @@ -4,6 +4,7 @@ import request from '../../../remote/activitypub/request'; import { queueLogger } from '../../logger'; import { registerOrFetchInstanceDoc } from '../../../services/register-or-fetch-instance-doc'; import Instance from '../../../models/instance'; +import instanceChart from '../../../services/chart/instance'; export default async (job: bq.Job, done: any): Promise<void> => { const { host } = new URL(job.data.to); @@ -19,6 +20,8 @@ export default async (job: bq.Job, done: any): Promise<void> => { latestStatus: 200 } }); + + instanceChart.requestSent(i.host, true); }); done(); @@ -31,6 +34,8 @@ export default async (job: bq.Job, done: any): Promise<void> => { latestStatus: res != null && res.hasOwnProperty('statusCode') ? res.statusCode : null } }); + + instanceChart.requestSent(i.host, false); }); if (res != null && res.hasOwnProperty('statusCode')) { diff --git a/src/queue/processors/http/process-inbox.ts b/src/queue/processors/http/process-inbox.ts index 583e255136..07d4b5ba71 100644 --- a/src/queue/processors/http/process-inbox.ts +++ b/src/queue/processors/http/process-inbox.ts @@ -10,6 +10,7 @@ import { publishApLogStream } from '../../../services/stream'; import Logger from '../../../misc/logger'; import { registerOrFetchInstanceDoc } from '../../../services/register-or-fetch-instance-doc'; import Instance from '../../../models/instance'; +import instanceChart from '../../../services/chart/instance'; const logger = new Logger('inbox'); @@ -128,6 +129,8 @@ export default async (job: bq.Job, done: any): Promise<void> => { latestRequestReceivedAt: new Date() } }); + + instanceChart.requestReceived(i.host); }); // アクティビティを処理 diff --git a/src/remote/activitypub/models/person.ts b/src/remote/activitypub/models/person.ts index c3b26b6577..7809314cd1 100644 --- a/src/remote/activitypub/models/person.ts +++ b/src/remote/activitypub/models/person.ts @@ -10,6 +10,7 @@ import { IDriveFile } from '../../../models/drive-file'; import Meta from '../../../models/meta'; import { fromHtml } from '../../../mfm/fromHtml'; import usersChart from '../../../services/chart/users'; +import instanceChart from '../../../services/chart/instance'; import { URL } from 'url'; import { resolveNote, extractEmojis } from './note'; import { registerOrFetchInstanceDoc } from '../../../services/register-or-fetch-instance-doc'; @@ -195,8 +196,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<IU } }); - // TODO - //perInstanceChart.newUser(); + instanceChart.newUser(i.host); }); //#region Increment users count diff --git a/src/server/api/endpoints/charts/instance.ts b/src/server/api/endpoints/charts/instance.ts new file mode 100644 index 0000000000..99e7d9c1e1 --- /dev/null +++ b/src/server/api/endpoints/charts/instance.ts @@ -0,0 +1,42 @@ +import $ from 'cafy'; +import define from '../../define'; +import instanceChart from '../../../../services/chart/instance'; + +export const meta = { + stability: 'stable', + + desc: { + 'ja-JP': 'インスタンスごとのチャートを取得します。' + }, + + params: { + span: { + validator: $.str.or(['day', 'hour']), + desc: { + 'ja-JP': '集計のスパン (day または hour)' + } + }, + + limit: { + validator: $.num.optional.range(1, 500), + default: 30, + desc: { + 'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。' + } + }, + + host: { + validator: $.str, + desc: { + 'ja-JP': '対象のインスタンスのホスト', + 'en-US': 'Target instance host' + } + } + } +}; + +export default define(meta, (ps) => new Promise(async (res, rej) => { + const stats = await instanceChart.getChart(ps.span as any, ps.limit, ps.host); + + res(stats); +})); diff --git a/src/server/api/endpoints/federation/instances.ts b/src/server/api/endpoints/federation/instances.ts index ce0d10af28..9b4efbaaff 100644 --- a/src/server/api/endpoints/federation/instances.ts +++ b/src/server/api/endpoints/federation/instances.ts @@ -70,6 +70,22 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => { sort = { caughtAt: 1 }; + } else if (ps.sort == '+driveUsage') { + sort = { + driveUsage: -1 + }; + } else if (ps.sort == '-driveUsage') { + sort = { + driveUsage: 1 + }; + } else if (ps.sort == '+driveFiles') { + sort = { + driveFiles: -1 + }; + } else if (ps.sort == '-driveFiles') { + sort = { + driveFiles: 1 + }; } } else { sort = { diff --git a/src/services/chart/instance.ts b/src/services/chart/instance.ts new file mode 100644 index 0000000000..5af398b902 --- /dev/null +++ b/src/services/chart/instance.ts @@ -0,0 +1,302 @@ +import autobind from 'autobind-decorator'; +import Chart, { Obj } from '.'; +import User from '../../models/user'; +import Note from '../../models/note'; +import Following from '../../models/following'; +import DriveFile, { IDriveFile } from '../../models/drive-file'; + +/** + * インスタンスごとのチャート + */ +type InstanceLog = { + requests: { + /** + * 失敗したリクエスト数 + */ + failed: number; + + /** + * 成功したリクエスト数 + */ + succeeded: number; + + /** + * 受信したリクエスト数 + */ + received: number; + }; + + notes: { + /** + * 集計期間時点での、全投稿数 + */ + total: number; + + /** + * 増加した投稿数 + */ + inc: number; + + /** + * 減少した投稿数 + */ + dec: number; + }; + + users: { + /** + * 集計期間時点での、全ユーザー数 + */ + total: number; + + /** + * 増加したユーザー数 + */ + inc: number; + + /** + * 減少したユーザー数 + */ + dec: number; + }; + + following: { + /** + * 集計期間時点での、全フォロー数 + */ + total: number; + + /** + * 増加したフォロー数 + */ + inc: number; + + /** + * 減少したフォロー数 + */ + dec: number; + }; + + followers: { + /** + * 集計期間時点での、全フォロワー数 + */ + total: number; + + /** + * 増加したフォロワー数 + */ + inc: number; + + /** + * 減少したフォロワー数 + */ + dec: number; + }; + + drive: { + /** + * 集計期間時点での、全ドライブファイル数 + */ + totalFiles: number; + + /** + * 集計期間時点での、全ドライブファイルの合計サイズ + */ + totalUsage: number; + + /** + * 増加したドライブファイル数 + */ + incFiles: number; + + /** + * 増加したドライブ使用量 + */ + incUsage: number; + + /** + * 減少したドライブファイル数 + */ + decFiles: number; + + /** + * 減少したドライブ使用量 + */ + decUsage: number; + }; +}; + +class InstanceChart extends Chart<InstanceLog> { + constructor() { + super('instance', true); + } + + @autobind + protected async getTemplate(init: boolean, latest?: InstanceLog, group?: any): Promise<InstanceLog> { + const calcUsage = () => DriveFile + .aggregate([{ + $match: { + 'metadata._user.host': group, + 'metadata.deletedAt': { $exists: false } + } + }, { + $project: { + length: true + } + }, { + $group: { + _id: null, + usage: { $sum: '$length' } + } + }]) + .then(res => res.length > 0 ? res[0].usage : 0); + + const [ + notesCount, + usersCount, + followingCount, + followersCount, + driveFiles, + driveUsage, + ] = init ? await Promise.all([ + Note.count({ '_user.host': group }), + User.count({ host: group }), + Following.count({ '_follower.host': group }), + Following.count({ '_followee.host': group }), + DriveFile.count({ 'metadata._user.host': group }), + calcUsage(), + ]) : [ + latest ? latest.notes.total : 0, + latest ? latest.users.total : 0, + latest ? latest.following.total : 0, + latest ? latest.followers.total : 0, + latest ? latest.drive.totalFiles : 0, + latest ? latest.drive.totalUsage : 0, + ]; + + return { + requests: { + failed: 0, + succeeded: 0, + received: 0 + }, + notes: { + total: notesCount, + inc: 0, + dec: 0 + }, + users: { + total: usersCount, + inc: 0, + dec: 0 + }, + following: { + total: followingCount, + inc: 0, + dec: 0 + }, + followers: { + total: followersCount, + inc: 0, + dec: 0 + }, + drive: { + totalFiles: driveFiles, + totalUsage: driveUsage, + incFiles: 0, + incUsage: 0, + decFiles: 0, + decUsage: 0 + } + }; + } + + @autobind + public async requestReceived(host: string) { + await this.inc({ + requests: { + received: 1 + } + }, host); + } + + @autobind + public async requestSent(host: string, isSucceeded: boolean) { + const update: Obj = {}; + + if (isSucceeded) { + update.succeeded = 1; + } else { + update.failed = 1; + } + + await this.inc({ + requests: update + }, host); + } + + @autobind + public async newUser(host: string) { + await this.inc({ + users: { + total: 1, + inc: 1 + } + }, host); + } + + @autobind + public async updateNote(host: string, isAdditional: boolean) { + await this.inc({ + notes: { + total: isAdditional ? 1 : -1, + inc: isAdditional ? 1 : 0, + dec: isAdditional ? 0 : 1, + } + }, host); + } + + @autobind + public async updateFollowing(host: string, isAdditional: boolean) { + await this.inc({ + following: { + total: isAdditional ? 1 : -1, + inc: isAdditional ? 1 : 0, + dec: isAdditional ? 0 : 1, + } + }, host); + } + + @autobind + public async updateFollowers(host: string, isAdditional: boolean) { + await this.inc({ + followers: { + total: isAdditional ? 1 : -1, + inc: isAdditional ? 1 : 0, + dec: isAdditional ? 0 : 1, + } + }, host); + } + + @autobind + public async updateDrive(file: IDriveFile, isAdditional: boolean) { + const update: Obj = {}; + + update.totalFiles = isAdditional ? 1 : -1; + update.totalUsage = isAdditional ? file.length : -file.length; + if (isAdditional) { + update.incFiles = 1; + update.incUsage = file.length; + } else { + update.decFiles = 1; + update.decUsage = file.length; + } + + await this.inc({ + drive: update + }, file.metadata._user.host); + } +} + +export default new InstanceChart(); diff --git a/src/services/drive/add-file.ts b/src/services/drive/add-file.ts index 0e588d3442..9f3805f94b 100644 --- a/src/services/drive/add-file.ts +++ b/src/services/drive/add-file.ts @@ -13,17 +13,19 @@ import DriveFile, { IMetadata, getDriveFileBucket, IDriveFile } from '../../mode import DriveFolder from '../../models/drive-folder'; import { pack } from '../../models/drive-file'; import { publishMainStream, publishDriveStream } from '../stream'; -import { isLocalUser, IUser, IRemoteUser } from '../../models/user'; +import { isLocalUser, IUser, IRemoteUser, isRemoteUser } from '../../models/user'; import delFile from './delete-file'; import config from '../../config'; import { getDriveFileWebpublicBucket } from '../../models/drive-file-webpublic'; import { getDriveFileThumbnailBucket } from '../../models/drive-file-thumbnail'; import driveChart from '../../services/chart/drive'; import perUserDriveChart from '../../services/chart/per-user-drive'; +import instanceChart from '../../services/chart/instance'; import fetchMeta from '../../misc/fetch-meta'; import { GenerateVideoThumbnail } from './generate-video-thumbnail'; import { driveLogger } from './logger'; import { IImage, ConvertToJpeg, ConvertToWebp, ConvertToPng } from './image-processor'; +import Instance from '../../models/instance'; const logger = driveLogger.createSubLogger('register', 'yellow'); @@ -523,6 +525,15 @@ export default async function( // 統計を更新 driveChart.update(driveFile, true); perUserDriveChart.update(driveFile, true); + if (isRemoteUser(driveFile.metadata._user)) { + instanceChart.updateDrive(driveFile, true); + Instance.update({ host: driveFile.metadata._user.host }, { + $inc: { + driveUsage: driveFile.length, + driveFiles: 1 + } + }); + } return driveFile; } diff --git a/src/services/drive/delete-file.ts b/src/services/drive/delete-file.ts index 4211cd8291..c5c15ca20b 100644 --- a/src/services/drive/delete-file.ts +++ b/src/services/drive/delete-file.ts @@ -4,7 +4,10 @@ import DriveFileThumbnail, { DriveFileThumbnailChunk } from '../../models/drive- import config from '../../config'; import driveChart from '../../services/chart/drive'; import perUserDriveChart from '../../services/chart/per-user-drive'; +import instanceChart from '../../services/chart/instance'; import DriveFileWebpublic, { DriveFileWebpublicChunk } from '../../models/drive-file-webpublic'; +import Instance from '../../models/instance'; +import { isRemoteUser } from '../../models/user'; export default async function(file: IDriveFile, isExpired = false) { if (file.metadata.storage == 'minio') { @@ -84,4 +87,13 @@ export default async function(file: IDriveFile, isExpired = false) { // 統計を更新 driveChart.update(file, false); perUserDriveChart.update(file, false); + if (isRemoteUser(file.metadata._user)) { + instanceChart.updateDrive(file, false); + Instance.update({ host: file.metadata._user.host }, { + $inc: { + driveUsage: -file.length, + driveFiles: -1 + } + }); + } } diff --git a/src/services/following/create.ts b/src/services/following/create.ts index 65b80dcf84..05f4632582 100644 --- a/src/services/following/create.ts +++ b/src/services/following/create.ts @@ -12,6 +12,7 @@ import createFollowRequest from './requests/create'; import perUserFollowingChart from '../../services/chart/per-user-following'; import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc'; import Instance from '../../models/instance'; +import instanceChart from '../../services/chart/instance'; export default async function(follower: IUser, followee: IUser, requestId?: string) { // check blocking @@ -108,8 +109,7 @@ export default async function(follower: IUser, followee: IUser, requestId?: stri } }); - // TODO - //perInstanceChart.newFollowing(); + instanceChart.updateFollowing(i.host, true); }); } else if (isLocalUser(follower) && isRemoteUser(followee)) { registerOrFetchInstanceDoc(followee.host).then(i => { @@ -119,8 +119,7 @@ export default async function(follower: IUser, followee: IUser, requestId?: stri } }); - // TODO - //perInstanceChart.newFollower(); + instanceChart.updateFollowers(i.host, true); }); } //#endregion diff --git a/src/services/following/delete.ts b/src/services/following/delete.ts index 87eaf826e5..93f72b51d8 100644 --- a/src/services/following/delete.ts +++ b/src/services/following/delete.ts @@ -7,6 +7,9 @@ import renderUndo from '../../remote/activitypub/renderer/undo'; import { deliver } from '../../queue'; import perUserFollowingChart from '../../services/chart/per-user-following'; import Logger from '../../misc/logger'; +import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc'; +import Instance from '../../models/instance'; +import instanceChart from '../../services/chart/instance'; const logger = new Logger('following/delete'); @@ -41,6 +44,30 @@ export default async function(follower: IUser, followee: IUser) { }); //#endregion + //#region Update instance stats + if (isRemoteUser(follower) && isLocalUser(followee)) { + registerOrFetchInstanceDoc(follower.host).then(i => { + Instance.update({ _id: i._id }, { + $inc: { + followingCount: -1 + } + }); + + instanceChart.updateFollowing(i.host, false); + }); + } else if (isLocalUser(follower) && isRemoteUser(followee)) { + registerOrFetchInstanceDoc(followee.host).then(i => { + Instance.update({ _id: i._id }, { + $inc: { + followersCount: -1 + } + }); + + instanceChart.updateFollowers(i.host, false); + }); + } + //#endregion + perUserFollowingChart.update(follower, followee, false); // Publish unfollow event diff --git a/src/services/note/create.ts b/src/services/note/create.ts index 0b71a9670c..126d698b08 100644 --- a/src/services/note/create.ts +++ b/src/services/note/create.ts @@ -24,6 +24,7 @@ import isQuote from '../../misc/is-quote'; import notesChart from '../../services/chart/notes'; import perUserNotesChart from '../../services/chart/per-user-notes'; import activeUsersChart from '../../services/chart/active-users'; +import instanceChart from '../../services/chart/instance'; import { erase, concat } from '../../prelude/array'; import insertNoteUnread from './unread'; @@ -229,8 +230,7 @@ export default async (user: IUser, data: Option, silent = false) => new Promise< } }); - // TODO - //perInstanceChart.newNote(); + instanceChart.updateNote(i.host, true); }); } diff --git a/src/services/note/delete.ts b/src/services/note/delete.ts index 8e8c20bfce..2b797545ed 100644 --- a/src/services/note/delete.ts +++ b/src/services/note/delete.ts @@ -1,5 +1,5 @@ import Note, { INote } from '../../models/note'; -import { IUser, isLocalUser } from '../../models/user'; +import { IUser, isLocalUser, isRemoteUser } from '../../models/user'; import { publishNoteStream } from '../stream'; import renderDelete from '../../remote/activitypub/renderer/delete'; import { renderActivity } from '../../remote/activitypub/renderer'; @@ -12,6 +12,9 @@ import config from '../../config'; import NoteUnread from '../../models/note-unread'; import read from './read'; import DriveFile from '../../models/drive-file'; +import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc'; +import Instance from '../../models/instance'; +import instanceChart from '../../services/chart/instance'; /** * 投稿を削除します。 @@ -91,4 +94,16 @@ export default async function(user: IUser, note: INote) { // 統計を更新 notesChart.update(note, false); perUserNotesChart.update(user, note, false); + + if (isRemoteUser(user)) { + registerOrFetchInstanceDoc(user.host).then(i => { + Instance.update({ _id: i._id }, { + $inc: { + notesCount: -1 + } + }); + + instanceChart.updateNote(i.host, false); + }); + } } |