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
This commit is contained in:
syuilo 2019-02-08 16:58:57 +09:00 committed by GitHub
parent f35688bab8
commit 56275bcfcb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 746 additions and 13 deletions

View file

@ -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();

View file

@ -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;
}

View file

@ -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
}
});
}
}

View file

@ -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

View file

@ -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

View file

@ -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);
});
}

View file

@ -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);
});
}
}