feat(event-buffer): enhance with enterprise-grade features

- Add environment variable configuration (BAILEYS_BUFFER_*)
- Implement destroy() method for proper resource cleanup
- Add force flush capability with flush(force) parameter
- Implement buffer overflow detection and prevention
- Add adaptive timeout algorithm based on event rate
- Replace historyCache.clear() with LRU cache cleanup
- Integrate Prometheus metrics for buffer monitoring
- Add isDestroyed state protection
- Fix createBufferedFunction to prevent orphaned timers
- Add comprehensive statistics tracking (getStatistics())
- Add configuration accessor (getConfig())

Enterprise features:
- Configurable buffer size limits (maxBufferSize)
- Configurable timeout ranges (min/max)
- LRU cleanup ratio configuration
- Buffer overflow warning threshold
- Adaptive timeout based on event rate
This commit is contained in:
Claude
2026-01-20 03:45:52 +00:00
parent 8a705e5a1e
commit 11e7c71fd1
2 changed files with 686 additions and 41 deletions
+568 -41
View File
@@ -16,6 +16,60 @@ import type { ILogger } from './logger'
import { updateMessageWithReaction, updateMessageWithReceipt } from './messages'
import { isRealMessage, shouldIncrementChatUnread } from './process-message'
// ============================================================================
// BUFFER CONFIGURATION - Environment Variable Support
// ============================================================================
/**
* Buffer configuration loaded from environment variables
* Allows runtime customization without code changes
*/
export interface BufferConfig {
/** Maximum buffer timeout in milliseconds */
bufferTimeoutMs: number
/** Minimum buffer timeout for adaptive algorithm */
minBufferTimeoutMs: number
/** Maximum buffer timeout for adaptive algorithm */
maxBufferTimeoutMs: number
/** Maximum history cache size before cleanup */
maxHistoryCacheSize: number
/** Maximum events before forcing a flush (overflow protection) */
maxBufferSize: number
/** Debounce delay for flush after buffered function */
flushDebounceMs: number
/** Enable adaptive timeout based on load */
enableAdaptiveTimeout: boolean
/** Enable Prometheus metrics */
enableMetrics: boolean
/** LRU cleanup percentage (0-1) when cache exceeds max */
lruCleanupRatio: number
/** Warn threshold for buffer size (percentage of max) */
bufferWarnThreshold: number
}
/**
* Load buffer configuration from environment variables
* Uses BAILEYS_BUFFER_* prefix for consistency
*/
export function loadBufferConfig(): BufferConfig {
return {
bufferTimeoutMs: parseInt(process.env.BAILEYS_BUFFER_TIMEOUT_MS || '30000', 10),
minBufferTimeoutMs: parseInt(process.env.BAILEYS_BUFFER_MIN_TIMEOUT_MS || '5000', 10),
maxBufferTimeoutMs: parseInt(process.env.BAILEYS_BUFFER_MAX_TIMEOUT_MS || '60000', 10),
maxHistoryCacheSize: parseInt(process.env.BAILEYS_BUFFER_MAX_HISTORY_CACHE || '10000', 10),
maxBufferSize: parseInt(process.env.BAILEYS_BUFFER_MAX_SIZE || '5000', 10),
flushDebounceMs: parseInt(process.env.BAILEYS_BUFFER_FLUSH_DEBOUNCE_MS || '100', 10),
enableAdaptiveTimeout: process.env.BAILEYS_BUFFER_ADAPTIVE_TIMEOUT !== 'false',
enableMetrics: process.env.BAILEYS_BUFFER_METRICS === 'true',
lruCleanupRatio: parseFloat(process.env.BAILEYS_BUFFER_LRU_CLEANUP_RATIO || '0.2'),
bufferWarnThreshold: parseFloat(process.env.BAILEYS_BUFFER_WARN_THRESHOLD || '0.8')
}
}
// ============================================================================
// BUFFERABLE EVENTS
// ============================================================================
const BUFFERABLE_EVENT = [
'messaging-history.set',
'chats.upsert',
@@ -44,95 +98,437 @@ type BaileysEventData = Partial<BaileysEventMap>
const BUFFERABLE_EVENT_SET = new Set<BaileysEvent>(BUFFERABLE_EVENT)
// ============================================================================
// BUFFER STATISTICS - For Metrics and Monitoring
// ============================================================================
/**
* Statistics about buffer operations for monitoring and debugging
*/
export interface BufferStatistics {
/** Total number of flushes performed */
totalFlushes: number
/** Total number of forced flushes (overflow/timeout) */
forcedFlushes: number
/** Total events buffered */
totalEventsBuffered: number
/** Current buffer size */
currentBufferSize: number
/** Peak buffer size reached */
peakBufferSize: number
/** Total buffer overflows prevented */
overflowsDetected: number
/** Current adaptive timeout */
currentTimeout: number
/** History cache size */
historyCacheSize: number
/** LRU cleanups performed */
lruCleanups: number
/** Average events per flush */
avgEventsPerFlush: number
/** Buffer creation timestamp */
createdAt: number
/** Last flush timestamp */
lastFlushAt: number | null
}
// ============================================================================
// EXTENDED EVENT EMITTER TYPE
// ============================================================================
type BaileysBufferableEventEmitter = BaileysEventEmitter & {
/** Use to process events in a batch */
process(handler: (events: BaileysEventData) => void | Promise<void>): () => void
/**
* starts buffering events, call flush() to release them
* */
*/
buffer(): void
/** buffers all events till the promise completes */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
createBufferedFunction<A extends any[], T>(work: (...args: A) => Promise<T>): (...args: A) => Promise<T>
/**
* flushes all buffered events
* @param force - If true, flush even if buffer is empty or not buffering
* @returns returns true if the flush actually happened, otherwise false
*/
flush(): boolean
flush(force?: boolean): boolean
/** is there an ongoing buffer */
isBuffering(): boolean
/**
* Destroy the buffer and clean up all resources
* CRITICAL: Call this when done to prevent memory leaks
*/
destroy(): void
/** Check if buffer has been destroyed */
isDestroyed(): boolean
/** Get buffer statistics for monitoring */
getStatistics(): BufferStatistics
/** Get current configuration */
getConfig(): BufferConfig
}
// ============================================================================
// LRU CACHE ENTRY WITH TIMESTAMP
// ============================================================================
interface LRUCacheEntry {
key: string
timestamp: number
}
/**
* Simple LRU tracking for history cache
* Tracks access times to enable partial cleanup
*/
class LRUTracker {
private entries: Map<string, number> = new Map()
add(key: string): void {
this.entries.set(key, Date.now())
}
has(key: string): boolean {
return this.entries.has(key)
}
touch(key: string): void {
if (this.entries.has(key)) {
this.entries.set(key, Date.now())
}
}
get size(): number {
return this.entries.size
}
/**
* Remove oldest entries up to the specified ratio
* @param ratio - Percentage of entries to remove (0-1)
* @returns Array of removed keys
*/
cleanup(ratio: number): string[] {
const removeCount = Math.floor(this.entries.size * ratio)
if (removeCount === 0) return []
// Sort by timestamp (oldest first)
const sorted: LRUCacheEntry[] = []
this.entries.forEach((timestamp, key) => {
sorted.push({ key, timestamp })
})
sorted.sort((a, b) => a.timestamp - b.timestamp)
// Remove oldest entries
const removed: string[] = []
for (let i = 0; i < removeCount && i < sorted.length; i++) {
const entry = sorted[i]
if (entry) {
this.entries.delete(entry.key)
removed.push(entry.key)
}
}
return removed
}
clear(): void {
this.entries.clear()
}
}
// ============================================================================
// ADAPTIVE TIMEOUT ALGORITHM
// ============================================================================
/**
* Adaptive timeout calculator based on buffer activity
* Adjusts timeout based on event rate to optimize batching
*/
class AdaptiveTimeoutCalculator {
private eventTimestamps: number[] = []
private readonly windowSize = 100 // Track last 100 events
private readonly minTimeout: number
private readonly maxTimeout: number
private currentTimeout: number
constructor(minTimeout: number, maxTimeout: number, initialTimeout: number) {
this.minTimeout = minTimeout
this.maxTimeout = maxTimeout
this.currentTimeout = initialTimeout
}
/**
* Record an event and recalculate optimal timeout
*/
recordEvent(): void {
const now = Date.now()
this.eventTimestamps.push(now)
// Keep only recent events
if (this.eventTimestamps.length > this.windowSize) {
this.eventTimestamps.shift()
}
this.recalculateTimeout()
}
/**
* Get current calculated timeout
*/
getTimeout(): number {
return this.currentTimeout
}
/**
* Recalculate timeout based on event rate
* High rate = shorter timeout (flush more often)
* Low rate = longer timeout (batch more events)
*/
private recalculateTimeout(): void {
if (this.eventTimestamps.length < 2) {
return // Not enough data
}
const oldest = this.eventTimestamps[0]!
const newest = this.eventTimestamps[this.eventTimestamps.length - 1]!
const timeSpan = newest - oldest
if (timeSpan === 0) return
// Calculate events per second
const eventsPerSecond = (this.eventTimestamps.length / timeSpan) * 1000
// Adjust timeout inversely to event rate
// High rate (>100 eps) = min timeout
// Low rate (<1 eps) = max timeout
if (eventsPerSecond > 100) {
this.currentTimeout = this.minTimeout
} else if (eventsPerSecond < 1) {
this.currentTimeout = this.maxTimeout
} else {
// Linear interpolation between min and max
const ratio = Math.log10(eventsPerSecond + 1) / 2 // 0 to 1 scale
this.currentTimeout = this.maxTimeout - (this.maxTimeout - this.minTimeout) * ratio
}
}
reset(): void {
this.eventTimestamps = []
this.currentTimeout = (this.minTimeout + this.maxTimeout) / 2
}
}
// ============================================================================
// MAIN EVENT BUFFER FACTORY
// ============================================================================
/**
* The event buffer logically consolidates different events into a single event
* making the data processing more efficient.
*
* Enterprise-grade features:
* - Environment variable configuration
* - Proper resource cleanup (destroy method)
* - Buffer overflow detection and prevention
* - Adaptive timeout based on event rate
* - LRU cache cleanup instead of full clear
* - Prometheus metrics integration
* - Force flush capability
* - Comprehensive statistics
*/
export const makeEventBuffer = (logger: ILogger): BaileysBufferableEventEmitter => {
export const makeEventBuffer = (
logger: ILogger,
configOverride?: Partial<BufferConfig>
): BaileysBufferableEventEmitter => {
// Load configuration
const config: BufferConfig = {
...loadBufferConfig(),
...configOverride
}
const ev = new EventEmitter()
const historyCache = new Set<string>()
const historyCache = new LRUTracker()
let data = makeBufferData()
let isBuffering = false
let destroyed = false
let bufferTimeout: NodeJS.Timeout | null = null
let flushPendingTimeout: NodeJS.Timeout | null = null // Add a specific timer for the debounced flush to prevent leak
let flushPendingTimeout: NodeJS.Timeout | null = null
let bufferCount = 0
const MAX_HISTORY_CACHE_SIZE = 10000 // Limit the history cache size to prevent memory bloat
const BUFFER_TIMEOUT_MS = 30000 // 30 seconds
let currentEventCount = 0
// take the generic event and fire it as a baileys event
// Statistics tracking
const stats: BufferStatistics = {
totalFlushes: 0,
forcedFlushes: 0,
totalEventsBuffered: 0,
currentBufferSize: 0,
peakBufferSize: 0,
overflowsDetected: 0,
currentTimeout: config.bufferTimeoutMs,
historyCacheSize: 0,
lruCleanups: 0,
avgEventsPerFlush: 0,
createdAt: Date.now(),
lastFlushAt: null
}
// Adaptive timeout calculator
const adaptiveTimeout = new AdaptiveTimeoutCalculator(
config.minBufferTimeoutMs,
config.maxBufferTimeoutMs,
config.bufferTimeoutMs
)
// Metrics integration (lazy loaded to avoid circular deps)
let metricsModule: typeof import('./prometheus-metrics') | null = null
if (config.enableMetrics) {
import('./prometheus-metrics').then(m => {
metricsModule = m
}).catch(() => {
logger.debug('Prometheus metrics not available for event buffer')
})
}
// Helper to record metrics
const recordMetrics = (eventType: string, count: number) => {
if (metricsModule) {
metricsModule.recordEventBuffered(eventType, count)
}
}
const recordFlushMetrics = (eventCount: number, forced: boolean) => {
if (metricsModule) {
metricsModule.recordBufferFlush(eventCount, forced)
}
}
// Cleanup helper
const clearAllTimers = () => {
if (bufferTimeout) {
clearTimeout(bufferTimeout)
bufferTimeout = null
}
if (flushPendingTimeout) {
clearTimeout(flushPendingTimeout)
flushPendingTimeout = null
}
}
// Take the generic event and fire it as a baileys event
ev.on('event', (map: BaileysEventData) => {
for (const event in map) {
ev.emit(event, map[event as keyof BaileysEventMap])
}
})
/**
* Check for buffer overflow and handle it
*/
function checkBufferOverflow(): boolean {
if (currentEventCount >= config.maxBufferSize) {
stats.overflowsDetected++
logger.warn({
currentSize: currentEventCount,
maxSize: config.maxBufferSize
}, 'Buffer overflow detected, forcing flush')
flush(true)
return true
}
// Warn if approaching threshold
const threshold = config.maxBufferSize * config.bufferWarnThreshold
if (currentEventCount >= threshold && currentEventCount < config.maxBufferSize) {
logger.debug({
currentSize: currentEventCount,
threshold,
maxSize: config.maxBufferSize
}, 'Buffer approaching overflow threshold')
}
return false
}
/**
* Perform LRU cleanup on history cache
*/
function cleanupHistoryCache(): void {
if (historyCache.size > config.maxHistoryCacheSize) {
const removed = historyCache.cleanup(config.lruCleanupRatio)
stats.lruCleanups++
logger.debug({
removed: removed.length,
remaining: historyCache.size,
maxSize: config.maxHistoryCacheSize
}, 'LRU cleanup performed on history cache')
}
}
function buffer() {
if (destroyed) {
logger.warn('Attempted to buffer on destroyed event buffer')
return
}
if (!isBuffering) {
logger.debug('Event buffer activated')
isBuffering = true
bufferCount = 0
currentEventCount = 0
if (bufferTimeout) {
clearTimeout(bufferTimeout)
}
clearAllTimers()
// Use adaptive timeout if enabled
const timeout = config.enableAdaptiveTimeout
? adaptiveTimeout.getTimeout()
: config.bufferTimeoutMs
stats.currentTimeout = timeout
bufferTimeout = setTimeout(() => {
if (isBuffering) {
logger.warn('Buffer timeout reached, auto-flushing')
flush()
logger.warn({ timeout }, 'Buffer timeout reached, auto-flushing')
stats.forcedFlushes++
flush(true)
}
}, BUFFER_TIMEOUT_MS)
}, timeout)
}
// Always increment count when requested
bufferCount++
}
function flush() {
if (!isBuffering) {
function flush(force: boolean = false): boolean {
if (destroyed) {
logger.warn('Attempted to flush destroyed event buffer')
return false
}
logger.debug({ bufferCount }, 'Flushing event buffer')
if (!isBuffering && !force) {
return false
}
const eventCount = currentEventCount
logger.debug({ bufferCount, eventCount, force }, 'Flushing event buffer')
isBuffering = false
bufferCount = 0
currentEventCount = 0
stats.totalFlushes++
stats.lastFlushAt = Date.now()
// Clear timeout
if (bufferTimeout) {
clearTimeout(bufferTimeout)
bufferTimeout = null
}
// Update average events per flush
stats.avgEventsPerFlush = stats.totalFlushes > 0
? (stats.avgEventsPerFlush * (stats.totalFlushes - 1) + eventCount) / stats.totalFlushes
: eventCount
if (flushPendingTimeout) {
clearTimeout(flushPendingTimeout)
flushPendingTimeout = null
}
// Clear timeouts
clearAllTimers()
// Clear history cache if it exceeds the max size
if (historyCache.size > MAX_HISTORY_CACHE_SIZE) {
logger.debug({ cacheSize: historyCache.size }, 'Clearing history cache')
historyCache.clear()
}
// Perform LRU cleanup instead of full clear
cleanupHistoryCache()
stats.historyCacheSize = historyCache.size
// Record metrics
recordFlushMetrics(eventCount, force)
const newData = makeBufferData()
const chatUpdates = Object.values(data.chatUpdates)
@@ -152,13 +548,49 @@ export const makeEventBuffer = (logger: ILogger): BaileysBufferableEventEmitter
data = newData
logger.trace({ conditionalChatUpdatesLeft }, 'released buffered events')
logger.trace({ conditionalChatUpdatesLeft, eventCount }, 'released buffered events')
// Reset adaptive timeout calculator on flush
if (config.enableAdaptiveTimeout) {
adaptiveTimeout.reset()
}
return true
}
function destroy(): void {
if (destroyed) {
logger.debug('Event buffer already destroyed')
return
}
logger.debug('Destroying event buffer')
destroyed = true
// Flush any remaining events
if (isBuffering) {
flush(true)
}
// Clear all timers
clearAllTimers()
// Clear all data structures
data = makeBufferData()
historyCache.clear()
// Remove all event listeners
ev.removeAllListeners()
logger.debug('Event buffer destroyed successfully')
}
return {
process(handler) {
if (destroyed) {
throw new Error('Cannot process on destroyed event buffer')
}
const listener = async (map: BaileysEventData) => {
await handler(map)
}
@@ -169,6 +601,11 @@ export const makeEventBuffer = (logger: ILogger): BaileysBufferableEventEmitter
}
},
emit<T extends BaileysEvent>(event: BaileysEvent, evData: BaileysEventMap[T]) {
if (destroyed) {
logger.warn({ event }, 'Attempted to emit on destroyed event buffer')
return false
}
// Check if this is a messages.upsert with a different type than what's buffered
// If so, flush the buffered messages first to avoid type overshadowing
if (event === 'messages.upsert') {
@@ -192,6 +629,28 @@ export const makeEventBuffer = (logger: ILogger): BaileysBufferableEventEmitter
}
if (isBuffering && BUFFERABLE_EVENT_SET.has(event)) {
// Record event for adaptive timeout
if (config.enableAdaptiveTimeout) {
adaptiveTimeout.recordEvent()
}
// Track statistics
currentEventCount++
stats.totalEventsBuffered++
stats.currentBufferSize = currentEventCount
if (currentEventCount > stats.peakBufferSize) {
stats.peakBufferSize = currentEventCount
}
// Record metrics
recordMetrics(event, 1)
// Check for overflow
if (checkBufferOverflow()) {
// Buffer was flushed due to overflow, re-buffer this event
buffer()
}
append(data, historyCache, event as BufferableEvent, evData, logger)
return true
}
@@ -199,44 +658,93 @@ export const makeEventBuffer = (logger: ILogger): BaileysBufferableEventEmitter
return ev.emit('event', { [event]: evData })
},
isBuffering() {
return isBuffering
return isBuffering && !destroyed
},
isDestroyed() {
return destroyed
},
buffer,
flush,
destroy,
getStatistics() {
return {
...stats,
currentBufferSize: currentEventCount,
historyCacheSize: historyCache.size,
currentTimeout: config.enableAdaptiveTimeout
? adaptiveTimeout.getTimeout()
: config.bufferTimeoutMs
}
},
getConfig() {
return { ...config }
},
createBufferedFunction(work) {
// Track the timeout for this specific function call
let functionTimeout: NodeJS.Timeout | null = null
return async (...args) => {
if (destroyed) {
throw new Error('Cannot execute buffered function on destroyed event buffer')
}
buffer()
try {
const result = await work(...args)
// If this is the only buffer, flush after a small delay
if (bufferCount === 1) {
setTimeout(() => {
if (isBuffering && bufferCount === 1) {
// Clear any existing function timeout to prevent orphaned timers
if (functionTimeout) {
clearTimeout(functionTimeout)
}
functionTimeout = setTimeout(() => {
functionTimeout = null
if (isBuffering && bufferCount === 1 && !destroyed) {
flush()
}
}, 100) // Small delay to allow nested buffers
}, config.flushDebounceMs)
}
return result
} catch (error) {
// Clear timeout on error
if (functionTimeout) {
clearTimeout(functionTimeout)
functionTimeout = null
}
throw error
} finally {
bufferCount = Math.max(0, bufferCount - 1)
if (bufferCount === 0) {
// Only schedule ONE timeout, not 10,000
if (bufferCount === 0 && !destroyed) {
// Only schedule ONE timeout, not multiple
if (!flushPendingTimeout) {
flushPendingTimeout = setTimeout(flush, 100)
flushPendingTimeout = setTimeout(() => {
flushPendingTimeout = null
if (!destroyed) {
flush()
}
}, config.flushDebounceMs)
}
}
}
}
},
on: (...args) => ev.on(...args),
on: (...args) => {
if (destroyed) {
throw new Error('Cannot add listener to destroyed event buffer')
}
return ev.on(...args)
},
off: (...args) => ev.off(...args),
removeAllListeners: (...args) => ev.removeAllListeners(...args)
}
}
// ============================================================================
// BUFFER DATA STRUCTURE
// ============================================================================
const makeBufferData = (): BufferedEventData => {
return {
historySets: {
@@ -260,9 +768,13 @@ const makeBufferData = (): BufferedEventData => {
}
}
// ============================================================================
// EVENT APPEND LOGIC
// ============================================================================
function append<E extends BufferableEvent>(
data: BufferedEventData,
historyCache: Set<string>,
historyCache: LRUTracker,
event: E,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
eventData: any,
@@ -282,6 +794,9 @@ function append<E extends BufferableEvent>(
historyCache.add(id)
absorbingChatUpdate(chat)
} else {
// Touch existing entry for LRU tracking
historyCache.touch(id)
}
}
@@ -295,6 +810,8 @@ function append<E extends BufferableEvent>(
if (!historyCache.has(historyContactId) || hasAnyName) {
data.historySets.contacts[contact.id] = contact
historyCache.add(historyContactId)
} else {
historyCache.touch(historyContactId)
}
}
}
@@ -305,6 +822,8 @@ function append<E extends BufferableEvent>(
if (!existingMsg && !historyCache.has(key)) {
data.historySets.messages[key] = message
historyCache.add(key)
} else {
historyCache.touch(key)
}
}
@@ -589,6 +1108,10 @@ function append<E extends BufferableEvent>(
}
}
// ============================================================================
// EVENT CONSOLIDATION
// ============================================================================
function consolidateEvents(data: BufferedEventData) {
const map: BaileysEventData = {}
@@ -670,6 +1193,10 @@ function consolidateEvents(data: BufferedEventData) {
return map
}
// ============================================================================
// UTILITY FUNCTIONS
// ============================================================================
function concatChats<C extends Partial<Chat>>(a: C, b: Partial<Chat>) {
if (
b.unreadCount === null && // neutralize unread counter
+118
View File
@@ -1468,6 +1468,124 @@ export function trackOperation(
}
}
// ============================================
// Event Buffer Metrics Integration
// ============================================
// Event buffer metrics (lazy initialized)
let eventBufferMetrics: {
eventsBuffered: Counter | null
bufferFlushes: Counter | null
bufferFlushEvents: Histogram | null
bufferCurrentSize: Gauge | null
bufferPeakSize: Gauge | null
bufferHistoryCacheSize: Gauge | null
bufferOverflows: Counter | null
bufferLruCleanups: Counter | null
} | null = null
function getEventBufferMetrics() {
if (!eventBufferMetrics) {
eventBufferMetrics = {
eventsBuffered: new Counter(
'events_buffered_total',
'Total number of events buffered',
['event_type']
),
bufferFlushes: new Counter(
'buffer_flushes_total',
'Total number of buffer flushes',
['forced']
),
bufferFlushEvents: new Histogram(
'buffer_flush_events',
'Number of events per buffer flush',
[],
[1, 5, 10, 25, 50, 100, 250, 500, 1000]
),
bufferCurrentSize: new Gauge(
'buffer_current_size',
'Current number of events in buffer'
),
bufferPeakSize: new Gauge(
'buffer_peak_size',
'Peak buffer size reached'
),
bufferHistoryCacheSize: new Gauge(
'buffer_history_cache_size',
'Current size of history cache'
),
bufferOverflows: new Counter(
'buffer_overflows_total',
'Total number of buffer overflows detected'
),
bufferLruCleanups: new Counter(
'buffer_lru_cleanups_total',
'Total number of LRU cache cleanups performed'
)
}
// Register all metrics
if (eventBufferMetrics.eventsBuffered) baileysMetrics.register(eventBufferMetrics.eventsBuffered)
if (eventBufferMetrics.bufferFlushes) baileysMetrics.register(eventBufferMetrics.bufferFlushes)
if (eventBufferMetrics.bufferFlushEvents) baileysMetrics.register(eventBufferMetrics.bufferFlushEvents)
if (eventBufferMetrics.bufferCurrentSize) baileysMetrics.register(eventBufferMetrics.bufferCurrentSize)
if (eventBufferMetrics.bufferPeakSize) baileysMetrics.register(eventBufferMetrics.bufferPeakSize)
if (eventBufferMetrics.bufferHistoryCacheSize) baileysMetrics.register(eventBufferMetrics.bufferHistoryCacheSize)
if (eventBufferMetrics.bufferOverflows) baileysMetrics.register(eventBufferMetrics.bufferOverflows)
if (eventBufferMetrics.bufferLruCleanups) baileysMetrics.register(eventBufferMetrics.bufferLruCleanups)
}
return eventBufferMetrics
}
/**
* Record an event being buffered
* Used by event-buffer.ts for metrics integration
*/
export function recordEventBuffered(eventType: string, count: number = 1): void {
try {
const metrics = getEventBufferMetrics()
metrics.eventsBuffered?.inc({ event_type: eventType }, count)
} catch {
// Metrics not initialized, ignore silently
}
}
/**
* Record a buffer flush operation
* Used by event-buffer.ts for metrics integration
*/
export function recordBufferFlush(eventCount: number, forced: boolean): void {
try {
const metrics = getEventBufferMetrics()
metrics.bufferFlushes?.inc({ forced: forced ? 'true' : 'false' })
metrics.bufferFlushEvents?.observe({}, eventCount)
metrics.bufferCurrentSize?.set({}, 0) // Reset after flush
} catch {
// Metrics not initialized, ignore silently
}
}
/**
* Update buffer statistics gauge
* Used by event-buffer.ts for metrics integration
*/
export function updateBufferStatistics(stats: {
currentSize: number
peakSize: number
historyCacheSize: number
overflowsDetected: number
lruCleanups: number
}): void {
try {
const metrics = getEventBufferMetrics()
metrics.bufferCurrentSize?.set({}, stats.currentSize)
metrics.bufferPeakSize?.set({}, stats.peakSize)
metrics.bufferHistoryCacheSize?.set({}, stats.historyCacheSize)
} catch {
// Metrics not initialized, ignore silently
}
}
// ============================================
// Global Instance
// ============================================