UNPKG

pinpoint-node-agent

Version:

Pinpoint APM agent for Node.js — distributed tracing, error analysis, and URI statistics

146 lines (116 loc) 4.25 kB
/** * Pinpoint Node.js Agent * Copyright 2020-present NAVER Corp. * Apache License v2.0 */ 'use strict' const { UriStatsInfoBuilder } = require('./uri-stats-info-builder') const { SnapshotManager } = require('../snapshot-manager') const { UriStatsSnapshot } = require('./uri-stats-snapshot') const DateNow = require('../../support/date-now') // Default maximum number of distinct URIs tracked per snapshot. // 1024 is a power of two, providing a reasonable balance between memory usage // and the need to cover typical production URI cardinality without tuning. const defaultCapacity = 1024 // Default aggregation window for URI statistics in milliseconds. // 30 seconds is a common metrics collection interval that smooths short spikes // while still providing timely visibility into URI performance. const defaultTimeWindow = 30000 // Maximum number of completed snapshots to retain in memory. // With a 30-second window, 4 snapshots keep roughly the last 2 minutes of data // while keeping memory bounded and polling latency low. const snapshotLimit = 4 class UriStatsRepository { constructor(capacity = defaultCapacity, timeWindow = defaultTimeWindow, config) { this.capacity = capacity this.timeWindow = timeWindow this.completedSnapshotQueue = [] this.snapshotManager = new SnapshotManager( (baseTimestamp) => new UriStatsSnapshot(baseTimestamp, this.capacity), (snapshot) => snapshot.getBaseTimestamp() ) this.config = config } storeUriStats(traceRoot, traceCloseTime) { if (!traceRoot || !Number.isFinite(traceCloseTime)) { return } const uriTemplate = traceRoot.getEnricher('uriStats.uriTemplate') if (!uriTemplate) { return } const traceStartTime = traceRoot.getTraceStartTime() if (!Number.isFinite(traceStartTime) || traceCloseTime < traceStartTime) { return } const builder = new UriStatsInfoBuilder(uriTemplate, traceStartTime, traceCloseTime) const httpMethod = traceRoot.getEnricher('uriStats.method') if (httpMethod) { builder.setMethod(httpMethod) } if (traceRoot.hasErrorCode()) { builder.errorResponse() } builder.setConfig(this.config) this.store(builder.build()) } store(uriStatsInfo) { if (!uriStatsInfo) { return } const baseTimestamp = this.calculateBaseTimestamp(DateNow.now()) this.checkAndFlushSnapshot(baseTimestamp) const snapshot = this.snapshotManager.getCurrent(baseTimestamp) snapshot.add(uriStatsInfo) } calculateBaseTimestamp(timestamp) { return Math.floor(timestamp / this.timeWindow) * this.timeWindow } checkAndFlushSnapshot(timestamp) { const completedSnapshot = this.snapshotManager.takeSnapshot(timestamp) if (completedSnapshot) { this.addCompletedSnapshot(completedSnapshot) } } addCompletedSnapshot(snapshot) { if (this.completedSnapshotQueue.length >= snapshotLimit) { this.completedSnapshotQueue.shift() } this.completedSnapshotQueue.push(snapshot) } poll() { const baseTimestamp = this.calculateBaseTimestamp(DateNow.now()) this.checkAndFlushSnapshot(baseTimestamp) return this.completedSnapshotQueue.shift() } getTimeWindow() { return this.timeWindow } } class NullObjectRepository { storeUriStats() { } poll() { } getTimeWindow() { return defaultTimeWindow } } class UriStatsRepositoryBuilder { static nullObject = new NullObjectRepository() constructor(config) { this.config = config } build() { if (this.config.isUriStatsEnabled() === false) { return UriStatsRepositoryBuilder.nullObject } const capacity = this.config.capacity ?? defaultCapacity const timeWindow = this.config.timeWindow ?? defaultTimeWindow return new UriStatsRepository(capacity, timeWindow, this.config) } } module.exports = { UriStatsRepository, UriStatsRepositoryBuilder }