/** * Copyright (c) 2025 Databricks Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import IClientContext from '../contracts/IClientContext'; import { TelemetryEvent } from './types'; import DatabricksTelemetryExporter from './DatabricksTelemetryExporter'; /** * Aggregates telemetry events by statement_id and manages batching/flushing. * * Overflow policy — when the pending buffer hits `maxPendingMetrics`, error * metrics are preserved preferentially over connection/statement metrics. * The first-failure error is usually the most valuable signal in post-mortem * debugging; dropping it FIFO would defeat the purpose of capture. */ export default class MetricsAggregator { private context; private exporter; private statementMetrics; private pendingMetrics; private flushTimer; private flushInFlight; private closed; private closing; private batchSize; private flushIntervalMs; private maxPendingMetrics; private maxErrorsPerStatement; private statementTtlMs; private maxStatementMetrics; private beforeExitHandler; private droppedMetrics; private evictedStatements; private lastReportedDrops; private lastReportedEvictions; constructor(context: IClientContext, exporter: DatabricksTelemetryExporter); processEvent(event: TelemetryEvent): void; private processConnectionEvent; private processErrorEvent; private pushBoundedError; private processStatementEvent; private getOrCreateStatementDetails; /** * Drop the oldest entry by insertion order to make room. Emits its buffered * errors as standalone metrics first so the first-failure signal survives. * Map iteration order is insertion order in JS. */ private evictOldestStatement; /** * Drop entries older than `statementTtlMs`, emitting their buffered error * events as standalone metrics first so the first-failure signal survives * the eviction. Called from the periodic flush timer so idle clients * don't leak orphan entries. */ private evictExpiredStatements; /** * Operator-visible snapshot of aggregator state. Returned synchronously so * a health-check endpoint or shutdown summary can include it without * awaiting anything. * * - `pendingMetricsCount` : current buffer depth (0..maxPendingMetrics). * - `inFlightStatements` : open statement aggregations (0..maxStatementMetrics). * - `droppedMetrics` : cumulative count of metrics dropped due to * buffer overflow since start. * - `evictedStatements` : cumulative count of statements evicted due * to TTL or map-cap, since start. */ getStats(): { pendingMetricsCount: number; inFlightStatements: number; droppedMetrics: number; evictedStatements: number; }; /** * Emit a warn-level summary if drops/evictions occurred since the last * report. Operators running on `LogLevel.info` (the driver default) need * to see capacity events without enabling debug. */ private maybeWarnOnCapacityEvents; completeStatement(statementId: string): void; /** * Append `metric` to the pending buffer, enforcing `maxPendingMetrics`. * * Overflow drops the oldest non-error entry (single `splice` — no new * allocation). Under an all-error buffer it falls back to dropping the * oldest entry at index 0. */ private addPendingMetric; private findDropIndex; /** * Drain the pending buffer and return a promise that resolves when the * exporter finishes with the drained batch. `close()` awaits this so * `process.exit()` after `client.close()` doesn't truncate the POST. */ flush(resetTimer?: boolean): Promise; private runFlush; private startFlushTimer; close(): Promise; }