class StatsCounters { constructor() { // [0] dbWritten, [1] filtered, [2] kafkaPulled, [3] dbWriteFailed, // [4] g4HotWritten, [5] g4HotWriteFailed, [6] roomStatusWritten, // [7] roomStatusFailed, [8] g4HotErrorTableInserted, // [9] g5Written, [10] g5WriteFailed // [0] dbWritten, [1] filtered, [2] kafkaPulled, [3] dbWriteFailed, // [4] g4HotWritten, [5] g4HotWriteFailed, [6] roomStatusWritten, // [7] roomStatusFailed, [8] g4HotErrorTableInserted this._minuteBuf = new SharedArrayBuffer(BigInt64Array.BYTES_PER_ELEMENT * 11); this._minute = new BigInt64Array(this._minuteBuf); } _inc(slot, n = 1) { const v = BigInt(Math.max(0, Number(n) || 0)); if (v === 0n) return; Atomics.add(this._minute, slot, v); } incDbWritten(n = 1) { this._inc(0, n); } incFiltered(n = 1) { this._inc(1, n); } incKafkaPulled(n = 1) { this._inc(2, n); } incDbWriteFailed(n = 1) { this._inc(3, n); } incG4HotWritten(n = 1) { this._inc(4, n); } incG4HotWriteFailed(n = 1) { this._inc(5, n); } incRoomStatusWritten(n = 1) { this._inc(6, n); } incRoomStatusFailed(n = 1) { this._inc(7, n); } incG4HotErrorTableInserted(n = 1) { this._inc(8, n); } incG5Written(n = 1) { this._inc(9, n); } incG5WriteFailed(n = 1) { this._inc(10, n); } snapshotAndResetMinute() { const dbWritten = Atomics.exchange(this._minute, 0, 0n); const filtered = Atomics.exchange(this._minute, 1, 0n); const kafkaPulled = Atomics.exchange(this._minute, 2, 0n); const dbWriteFailed = Atomics.exchange(this._minute, 3, 0n); const g4HotWritten = Atomics.exchange(this._minute, 4, 0n); const g4HotWriteFailed = Atomics.exchange(this._minute, 5, 0n); const roomStatusWritten = Atomics.exchange(this._minute, 6, 0n); const roomStatusFailed = Atomics.exchange(this._minute, 7, 0n); const g4HotErrorTableInserted = Atomics.exchange(this._minute, 8, 0n); const g5Written = Atomics.exchange(this._minute, 9, 0n); const g5WriteFailed = Atomics.exchange(this._minute, 10, 0n); return { dbWritten, filtered, kafkaPulled, dbWriteFailed, g4HotWritten, g4HotWriteFailed, roomStatusWritten, roomStatusFailed, g4HotErrorTableInserted, g5Written, g5WriteFailed }; } } const pad2 = (n) => String(n).padStart(2, '0'); const pad3 = (n) => String(n).padStart(3, '0'); const formatTimestamp = (d) => { const year = d.getFullYear(); const month = pad2(d.getMonth() + 1); const day = pad2(d.getDate()); const hour = pad2(d.getHours()); const minute = pad2(d.getMinutes()); const second = pad2(d.getSeconds()); const ms = pad3(d.getMilliseconds()); return `${year}-${month}-${day} ${hour}:${minute}:${second}.${ms}`; }; class StatsReporter { constructor({ redis, stats }) { this.redis = redis; this.stats = stats; this._timer = null; this._running = false; this._lastFlushMinute = null; } start() { if (this._running) return; this._running = true; this._scheduleNext(); } stop() { this._running = false; if (this._timer) { clearTimeout(this._timer); this._timer = null; } } flushOnce() { if (!this.redis?.isEnabled?.()) return; const now = Date.now(); const minuteKey = Math.floor(now / 60_000); if (this._lastFlushMinute === minuteKey) { return; } const { dbWritten, filtered, kafkaPulled, dbWriteFailed, g4HotWritten, g4HotWriteFailed, roomStatusWritten, roomStatusFailed, g4HotErrorTableInserted, g5Written, g5WriteFailed } = this.stats.snapshotAndResetMinute(); this._lastFlushMinute = minuteKey; const ts = formatTimestamp(new Date()); this.redis.pushConsoleLog?.({ level: 'info', message: `[STATS] ${ts} Legacy写入量: ${dbWritten}条`, metadata: { module: 'stats' } }); this.redis.pushConsoleLog?.({ level: 'info', message: `[STATS] ${ts} Legacy写入失败量: ${dbWriteFailed}条`, metadata: { module: 'stats' } }); this.redis.pushConsoleLog?.({ level: 'info', message: `[STATS] ${ts} G4Hot写入量: ${g4HotWritten}条`, metadata: { module: 'stats' } }); this.redis.pushConsoleLog?.({ level: 'info', message: `[STATS] ${ts} G4Hot写入失败量: ${g4HotWriteFailed}条`, metadata: { module: 'stats' } }); this.redis.pushConsoleLog?.({ level: 'info', message: `[STATS] ${ts} G5写入量: ${g5Written}条`, metadata: { module: 'stats' } }); this.redis.pushConsoleLog?.({ level: 'info', message: `[STATS] ${ts} G5写入失败量: ${g5WriteFailed}条`, metadata: { module: 'stats' } }); this.redis.pushConsoleLog?.({ level: 'info', message: `[STATS] ${ts} RoomStatus写入量: ${roomStatusWritten}条`, metadata: { module: 'stats' } }); this.redis.pushConsoleLog?.({ level: 'info', message: `[STATS] ${ts} RoomStatus失败量: ${roomStatusFailed}条`, metadata: { module: 'stats' } }); this.redis.pushConsoleLog?.({ level: 'info', message: `[STATS] ${ts} G4Hot错误表插入量: ${g4HotErrorTableInserted}条`, metadata: { module: 'stats' } }); this.redis.pushConsoleLog?.({ level: 'info', message: `[STATS] ${ts} 数据过滤量: ${filtered}条`, metadata: { module: 'stats' } }); this.redis.pushConsoleLog?.({ level: 'info', message: `[STATS] ${ts} Kafka拉取量: ${kafkaPulled}条`, metadata: { module: 'stats' } }); } _scheduleNext() { if (!this._running) return; if (this._timer) return; const now = Date.now(); const delay = 60_000 - (now % 60_000); this._timer = setTimeout(() => { this._timer = null; try { this.flushOnce(); } catch (err) { this.redis?.pushConsoleLog?.({ level: 'warn', message: `[ERROR] ${formatTimestamp(new Date())} 统计任务异常: ${String(err?.message ?? err)}`, metadata: { module: 'stats' }, }); } finally { this._scheduleNext(); } }, delay); } } export { StatsCounters, StatsReporter, formatTimestamp };