pinpoint-node-agent
Version:
Pinpoint APM agent for Node.js — distributed tracing, error analysis, and URI statistics
146 lines (116 loc) • 4.25 kB
JavaScript
/**
* 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
}