server/core/lib/stat-manager.ts
import Bluebird from 'bluebird'
import { CONFIG } from '@server/initializers/config.js'
import { ActorFollowModel } from '@server/models/actor/actor-follow.js'
import { VideoRedundancyModel } from '@server/models/redundancy/video-redundancy.js'
import { UserModel } from '@server/models/user/user.js'
import { VideoModel } from '@server/models/video/video.js'
import { VideoChannelModel } from '@server/models/video/video-channel.js'
import { VideoCommentModel } from '@server/models/video/video-comment.js'
import { VideoFileModel } from '@server/models/video/video-file.js'
import { VideoPlaylistModel } from '@server/models/video/video-playlist.js'
import { ActivityType, ServerStats, VideoRedundancyStrategyWithManual } from '@peertube/peertube-models'
import { UserRegistrationModel } from '@server/models/user/user-registration.js'
import { AbuseModel } from '@server/models/abuse/abuse.js'
import { pick } from '@peertube/peertube-core-utils'
class StatsManager {
private static instance: StatsManager
private readonly instanceStartDate = new Date()
private readonly inboxMessages = {
processed: 0,
errors: 0,
successes: 0,
waiting: 0,
errorsPerType: this.buildAPPerType(),
successesPerType: this.buildAPPerType()
}
private constructor () {}
updateInboxWaiting (inboxMessagesWaiting: number) {
this.inboxMessages.waiting = inboxMessagesWaiting
}
addInboxProcessedSuccess (type: ActivityType) {
this.inboxMessages.processed++
this.inboxMessages.successes++
this.inboxMessages.successesPerType[type]++
}
addInboxProcessedError (type: ActivityType) {
this.inboxMessages.processed++
this.inboxMessages.errors++
this.inboxMessages.errorsPerType[type]++
}
async getStats () {
const { totalLocalVideos, totalLocalVideoViews, totalVideos } = await VideoModel.getStats()
const { totalLocalVideoComments, totalVideoComments } = await VideoCommentModel.getStats()
const {
totalUsers,
totalDailyActiveUsers,
totalWeeklyActiveUsers,
totalMonthlyActiveUsers,
totalAdmins,
totalModerators
} = await UserModel.getStats()
const { totalInstanceFollowers, totalInstanceFollowing } = await ActorFollowModel.getStats()
const { totalLocalVideoFilesSize } = await VideoFileModel.getStats()
const {
totalLocalVideoChannels,
totalLocalDailyActiveVideoChannels,
totalLocalWeeklyActiveVideoChannels,
totalLocalMonthlyActiveVideoChannels
} = await VideoChannelModel.getStats()
const { totalLocalPlaylists } = await VideoPlaylistModel.getStats()
const videosRedundancyStats = await this.buildRedundancyStats()
const data: ServerStats = {
totalUsers,
totalDailyActiveUsers,
totalWeeklyActiveUsers,
totalMonthlyActiveUsers,
totalModerators: CONFIG.STATS.TOTAL_MODERATORS.ENABLED
? totalModerators
: null,
totalAdmins: CONFIG.STATS.TOTAL_ADMINS.ENABLED
? totalAdmins
: null,
totalLocalVideos,
totalLocalVideoViews,
totalLocalVideoComments,
totalLocalVideoFilesSize,
totalVideos,
totalVideoComments,
totalLocalVideoChannels,
totalLocalDailyActiveVideoChannels,
totalLocalWeeklyActiveVideoChannels,
totalLocalMonthlyActiveVideoChannels,
totalLocalPlaylists,
totalInstanceFollowers,
totalInstanceFollowing,
videosRedundancy: videosRedundancyStats,
...await this.buildAbuseStats(),
...await this.buildRegistrationRequestsStats(),
...this.buildAPStats()
}
return data
}
private buildActivityPubMessagesProcessedPerSecond () {
const now = new Date()
const startedSeconds = (now.getTime() - this.instanceStartDate.getTime()) / 1000
return this.inboxMessages.processed / startedSeconds
}
private buildRedundancyStats () {
const strategies = CONFIG.REDUNDANCY.VIDEOS.STRATEGIES
.map(r => ({
strategy: r.strategy as VideoRedundancyStrategyWithManual,
size: r.size
}))
strategies.push({ strategy: 'manual', size: null })
return Bluebird.mapSeries(strategies, r => {
return VideoRedundancyModel.getStats(r.strategy)
.then(stats => Object.assign(stats, { strategy: r.strategy, totalSize: r.size }))
})
}
private buildAPPerType () {
return {
Create: 0,
Update: 0,
Delete: 0,
Follow: 0,
Accept: 0,
Reject: 0,
Announce: 0,
Undo: 0,
Like: 0,
Dislike: 0,
Flag: 0,
View: 0,
ApproveReply: 0,
RejectReply: 0
}
}
private buildAPStats () {
return {
totalActivityPubMessagesProcessed: this.inboxMessages.processed,
totalActivityPubMessagesSuccesses: this.inboxMessages.successes,
// Dirty, but simpler and with type checking
totalActivityPubCreateMessagesSuccesses: this.inboxMessages.successesPerType.Create,
totalActivityPubUpdateMessagesSuccesses: this.inboxMessages.successesPerType.Update,
totalActivityPubDeleteMessagesSuccesses: this.inboxMessages.successesPerType.Delete,
totalActivityPubFollowMessagesSuccesses: this.inboxMessages.successesPerType.Follow,
totalActivityPubAcceptMessagesSuccesses: this.inboxMessages.successesPerType.Accept,
totalActivityPubRejectMessagesSuccesses: this.inboxMessages.successesPerType.Reject,
totalActivityPubAnnounceMessagesSuccesses: this.inboxMessages.successesPerType.Announce,
totalActivityPubUndoMessagesSuccesses: this.inboxMessages.successesPerType.Undo,
totalActivityPubLikeMessagesSuccesses: this.inboxMessages.successesPerType.Like,
totalActivityPubDislikeMessagesSuccesses: this.inboxMessages.successesPerType.Dislike,
totalActivityPubFlagMessagesSuccesses: this.inboxMessages.successesPerType.Flag,
totalActivityPubViewMessagesSuccesses: this.inboxMessages.successesPerType.View,
totalActivityPubApproveReplyMessagesSuccesses: this.inboxMessages.successesPerType.ApproveReply,
totalActivityPubRejectReplyMessagesSuccesses: this.inboxMessages.successesPerType.RejectReply,
totalActivityPubCreateMessagesErrors: this.inboxMessages.errorsPerType.Create,
totalActivityPubUpdateMessagesErrors: this.inboxMessages.errorsPerType.Update,
totalActivityPubDeleteMessagesErrors: this.inboxMessages.errorsPerType.Delete,
totalActivityPubFollowMessagesErrors: this.inboxMessages.errorsPerType.Follow,
totalActivityPubAcceptMessagesErrors: this.inboxMessages.errorsPerType.Accept,
totalActivityPubRejectMessagesErrors: this.inboxMessages.errorsPerType.Reject,
totalActivityPubAnnounceMessagesErrors: this.inboxMessages.errorsPerType.Announce,
totalActivityPubUndoMessagesErrors: this.inboxMessages.errorsPerType.Undo,
totalActivityPubLikeMessagesErrors: this.inboxMessages.errorsPerType.Like,
totalActivityPubDislikeMessagesErrors: this.inboxMessages.errorsPerType.Dislike,
totalActivityPubFlagMessagesErrors: this.inboxMessages.errorsPerType.Flag,
totalActivityPubViewMessagesErrors: this.inboxMessages.errorsPerType.View,
totalActivityPubApproveReplyMessagesErrors: this.inboxMessages.errorsPerType.ApproveReply,
totalActivityPubRejectReplyMessagesErrors: this.inboxMessages.errorsPerType.RejectReply,
totalActivityPubMessagesErrors: this.inboxMessages.errors,
activityPubMessagesProcessedPerSecond: this.buildActivityPubMessagesProcessedPerSecond(),
totalActivityPubMessagesWaiting: this.inboxMessages.waiting
}
}
private async buildRegistrationRequestsStats () {
if (!CONFIG.STATS.REGISTRATION_REQUESTS.ENABLED) {
return {
averageRegistrationRequestResponseTimeMs: null,
totalRegistrationRequests: null,
totalRegistrationRequestsProcessed: null
}
}
const res = await UserRegistrationModel.getStats()
return pick(res, [ 'averageRegistrationRequestResponseTimeMs', 'totalRegistrationRequests', 'totalRegistrationRequestsProcessed' ])
}
private async buildAbuseStats () {
if (!CONFIG.STATS.ABUSES.ENABLED) {
return {
averageAbuseResponseTimeMs: null,
totalAbuses: null,
totalAbusesProcessed: null
}
}
const res = await AbuseModel.getStats()
return pick(res, [ 'averageAbuseResponseTimeMs', 'totalAbuses', 'totalAbusesProcessed' ])
}
static get Instance () {
return this.instance || (this.instance = new this())
}
}
// ---------------------------------------------------------------------------
export {
StatsManager
}