summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2019-02-08 16:58:57 +0900
committerGitHub <noreply@github.com>2019-02-08 16:58:57 +0900
commit56275bcfcbd1366b0e96b79915e810baed0548bb (patch)
treeb35c8a6bd15ef8046c00119f0271808970f4f939 /src
parentSupress logs during test (diff)
downloadsharkey-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.vue275
-rw-r--r--src/models/drive-file.ts1
-rw-r--r--src/models/instance.ts10
-rw-r--r--src/models/user.ts1
-rw-r--r--src/queue/processors/http/deliver.ts5
-rw-r--r--src/queue/processors/http/process-inbox.ts3
-rw-r--r--src/remote/activitypub/models/person.ts4
-rw-r--r--src/server/api/endpoints/charts/instance.ts42
-rw-r--r--src/server/api/endpoints/federation/instances.ts16
-rw-r--r--src/services/chart/instance.ts302
-rw-r--r--src/services/drive/add-file.ts13
-rw-r--r--src/services/drive/delete-file.ts12
-rw-r--r--src/services/following/create.ts7
-rw-r--r--src/services/following/delete.ts27
-rw-r--r--src/services/note/create.ts4
-rw-r--r--src/services/note/delete.ts17
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);
+ });
+ }
}