Refactoring
This commit is contained in:
parent
cd12bb33a5
commit
ba0e57396d
|
@ -90,11 +90,25 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
stats(): any[] {
|
stats(): any[] {
|
||||||
return (
|
const now = new Date();
|
||||||
|
const y = now.getFullYear();
|
||||||
|
const m = now.getMonth();
|
||||||
|
const d = now.getDate();
|
||||||
|
const h = now.getHours();
|
||||||
|
|
||||||
|
const stats =
|
||||||
this.span == 'day' ? this.chart.perDay :
|
this.span == 'day' ? this.chart.perDay :
|
||||||
this.span == 'hour' ? this.chart.perHour :
|
this.span == 'hour' ? this.chart.perHour :
|
||||||
null
|
null;
|
||||||
);
|
|
||||||
|
stats.forEach((s, i) => {
|
||||||
|
s.date =
|
||||||
|
this.span == 'day' ? new Date(y, m, d - i) :
|
||||||
|
this.span == 'hour' ? new Date(y, m, d, h - i) :
|
||||||
|
null;
|
||||||
|
});
|
||||||
|
|
||||||
|
return stats;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -560,19 +574,19 @@ export default Vue.extend({
|
||||||
networkRequestsChart(): any {
|
networkRequestsChart(): any {
|
||||||
const data = this.stats.slice().reverse().map(x => ({
|
const data = this.stats.slice().reverse().map(x => ({
|
||||||
date: new Date(x.date),
|
date: new Date(x.date),
|
||||||
requests: x.network.requests
|
incoming: x.network.incomingRequests
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return [{
|
return [{
|
||||||
datasets: [{
|
datasets: [{
|
||||||
label: 'Requests',
|
label: 'Incoming',
|
||||||
fill: true,
|
fill: true,
|
||||||
backgroundColor: rgba(colors.localPlus),
|
backgroundColor: rgba(colors.localPlus),
|
||||||
borderColor: colors.localPlus,
|
borderColor: colors.localPlus,
|
||||||
borderWidth: 2,
|
borderWidth: 2,
|
||||||
pointBackgroundColor: '#fff',
|
pointBackgroundColor: '#fff',
|
||||||
lineTension: 0,
|
lineTension: 0,
|
||||||
data: data.map(x => ({ t: x.date, y: x.requests }))
|
data: data.map(x => ({ t: x.date, y: x.incomingRequests }))
|
||||||
}]
|
}]
|
||||||
}];
|
}];
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,228 +0,0 @@
|
||||||
import * as mongo from 'mongodb';
|
|
||||||
import db from '../db/mongodb';
|
|
||||||
|
|
||||||
const Stats = db.get<IStats>('stats');
|
|
||||||
|
|
||||||
Stats.createIndex({ span: -1, date: -1 }, { unique: true });
|
|
||||||
export default Stats;
|
|
||||||
|
|
||||||
export interface IStats {
|
|
||||||
_id: mongo.ObjectID;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 集計日時
|
|
||||||
*/
|
|
||||||
date: Date;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 集計期間
|
|
||||||
*/
|
|
||||||
span: 'day' | 'hour';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ユーザーに関する統計
|
|
||||||
*/
|
|
||||||
users: {
|
|
||||||
local: {
|
|
||||||
/**
|
|
||||||
* 集計期間時点での、全ユーザー数 (ローカル)
|
|
||||||
*/
|
|
||||||
total: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 増加したユーザー数 (ローカル)
|
|
||||||
*/
|
|
||||||
inc: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 減少したユーザー数 (ローカル)
|
|
||||||
*/
|
|
||||||
dec: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
remote: {
|
|
||||||
/**
|
|
||||||
* 集計期間時点での、全ユーザー数 (リモート)
|
|
||||||
*/
|
|
||||||
total: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 増加したユーザー数 (リモート)
|
|
||||||
*/
|
|
||||||
inc: 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: {
|
|
||||||
/**
|
|
||||||
* サーバーへのリクエスト数
|
|
||||||
*/
|
|
||||||
requests: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 応答時間の合計
|
|
||||||
* TIP: (totalTime / requests) でひとつのリクエストに平均でどれくらいの時間がかかったか知れる
|
|
||||||
*/
|
|
||||||
totalTime: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 合計受信データ量
|
|
||||||
*/
|
|
||||||
incomingBytes: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 合計送信データ量
|
|
||||||
*/
|
|
||||||
outgoingBytes: number;
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -10,7 +10,7 @@ import { isCollectionOrOrderedCollection, isCollection, IPerson } from '../type'
|
||||||
import { IDriveFile } from '../../../models/drive-file';
|
import { IDriveFile } from '../../../models/drive-file';
|
||||||
import Meta from '../../../models/meta';
|
import Meta from '../../../models/meta';
|
||||||
import htmlToMFM from '../../../mfm/html-to-mfm';
|
import htmlToMFM from '../../../mfm/html-to-mfm';
|
||||||
import { updateUserStats } from '../../../services/update-chart';
|
import { coreChart } from '../../../services/stats';
|
||||||
import { URL } from 'url';
|
import { URL } from 'url';
|
||||||
import { resolveNote } from './note';
|
import { resolveNote } from './note';
|
||||||
|
|
||||||
|
@ -180,7 +180,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<IU
|
||||||
}
|
}
|
||||||
}, { upsert: true });
|
}, { upsert: true });
|
||||||
|
|
||||||
updateUserStats(user, true);
|
coreChart.updateUserStats(user, true);
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
//#region アイコンとヘッダー画像をフェッチ
|
//#region アイコンとヘッダー画像をフェッチ
|
||||||
|
|
|
@ -1,58 +1,6 @@
|
||||||
import $ from 'cafy';
|
import $ from 'cafy';
|
||||||
import Stats, { IStats } from '../../../models/stats';
|
|
||||||
import getParams from '../get-params';
|
import getParams from '../get-params';
|
||||||
|
import { coreChart } from '../../../services/stats';
|
||||||
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
|
|
||||||
|
|
||||||
function migrateStats(stats: IStats[]) {
|
|
||||||
stats.forEach(stat => {
|
|
||||||
if (stat.network == null) {
|
|
||||||
stat.network = {
|
|
||||||
requests: 0,
|
|
||||||
totalTime: 0,
|
|
||||||
incomingBytes: 0,
|
|
||||||
outgoingBytes: 0
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const isOldData =
|
|
||||||
stat.users.local.inc == null ||
|
|
||||||
stat.users.local.dec == null ||
|
|
||||||
stat.users.remote.inc == null ||
|
|
||||||
stat.users.remote.dec == null ||
|
|
||||||
stat.notes.local.inc == null ||
|
|
||||||
stat.notes.local.dec == null ||
|
|
||||||
stat.notes.remote.inc == null ||
|
|
||||||
stat.notes.remote.dec == null ||
|
|
||||||
stat.drive.local.incCount == null ||
|
|
||||||
stat.drive.local.decCount == null ||
|
|
||||||
stat.drive.local.incSize == null ||
|
|
||||||
stat.drive.local.decSize == null ||
|
|
||||||
stat.drive.remote.incCount == null ||
|
|
||||||
stat.drive.remote.decCount == null ||
|
|
||||||
stat.drive.remote.incSize == null ||
|
|
||||||
stat.drive.remote.decSize == null;
|
|
||||||
|
|
||||||
if (!isOldData) return;
|
|
||||||
|
|
||||||
stat.users.local.inc = (stat as any).users.local.diff;
|
|
||||||
stat.users.local.dec = 0;
|
|
||||||
stat.users.remote.inc = (stat as any).users.remote.diff;
|
|
||||||
stat.users.remote.dec = 0;
|
|
||||||
stat.notes.local.inc = (stat as any).notes.local.diff;
|
|
||||||
stat.notes.local.dec = 0;
|
|
||||||
stat.notes.remote.inc = (stat as any).notes.remote.diff;
|
|
||||||
stat.notes.remote.dec = 0;
|
|
||||||
stat.drive.local.incCount = (stat as any).drive.local.diffCount;
|
|
||||||
stat.drive.local.decCount = 0;
|
|
||||||
stat.drive.local.incSize = (stat as any).drive.local.diffSize;
|
|
||||||
stat.drive.local.decSize = 0;
|
|
||||||
stat.drive.remote.incCount = (stat as any).drive.remote.diffCount;
|
|
||||||
stat.drive.remote.decCount = 0;
|
|
||||||
stat.drive.remote.incSize = (stat as any).drive.remote.diffSize;
|
|
||||||
stat.drive.remote.decSize = 0;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
desc: {
|
desc: {
|
||||||
|
@ -73,205 +21,13 @@ export default (params: any) => new Promise(async (res, rej) => {
|
||||||
const [ps, psErr] = getParams(meta, params);
|
const [ps, psErr] = getParams(meta, params);
|
||||||
if (psErr) throw psErr;
|
if (psErr) throw psErr;
|
||||||
|
|
||||||
const daysRange = ps.limit;
|
|
||||||
const hoursRange = ps.limit;
|
|
||||||
|
|
||||||
const now = new Date();
|
|
||||||
const y = now.getFullYear();
|
|
||||||
const m = now.getMonth();
|
|
||||||
const d = now.getDate();
|
|
||||||
const h = now.getHours();
|
|
||||||
|
|
||||||
const [statsPerDay, statsPerHour] = await Promise.all([
|
const [statsPerDay, statsPerHour] = await Promise.all([
|
||||||
Stats.find({
|
coreChart.getStats('day', ps.limit),
|
||||||
span: 'day',
|
coreChart.getStats('hour', ps.limit)
|
||||||
date: {
|
|
||||||
$gt: new Date(y, m, d - daysRange)
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
sort: {
|
|
||||||
date: -1
|
|
||||||
},
|
|
||||||
fields: {
|
|
||||||
_id: 0
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
Stats.find({
|
|
||||||
span: 'hour',
|
|
||||||
date: {
|
|
||||||
$gt: new Date(y, m, d, h - hoursRange)
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
sort: {
|
|
||||||
date: -1
|
|
||||||
},
|
|
||||||
fields: {
|
|
||||||
_id: 0
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// 後方互換性のため
|
|
||||||
migrateStats(statsPerDay);
|
|
||||||
migrateStats(statsPerHour);
|
|
||||||
|
|
||||||
const format = (src: IStats[], span: 'day' | 'hour') => {
|
|
||||||
const chart: Array<Omit<Omit<IStats, '_id'>, 'span'>> = [];
|
|
||||||
|
|
||||||
const range =
|
|
||||||
span == 'day' ? daysRange :
|
|
||||||
span == 'hour' ? hoursRange :
|
|
||||||
null;
|
|
||||||
|
|
||||||
for (let i = (range - 1); i >= 0; i--) {
|
|
||||||
const current =
|
|
||||||
span == 'day' ? new Date(y, m, d - i) :
|
|
||||||
span == 'hour' ? new Date(y, m, d, h - i) :
|
|
||||||
null;
|
|
||||||
|
|
||||||
const stat = src.find(s => s.date.getTime() == current.getTime());
|
|
||||||
|
|
||||||
if (stat) {
|
|
||||||
chart.unshift(stat);
|
|
||||||
} else { // 隙間埋め
|
|
||||||
const mostRecent = src.find(s => s.date.getTime() < current.getTime());
|
|
||||||
if (mostRecent) {
|
|
||||||
chart.unshift({
|
|
||||||
date: current,
|
|
||||||
users: {
|
|
||||||
local: {
|
|
||||||
total: mostRecent.users.local.total,
|
|
||||||
inc: 0,
|
|
||||||
dec: 0
|
|
||||||
},
|
|
||||||
remote: {
|
|
||||||
total: mostRecent.users.remote.total,
|
|
||||||
inc: 0,
|
|
||||||
dec: 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
notes: {
|
|
||||||
local: {
|
|
||||||
total: mostRecent.notes.local.total,
|
|
||||||
inc: 0,
|
|
||||||
dec: 0,
|
|
||||||
diffs: {
|
|
||||||
normal: 0,
|
|
||||||
reply: 0,
|
|
||||||
renote: 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
remote: {
|
|
||||||
total: mostRecent.notes.remote.total,
|
|
||||||
inc: 0,
|
|
||||||
dec: 0,
|
|
||||||
diffs: {
|
|
||||||
normal: 0,
|
|
||||||
reply: 0,
|
|
||||||
renote: 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
drive: {
|
|
||||||
local: {
|
|
||||||
totalCount: mostRecent.drive.local.totalCount,
|
|
||||||
totalSize: mostRecent.drive.local.totalSize,
|
|
||||||
incCount: 0,
|
|
||||||
incSize: 0,
|
|
||||||
decCount: 0,
|
|
||||||
decSize: 0
|
|
||||||
},
|
|
||||||
remote: {
|
|
||||||
totalCount: mostRecent.drive.remote.totalCount,
|
|
||||||
totalSize: mostRecent.drive.remote.totalSize,
|
|
||||||
incCount: 0,
|
|
||||||
incSize: 0,
|
|
||||||
decCount: 0,
|
|
||||||
decSize: 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
network: {
|
|
||||||
requests: 0,
|
|
||||||
totalTime: 0,
|
|
||||||
incomingBytes: 0,
|
|
||||||
outgoingBytes: 0
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
chart.unshift({
|
|
||||||
date: current,
|
|
||||||
users: {
|
|
||||||
local: {
|
|
||||||
total: 0,
|
|
||||||
inc: 0,
|
|
||||||
dec: 0
|
|
||||||
},
|
|
||||||
remote: {
|
|
||||||
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: {
|
|
||||||
requests: 0,
|
|
||||||
totalTime: 0,
|
|
||||||
incomingBytes: 0,
|
|
||||||
outgoingBytes: 0
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
chart.forEach(x => {
|
|
||||||
delete (x as any).span;
|
|
||||||
});
|
|
||||||
|
|
||||||
return chart;
|
|
||||||
};
|
|
||||||
|
|
||||||
res({
|
res({
|
||||||
perDay: format(statsPerDay, 'day'),
|
perDay: statsPerDay,
|
||||||
perHour: format(statsPerHour, 'hour')
|
perHour: statsPerHour
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,7 +7,7 @@ import generateUserToken from '../common/generate-native-user-token';
|
||||||
import config from '../../../config';
|
import config from '../../../config';
|
||||||
import Meta from '../../../models/meta';
|
import Meta from '../../../models/meta';
|
||||||
import RegistrationTicket from '../../../models/registration-tickets';
|
import RegistrationTicket from '../../../models/registration-tickets';
|
||||||
import { updateUserStats } from '../../../services/update-chart';
|
import { coreChart } from '../../../services/stats';
|
||||||
|
|
||||||
if (config.recaptcha) {
|
if (config.recaptcha) {
|
||||||
recaptcha.init({
|
recaptcha.init({
|
||||||
|
@ -130,7 +130,7 @@ export default async (ctx: Koa.Context) => {
|
||||||
}, { upsert: true });
|
}, { upsert: true });
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
updateUserStats(account, true);
|
coreChart.updateUserStats(account, true);
|
||||||
|
|
||||||
const res = await pack(account, account, {
|
const res = await pack(account, account, {
|
||||||
detail: true,
|
detail: true,
|
||||||
|
|
|
@ -17,7 +17,7 @@ const requestStats = require('request-stats');
|
||||||
import activityPub from './activitypub';
|
import activityPub from './activitypub';
|
||||||
import webFinger from './webfinger';
|
import webFinger from './webfinger';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
import { updateNetworkStats } from '../services/update-chart';
|
import { coreChart } from '../services/stats';
|
||||||
import apiServer from './api';
|
import apiServer from './api';
|
||||||
|
|
||||||
// Init app
|
// Init app
|
||||||
|
@ -104,7 +104,7 @@ export default () => new Promise(resolve => {
|
||||||
const outgoingBytes = queue.reduce((a, b) => a + b.res.bytes, 0);
|
const outgoingBytes = queue.reduce((a, b) => a + b.res.bytes, 0);
|
||||||
queue = [];
|
queue = [];
|
||||||
|
|
||||||
updateNetworkStats(requests, time, incomingBytes, outgoingBytes);
|
coreChart.updateNetworkStats(requests, time, incomingBytes, outgoingBytes);
|
||||||
}, 5000);
|
}, 5000);
|
||||||
//#endregion
|
//#endregion
|
||||||
});
|
});
|
||||||
|
|
|
@ -17,7 +17,7 @@ import { isLocalUser, IUser, IRemoteUser } from '../../models/user';
|
||||||
import delFile from './delete-file';
|
import delFile from './delete-file';
|
||||||
import config from '../../config';
|
import config from '../../config';
|
||||||
import { getDriveFileThumbnailBucket } from '../../models/drive-file-thumbnail';
|
import { getDriveFileThumbnailBucket } from '../../models/drive-file-thumbnail';
|
||||||
import { updateDriveStats } from '../update-chart';
|
import { coreChart } from '../stats';
|
||||||
|
|
||||||
const log = debug('misskey:drive:add-file');
|
const log = debug('misskey:drive:add-file');
|
||||||
|
|
||||||
|
@ -389,7 +389,7 @@ export default async function(
|
||||||
});
|
});
|
||||||
|
|
||||||
// 統計を更新
|
// 統計を更新
|
||||||
updateDriveStats(driveFile, true);
|
coreChart.updateDriveStats(driveFile, true);
|
||||||
|
|
||||||
return driveFile;
|
return driveFile;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import * as Minio from 'minio';
|
||||||
import DriveFile, { DriveFileChunk, IDriveFile } from '../../models/drive-file';
|
import DriveFile, { DriveFileChunk, IDriveFile } from '../../models/drive-file';
|
||||||
import DriveFileThumbnail, { DriveFileThumbnailChunk } from '../../models/drive-file-thumbnail';
|
import DriveFileThumbnail, { DriveFileThumbnailChunk } from '../../models/drive-file-thumbnail';
|
||||||
import config from '../../config';
|
import config from '../../config';
|
||||||
import { updateDriveStats } from '../update-chart';
|
import { coreChart } from '../stats';
|
||||||
|
|
||||||
export default async function(file: IDriveFile, isExpired = false) {
|
export default async function(file: IDriveFile, isExpired = false) {
|
||||||
if (file.metadata.storage == 'minio') {
|
if (file.metadata.storage == 'minio') {
|
||||||
|
@ -48,5 +48,5 @@ export default async function(file: IDriveFile, isExpired = false) {
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
// 統計を更新
|
// 統計を更新
|
||||||
updateDriveStats(file, false);
|
coreChart.updateDriveStats(file, false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ import registerHashtag from '../register-hashtag';
|
||||||
import isQuote from '../../misc/is-quote';
|
import isQuote from '../../misc/is-quote';
|
||||||
import { TextElementMention } from '../../mfm/parse/elements/mention';
|
import { TextElementMention } from '../../mfm/parse/elements/mention';
|
||||||
import { TextElementHashtag } from '../../mfm/parse/elements/hashtag';
|
import { TextElementHashtag } from '../../mfm/parse/elements/hashtag';
|
||||||
import { updateNoteStats } from '../update-chart';
|
import { coreChart } from '../stats';
|
||||||
import { erase, unique } from '../../prelude/array';
|
import { erase, unique } from '../../prelude/array';
|
||||||
import insertNoteUnread from './unread';
|
import insertNoteUnread from './unread';
|
||||||
|
|
||||||
|
@ -165,7 +165,7 @@ export default async (user: IUser, data: Option, silent = false) => new Promise<
|
||||||
}
|
}
|
||||||
|
|
||||||
// 統計を更新
|
// 統計を更新
|
||||||
updateNoteStats(note, true);
|
coreChart.updateNoteStats(note, true);
|
||||||
|
|
||||||
// ハッシュタグ登録
|
// ハッシュタグ登録
|
||||||
tags.map(tag => registerHashtag(user, tag));
|
tags.map(tag => registerHashtag(user, tag));
|
||||||
|
|
|
@ -6,7 +6,7 @@ import pack from '../../remote/activitypub/renderer';
|
||||||
import { deliver } from '../../queue';
|
import { deliver } from '../../queue';
|
||||||
import Following from '../../models/following';
|
import Following from '../../models/following';
|
||||||
import renderTombstone from '../../remote/activitypub/renderer/tombstone';
|
import renderTombstone from '../../remote/activitypub/renderer/tombstone';
|
||||||
import { updateNoteStats } from '../update-chart';
|
import { coreChart } from '../stats';
|
||||||
import config from '../../config';
|
import config from '../../config';
|
||||||
import NoteUnread from '../../models/note-unread';
|
import NoteUnread from '../../models/note-unread';
|
||||||
import read from './read';
|
import read from './read';
|
||||||
|
@ -63,5 +63,5 @@ export default async function(user: IUser, note: INote) {
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
// 統計を更新
|
// 統計を更新
|
||||||
updateNoteStats(note, false);
|
coreChart.updateNoteStats(note, false);
|
||||||
}
|
}
|
||||||
|
|
628
src/services/stats.ts
Normal file
628
src/services/stats.ts
Normal file
|
@ -0,0 +1,628 @@
|
||||||
|
import * as mongo from 'mongodb';
|
||||||
|
import db from '../db/mongodb';
|
||||||
|
import { INote } from '../models/note';
|
||||||
|
import { isLocalUser, IUser } from '../models/user';
|
||||||
|
import { IDriveFile } from '../models/drive-file';
|
||||||
|
import { ICollection } from 'monk';
|
||||||
|
|
||||||
|
type Obj = { [key: string]: any };
|
||||||
|
|
||||||
|
type Partial<T> = {
|
||||||
|
[P in keyof T]?: Partial<T[P]>;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Span = 'day' | 'hour';
|
||||||
|
|
||||||
|
type ChartDocument<T extends Obj> = {
|
||||||
|
_id: mongo.ObjectID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 集計日時
|
||||||
|
*/
|
||||||
|
date: Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 集計期間
|
||||||
|
*/
|
||||||
|
span: Span;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* データ
|
||||||
|
*/
|
||||||
|
data: T;
|
||||||
|
};
|
||||||
|
|
||||||
|
abstract class Chart<T> {
|
||||||
|
protected collection: ICollection<ChartDocument<T>>;
|
||||||
|
protected abstract generateInitialStats(): T;
|
||||||
|
protected abstract generateEmptyStats(mostRecentStats: T): T;
|
||||||
|
|
||||||
|
constructor(dbCollectionName: string) {
|
||||||
|
this.collection = db.get<ChartDocument<T>>(dbCollectionName);
|
||||||
|
this.collection.createIndex({ span: -1, date: -1 }, { unique: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async getCurrentStats(span: Span, group?: Obj): Promise<ChartDocument<T>> {
|
||||||
|
const now = new Date();
|
||||||
|
const y = now.getFullYear();
|
||||||
|
const m = now.getMonth();
|
||||||
|
const d = now.getDate();
|
||||||
|
const h = now.getHours();
|
||||||
|
|
||||||
|
const current =
|
||||||
|
span == 'day' ? new Date(y, m, d) :
|
||||||
|
span == 'hour' ? new Date(y, m, d, h) :
|
||||||
|
null;
|
||||||
|
|
||||||
|
// 現在(今日または今のHour)の統計
|
||||||
|
const currentStats = await this.collection.findOne(Object.assign({}, {
|
||||||
|
span: span,
|
||||||
|
date: current
|
||||||
|
}, group));
|
||||||
|
|
||||||
|
if (currentStats) {
|
||||||
|
return currentStats;
|
||||||
|
} else {
|
||||||
|
// 集計期間が変わってから、初めてのチャート更新なら
|
||||||
|
// 最も最近の統計を持ってくる
|
||||||
|
// * 例えば集計期間が「日」である場合で考えると、
|
||||||
|
// * 昨日何もチャートを更新するような出来事がなかった場合は、
|
||||||
|
// * 統計がそもそも作られずドキュメントが存在しないということがあり得るため、
|
||||||
|
// * 「昨日の」と決め打ちせずに「もっとも最近の」とします
|
||||||
|
const mostRecentStats = await this.collection.findOne(Object.assign({}, {
|
||||||
|
span: span
|
||||||
|
}, group), {
|
||||||
|
sort: {
|
||||||
|
date: -1
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (mostRecentStats) {
|
||||||
|
// 現在の統計を初期挿入
|
||||||
|
const data = this.generateEmptyStats(mostRecentStats.data);
|
||||||
|
|
||||||
|
const stats = await this.collection.insert(Object.assign({}, {
|
||||||
|
span: span,
|
||||||
|
date: current,
|
||||||
|
data: data
|
||||||
|
}, group));
|
||||||
|
|
||||||
|
return stats;
|
||||||
|
} else {
|
||||||
|
// 統計が存在しなかったら
|
||||||
|
// * Misskeyインスタンスを建てて初めてのチャート更新時など
|
||||||
|
|
||||||
|
// 空の統計を作成
|
||||||
|
const data = this.generateInitialStats();
|
||||||
|
|
||||||
|
const stats = await this.collection.insert(Object.assign({}, {
|
||||||
|
span: span,
|
||||||
|
date: current,
|
||||||
|
data: data
|
||||||
|
}, group));
|
||||||
|
|
||||||
|
return stats;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected update(inc: Partial<T>, group?: Obj): void {
|
||||||
|
const query: Obj = {};
|
||||||
|
|
||||||
|
const dive = (path: string, x: Obj) => {
|
||||||
|
Object.entries(x).forEach(([k, v]) => {
|
||||||
|
if (typeof v === 'number') {
|
||||||
|
query[path == null ? `data.${k}` : `data.${path}.${k}`] = v;
|
||||||
|
} else {
|
||||||
|
dive(path == null ? k : `${path}.${k}`, v);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
dive(null, inc);
|
||||||
|
|
||||||
|
this.getCurrentStats('day', group).then(stats => {
|
||||||
|
this.collection.findOneAndUpdate({
|
||||||
|
_id: stats._id
|
||||||
|
}, {
|
||||||
|
$inc: query
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.getCurrentStats('hour', group).then(stats => {
|
||||||
|
this.collection.findOneAndUpdate({
|
||||||
|
_id: stats._id
|
||||||
|
}, {
|
||||||
|
$inc: query
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getStats(span: Span, range: number, group?: Obj): Promise<T[]> {
|
||||||
|
const chart: T[] = [];
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
const y = now.getFullYear();
|
||||||
|
const m = now.getMonth();
|
||||||
|
const d = now.getDate();
|
||||||
|
const h = now.getHours();
|
||||||
|
|
||||||
|
const gt =
|
||||||
|
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({
|
||||||
|
span: span,
|
||||||
|
date: {
|
||||||
|
$gt: gt
|
||||||
|
}
|
||||||
|
}, group), {
|
||||||
|
sort: {
|
||||||
|
date: -1
|
||||||
|
},
|
||||||
|
fields: {
|
||||||
|
_id: 0
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for (let i = (range - 1); i >= 0; i--) {
|
||||||
|
const current =
|
||||||
|
span == 'day' ? new Date(y, m, d - i) :
|
||||||
|
span == 'hour' ? new Date(y, m, d, h - i) :
|
||||||
|
null;
|
||||||
|
|
||||||
|
const stat = stats.find(s => s.date.getTime() == current.getTime());
|
||||||
|
|
||||||
|
if (stat) {
|
||||||
|
chart.unshift(stat.data);
|
||||||
|
} else { // 隙間埋め
|
||||||
|
const mostRecent = stats.find(s => s.date.getTime() < current.getTime());
|
||||||
|
if (mostRecent) {
|
||||||
|
chart.unshift(this.generateEmptyStats(mostRecent.data));
|
||||||
|
} else {
|
||||||
|
chart.unshift(this.generateInitialStats());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return chart;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type CoreStats = {
|
||||||
|
/**
|
||||||
|
* ユーザーに関する統計
|
||||||
|
*/
|
||||||
|
users: {
|
||||||
|
local: {
|
||||||
|
/**
|
||||||
|
* 集計期間時点での、全ユーザー数 (ローカル)
|
||||||
|
*/
|
||||||
|
total: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 増加したユーザー数 (ローカル)
|
||||||
|
*/
|
||||||
|
inc: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 減少したユーザー数 (ローカル)
|
||||||
|
*/
|
||||||
|
dec: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
remote: {
|
||||||
|
/**
|
||||||
|
* 集計期間時点での、全ユーザー数 (リモート)
|
||||||
|
*/
|
||||||
|
total: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 増加したユーザー数 (リモート)
|
||||||
|
*/
|
||||||
|
inc: 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: {
|
||||||
|
/**
|
||||||
|
* 受信したリクエスト数
|
||||||
|
*/
|
||||||
|
incomingRequests: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 送信したリクエスト数
|
||||||
|
*/
|
||||||
|
outgoingRequests: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 応答時間の合計
|
||||||
|
* TIP: (totalTime / incomingRequests) でひとつのリクエストに平均でどれくらいの時間がかかったか知れる
|
||||||
|
*/
|
||||||
|
totalTime: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 合計受信データ量
|
||||||
|
*/
|
||||||
|
incomingBytes: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 合計送信データ量
|
||||||
|
*/
|
||||||
|
outgoingBytes: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
class CoreChart extends Chart<CoreStats> {
|
||||||
|
constructor() {
|
||||||
|
super('coreStats');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected generateInitialStats(): CoreStats {
|
||||||
|
return {
|
||||||
|
users: {
|
||||||
|
local: {
|
||||||
|
total: 0,
|
||||||
|
inc: 0,
|
||||||
|
dec: 0
|
||||||
|
},
|
||||||
|
remote: {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected generateEmptyStats(mostRecentStats: CoreStats): CoreStats {
|
||||||
|
return {
|
||||||
|
users: {
|
||||||
|
local: {
|
||||||
|
total: mostRecentStats.users.local.total,
|
||||||
|
inc: 0,
|
||||||
|
dec: 0
|
||||||
|
},
|
||||||
|
remote: {
|
||||||
|
total: mostRecentStats.users.remote.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
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async updateUserStats(user: IUser, isAdditional: boolean) {
|
||||||
|
const origin = isLocalUser(user) ? 'local' : 'remote';
|
||||||
|
|
||||||
|
const update: Obj = {};
|
||||||
|
|
||||||
|
update.total = isAdditional ? 1 : -1;
|
||||||
|
if (isAdditional) {
|
||||||
|
update.inc = 1;
|
||||||
|
} else {
|
||||||
|
update.dec = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const inc: Obj = {
|
||||||
|
users: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
inc.users[origin] = update;
|
||||||
|
|
||||||
|
await this.update(inc);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async updateNoteStats(note: INote, isAdditional: boolean) {
|
||||||
|
const origin = isLocalUser(note._user) ? 'local' : 'remote';
|
||||||
|
|
||||||
|
const update: Obj = {};
|
||||||
|
|
||||||
|
update.total = isAdditional ? 1 : -1;
|
||||||
|
|
||||||
|
if (isAdditional) {
|
||||||
|
update.inc = 1;
|
||||||
|
} else {
|
||||||
|
update.dec = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (note.replyId != null) {
|
||||||
|
update.diffs.reply = isAdditional ? 1 : -1;
|
||||||
|
} else if (note.renoteId != null) {
|
||||||
|
update.diffs.renote = isAdditional ? 1 : -1;
|
||||||
|
} else {
|
||||||
|
update.diffs.normal = isAdditional ? 1 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const inc: Obj = {
|
||||||
|
notes: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
inc.notes[origin] = update;
|
||||||
|
|
||||||
|
await this.update(inc);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async updateDriveStats(file: IDriveFile, isAdditional: boolean) {
|
||||||
|
const origin = isLocalUser(file.metadata._user) ? 'local' : 'remote';
|
||||||
|
|
||||||
|
const update: Obj = {};
|
||||||
|
|
||||||
|
update.totalCount = isAdditional ? 1 : -1;
|
||||||
|
update.totalSize = isAdditional ? file.length : -file.length;
|
||||||
|
if (isAdditional) {
|
||||||
|
update.incCount = 1;
|
||||||
|
update.incSize = file.length;
|
||||||
|
} else {
|
||||||
|
update.decCount = 1;
|
||||||
|
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<CoreStats> = {
|
||||||
|
network: {
|
||||||
|
incomingRequests: incomingRequests,
|
||||||
|
totalTime: time,
|
||||||
|
incomingBytes: incomingBytes,
|
||||||
|
outgoingBytes: outgoingBytes
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
await this.update(inc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const coreChart = new CoreChart();
|
|
@ -1,267 +0,0 @@
|
||||||
import { INote } from '../models/note';
|
|
||||||
import Stats, { IStats } from '../models/stats';
|
|
||||||
import { isLocalUser, IUser } from '../models/user';
|
|
||||||
import { IDriveFile } from '../models/drive-file';
|
|
||||||
|
|
||||||
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
|
|
||||||
|
|
||||||
async function getCurrentStats(span: 'day' | 'hour'): Promise<IStats> {
|
|
||||||
const now = new Date();
|
|
||||||
const y = now.getFullYear();
|
|
||||||
const m = now.getMonth();
|
|
||||||
const d = now.getDate();
|
|
||||||
const h = now.getHours();
|
|
||||||
|
|
||||||
const current =
|
|
||||||
span == 'day' ? new Date(y, m, d) :
|
|
||||||
span == 'hour' ? new Date(y, m, d, h) :
|
|
||||||
null;
|
|
||||||
|
|
||||||
// 現在(今日または今のHour)の統計
|
|
||||||
const currentStats = await Stats.findOne({
|
|
||||||
span: span,
|
|
||||||
date: current
|
|
||||||
});
|
|
||||||
|
|
||||||
if (currentStats) {
|
|
||||||
return currentStats;
|
|
||||||
} else {
|
|
||||||
// 集計期間が変わってから、初めてのチャート更新なら
|
|
||||||
// 最も最近の統計を持ってくる
|
|
||||||
// * 例えば集計期間が「日」である場合で考えると、
|
|
||||||
// * 昨日何もチャートを更新するような出来事がなかった場合は、
|
|
||||||
// * 統計がそもそも作られずドキュメントが存在しないということがあり得るため、
|
|
||||||
// * 「昨日の」と決め打ちせずに「もっとも最近の」とします
|
|
||||||
const mostRecentStats = await Stats.findOne({
|
|
||||||
span: span
|
|
||||||
}, {
|
|
||||||
sort: {
|
|
||||||
date: -1
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (mostRecentStats) {
|
|
||||||
// 現在の統計を初期挿入
|
|
||||||
const data: Omit<IStats, '_id'> = {
|
|
||||||
span: span,
|
|
||||||
date: current,
|
|
||||||
users: {
|
|
||||||
local: {
|
|
||||||
total: mostRecentStats.users.local.total,
|
|
||||||
inc: 0,
|
|
||||||
dec: 0
|
|
||||||
},
|
|
||||||
remote: {
|
|
||||||
total: mostRecentStats.users.remote.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: {
|
|
||||||
requests: 0,
|
|
||||||
totalTime: 0,
|
|
||||||
incomingBytes: 0,
|
|
||||||
outgoingBytes: 0
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const stats = await Stats.insert(data);
|
|
||||||
|
|
||||||
return stats;
|
|
||||||
} else {
|
|
||||||
// 統計が存在しなかったら
|
|
||||||
// * Misskeyインスタンスを建てて初めてのチャート更新時など
|
|
||||||
|
|
||||||
// 空の統計を作成
|
|
||||||
const emptyStat: Omit<IStats, '_id'> = {
|
|
||||||
span: span,
|
|
||||||
date: current,
|
|
||||||
users: {
|
|
||||||
local: {
|
|
||||||
total: 0,
|
|
||||||
inc: 0,
|
|
||||||
dec: 0
|
|
||||||
},
|
|
||||||
remote: {
|
|
||||||
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: {
|
|
||||||
requests: 0,
|
|
||||||
totalTime: 0,
|
|
||||||
incomingBytes: 0,
|
|
||||||
outgoingBytes: 0
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const stats = await Stats.insert(emptyStat);
|
|
||||||
|
|
||||||
return stats;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function update(inc: any) {
|
|
||||||
getCurrentStats('day').then(stats => {
|
|
||||||
Stats.findOneAndUpdate({
|
|
||||||
_id: stats._id
|
|
||||||
}, {
|
|
||||||
$inc: inc
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
getCurrentStats('hour').then(stats => {
|
|
||||||
Stats.findOneAndUpdate({
|
|
||||||
_id: stats._id
|
|
||||||
}, {
|
|
||||||
$inc: inc
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function updateUserStats(user: IUser, isAdditional: boolean) {
|
|
||||||
const origin = isLocalUser(user) ? 'local' : 'remote';
|
|
||||||
|
|
||||||
const inc = {} as any;
|
|
||||||
inc[`users.${origin}.total`] = isAdditional ? 1 : -1;
|
|
||||||
if (isAdditional) {
|
|
||||||
inc[`users.${origin}.inc`] = 1;
|
|
||||||
} else {
|
|
||||||
inc[`users.${origin}.dec`] = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
await update(inc);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function updateNoteStats(note: INote, isAdditional: boolean) {
|
|
||||||
const origin = isLocalUser(note._user) ? 'local' : 'remote';
|
|
||||||
|
|
||||||
const inc = {} as any;
|
|
||||||
|
|
||||||
inc[`notes.${origin}.total`] = isAdditional ? 1 : -1;
|
|
||||||
|
|
||||||
if (isAdditional) {
|
|
||||||
inc[`notes.${origin}.inc`] = 1;
|
|
||||||
} else {
|
|
||||||
inc[`notes.${origin}.dec`] = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (note.replyId != null) {
|
|
||||||
inc[`notes.${origin}.diffs.reply`] = isAdditional ? 1 : -1;
|
|
||||||
} else if (note.renoteId != null) {
|
|
||||||
inc[`notes.${origin}.diffs.renote`] = isAdditional ? 1 : -1;
|
|
||||||
} else {
|
|
||||||
inc[`notes.${origin}.diffs.normal`] = isAdditional ? 1 : -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
await update(inc);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function updateDriveStats(file: IDriveFile, isAdditional: boolean) {
|
|
||||||
const origin = isLocalUser(file.metadata._user) ? 'local' : 'remote';
|
|
||||||
|
|
||||||
const inc = {} as any;
|
|
||||||
inc[`drive.${origin}.totalCount`] = isAdditional ? 1 : -1;
|
|
||||||
inc[`drive.${origin}.totalSize`] = isAdditional ? file.length : -file.length;
|
|
||||||
if (isAdditional) {
|
|
||||||
inc[`drive.${origin}.incCount`] = 1;
|
|
||||||
inc[`drive.${origin}.incSize`] = file.length;
|
|
||||||
} else {
|
|
||||||
inc[`drive.${origin}.decCount`] = 1;
|
|
||||||
inc[`drive.${origin}.decSize`] = file.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
await update(inc);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function updateNetworkStats(requests: number, time: number, incomingBytes: number, outgoingBytes: number) {
|
|
||||||
const inc = {} as any;
|
|
||||||
inc['network.requests'] = requests;
|
|
||||||
inc['network.totalTime'] = time;
|
|
||||||
inc['network.incomingBytes'] = incomingBytes;
|
|
||||||
inc['network.outgoingBytes'] = outgoingBytes;
|
|
||||||
|
|
||||||
await update(inc);
|
|
||||||
}
|
|
Loading…
Reference in a new issue