UNPKG

couchbase

Version:

The official Couchbase Node.js Client Library.

658 lines (657 loc) 25.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ThresholdLoggingTracer = exports.ThresholdLoggingSpan = void 0; const generaltypes_1 = require("./generaltypes"); const observabilitytypes_1 = require("./observabilitytypes"); const observabilityutilities_1 = require("./observabilityutilities"); /* eslint-disable jsdoc/require-jsdoc */ /** * @internal */ var ThresholdLoggingAttributeName; (function (ThresholdLoggingAttributeName) { ThresholdLoggingAttributeName["EncodeDuration"] = "encode_duration_us"; ThresholdLoggingAttributeName["OperationName"] = "operation_name"; ThresholdLoggingAttributeName["OperationId"] = "operation_id"; ThresholdLoggingAttributeName["LastDispatchDuration"] = "last_dispatch_duration_us"; ThresholdLoggingAttributeName["LastLocalId"] = "last_local_id"; ThresholdLoggingAttributeName["LastLocalSocket"] = "last_local_socket"; ThresholdLoggingAttributeName["LastRemoteSocket"] = "last_remote_socket"; ThresholdLoggingAttributeName["LastServerDuration"] = "last_server_duration_us"; ThresholdLoggingAttributeName["Timeout"] = "timeout_ms"; ThresholdLoggingAttributeName["TotalDuration"] = "total_duration_us"; ThresholdLoggingAttributeName["TotalDispatchDuration"] = "total_dispatch_duration_us"; ThresholdLoggingAttributeName["TotalServerDuration"] = "total_server_duration_us"; })(ThresholdLoggingAttributeName || (ThresholdLoggingAttributeName = {})); /** * @internal */ var IgnoredParentSpan; (function (IgnoredParentSpan) { IgnoredParentSpan["ListGetAll"] = "list_get_all"; IgnoredParentSpan["ListGetAt"] = "list_get_at"; IgnoredParentSpan["ListIndexOf"] = "list_index_of"; IgnoredParentSpan["ListPush"] = "list_push"; IgnoredParentSpan["ListRemoveAt"] = "list_remove_at"; IgnoredParentSpan["ListSize"] = "list_size"; IgnoredParentSpan["ListUnshift"] = "list_unshift"; IgnoredParentSpan["MapExists"] = "map_exists"; IgnoredParentSpan["MapGet"] = "map_get"; IgnoredParentSpan["MapGetAll"] = "map_get_all"; IgnoredParentSpan["MapKeys"] = "map_keys"; IgnoredParentSpan["MapRemove"] = "map_remove"; IgnoredParentSpan["MapSet"] = "map_set"; IgnoredParentSpan["MapSize"] = "map_size"; IgnoredParentSpan["MapValues"] = "map_values"; IgnoredParentSpan["QueuePop"] = "queue_pop"; IgnoredParentSpan["QueuePush"] = "queue_push"; IgnoredParentSpan["QueueSize"] = "queue_size"; IgnoredParentSpan["SetAdd"] = "set_add"; IgnoredParentSpan["SetContains"] = "set_contains"; IgnoredParentSpan["SetRemove"] = "set_remove"; IgnoredParentSpan["SetSize"] = "set_size"; IgnoredParentSpan["SetValues"] = "set_values"; })(IgnoredParentSpan || (IgnoredParentSpan = {})); const IGNORED_PARENT_SPAN_VALUES = new Set(Object.values(IgnoredParentSpan)); class ThresholdLoggingSpanSnapshot { constructor(span) { this.name = span.name; this.serviceType = span.serviceType; this.totalDuration = span.totalDuration; this.encodeDuration = span.encodeDuration; this.dispatchDuration = span.dispatchDuration; this.totalDispatchDuration = span.totalDispatchDuration; this.serverDuration = span.serverDuration; this.totalServerDuration = span.totalServerDuration; this.localId = span.localId; this.operationId = span.operationId; this.localSocket = span.localSocket; this.remoteSocket = span.remoteSocket; } } /** * @internal */ class PriorityQueue { constructor(capacity) { this._capacity = capacity !== null && capacity !== void 0 ? capacity : 10; this._queue = new Array(this._capacity); this._size = 0; this._droppedCount = 0; } get droppedCount() { return this._droppedCount; } _getInsertIndex(priority) { let left = 0; let right = this._size; while (left < right) { const mid = (left + right) >>> 1; // Math.floor((left + right) / 2) but slightly better if (this._queue[mid].priority < priority) { left = mid + 1; } else { right = mid; } } return left; } enqueue(item, priority) { if (this._size >= this._capacity) { const lowestPriority = this._queue[0].priority; if (lowestPriority >= priority) { this._droppedCount++; return false; } // shift the queue left for (let i = 0; i < this._size - 1; i++) { this._queue[i] = this._queue[i + 1]; } this._size--; this._droppedCount++; } const insertIdx = this._getInsertIndex(priority); // shift items > insertIdx right for (let i = this._size; i > insertIdx; i--) { this._queue[i] = this._queue[i - 1]; } this._queue[insertIdx] = { item: item, priority: priority }; this._size++; return true; } peek() { return this._size > 0 ? this._queue[0].item : undefined; } drain() { const items = []; for (let i = 0; i < this._size; i++) { items.push(this._queue[i].item); } const droppedCount = this._droppedCount; this._size = 0; this._droppedCount = 0; // Return items in descending priority order (highest duration first) items.reverse(); return [items, droppedCount]; } } /** * @internal */ class ThresholdLoggingReporter { constructor(logger, interval, capacity) { this._logger = logger; this._interval = interval; this._thresholdQueues = new Map(); this._thresholdQueues.set(generaltypes_1.ServiceType.KeyValue, new PriorityQueue(capacity)); this._thresholdQueues.set(generaltypes_1.ServiceType.Query, new PriorityQueue(capacity)); this._thresholdQueues.set(generaltypes_1.ServiceType.Search, new PriorityQueue(capacity)); this._thresholdQueues.set(generaltypes_1.ServiceType.Analytics, new PriorityQueue(capacity)); this._thresholdQueues.set(generaltypes_1.ServiceType.Views, new PriorityQueue(capacity)); this._thresholdQueues.set(generaltypes_1.ServiceType.Management, new PriorityQueue(capacity)); this._thresholdQueues.set(generaltypes_1.ServiceType.Eventing, new PriorityQueue(capacity)); this._stopped = false; } addLogRecord(serviceType, item, totalDuration) { var _a; (_a = this._thresholdQueues.get(serviceType)) === null || _a === void 0 ? void 0 : _a.enqueue(item, totalDuration); } start() { this._timerId = setInterval(this.report.bind(this), this._interval); // let the process exit even if the reporter has not been shutdown this._timerId.unref(); this._stopped = false; } stop() { if (this._stopped) { return; } this._stopped = true; clearInterval(this._timerId); } report(returnReport) { const report = {}; for (const [serviceType, queue] of this._thresholdQueues) { const [items, droppedCount] = queue.drain(); if (items.length > 0) { report[serviceType] = { total_count: items.length + droppedCount, top_requests: items.map((item) => Object.fromEntries(item)), }; } } if (returnReport) { return report; } else { if (Object.keys(report).length > 0) { this._logger.info(JSON.stringify(report)); } } } } /** * @internal */ function serviceTypeFromString(serviceType) { switch (serviceType.toLocaleLowerCase()) { case 'kv': case 'keyValue': return generaltypes_1.ServiceType.KeyValue; case 'query': return generaltypes_1.ServiceType.Query; case 'search': return generaltypes_1.ServiceType.Search; case 'analytics': return generaltypes_1.ServiceType.Analytics; case 'views': return generaltypes_1.ServiceType.Views; case 'management': return generaltypes_1.ServiceType.Management; case 'eventing': return generaltypes_1.ServiceType.Eventing; default: throw new Error('Unrecognized service type.'); } } /** * A RequestSpan implementation that tracks operation timing for threshold logging. * * Works with {@link ThresholdLoggingTracer} to identify and log slow operations * that exceed configured thresholds. * */ class ThresholdLoggingSpan { /** * Creates a new threshold logging span. * * @param name - The name of the operation being traced. * @param tracer - The span's tracer. * @param parentSpan - Optional parent span for hierarchical tracing. * @param startTime - Optional start time; defaults to current time if not provided. */ constructor(name, tracer, parentSpan, startTime) { this._attributes = {}; this._status = { code: observabilitytypes_1.SpanStatusCode.UNSET }; this._name = name; this._tracer = tracer; this._parentSpan = parentSpan; this._startTime = this._getTime(startTime); this._totalDuration = 0; this._totalDispatchDuration = 0; this._totalServerDuration = 0; this._totalEncodeDuration = 0; } /** * Gets the dispatch duration of the request, in microseconds. * * @internal */ get dispatchDuration() { return this._dispatchDuration; } /** * Sets the dispatch duration of the request, in microseconds. * * @internal */ set dispatchDuration(duration) { this._dispatchDuration = duration; this._totalDispatchDuration += this._dispatchDuration; if (this._parentSpan) { this._parentSpan.dispatchDuration = duration; } } /** * Gets the duration spent encoding the request, in microseconds. * * @internal */ get encodeDuration() { return this._encodeDuration; } /** * Sets the duration spent encoding the request, in microseconds. * * @internal */ set encodeDuration(duration) { this._encodeDuration = duration; this._totalEncodeDuration += this._encodeDuration; if (this._parentSpan) { this._parentSpan.encodeDuration = duration; } } /** * Gets the local ID of the request's dispatch span. * * @internal */ get localId() { return this._localId; } /** * Gets the local socket of the request's dispatch span. * * @internal */ get localSocket() { var _a, _b; if (this._peerAddress !== undefined || this._peerPort !== undefined) { const address = (_a = this._peerAddress) !== null && _a !== void 0 ? _a : ''; const port = (_b = this._peerPort) !== null && _b !== void 0 ? _b : ''; return `${address}:${port}`; } } /** * Gets the name of the span. */ get name() { return this._name; } /** * Gets the operation ID of the request's dispatch span. * * @internal */ get operationId() { return this._operationId; } /** * Gets the peer address of the request's dispatch span. * * @internal */ get peerAddress() { return this._peerAddress; } /** * Gets the peer port of the request's dispatch span. * * @internal */ get peerPort() { return this._peerPort; } /** * Gets the remote socket of the request's dispatch span. * * @internal */ get remoteSocket() { var _a, _b; if (this._remoteAddress !== undefined || this._remotePort !== undefined) { const address = (_a = this._remoteAddress) !== null && _a !== void 0 ? _a : ''; const port = (_b = this._remotePort) !== null && _b !== void 0 ? _b : ''; return `${address}:${port}`; } } /** * Gets the server duration of the request, in microseconds. * * @internal */ get serverDuration() { return this._serverDuration; } /** * Gets the Couchbase service type handling this operation. * * @internal */ get serviceType() { return this._serviceType; } /** * Gets the snapshot of the span containing all relevant information for threshold logging. * * @internal */ get snapshot() { return this._snapshot; } /** * Gets the total dispatch duration of the request, in microseconds. * * @internal */ get totalDispatchDuration() { return this._totalDispatchDuration; } /** * Gets the total encoding duration of the request, in microseconds. * * @internal */ get totalEncodeDuration() { return this._totalEncodeDuration; } /** * Gets the total duration of the request, in microseconds. * * @internal */ get totalDuration() { return this._totalDuration; } /** * Gets the total server duration of the request, in microseconds. * * @internal */ get totalServerDuration() { return this._totalServerDuration; } /** * Sets a single attribute on the span. * * @param key - The attribute key. * @param value - The attribute value. */ setAttribute(key, value) { let propagatedToParent = true; if (key === observabilitytypes_1.OpAttributeName.Service) { this._serviceType = serviceTypeFromString(value); } else if (key === observabilitytypes_1.DispatchAttributeName.ServerDuration) { this._serverDuration = value; this._totalServerDuration += this._serverDuration; } else if (key === observabilitytypes_1.DispatchAttributeName.LocalId) { this._localId = value; } else if (key === observabilitytypes_1.DispatchAttributeName.OperationId) { this._operationId = value; } else if (key === observabilitytypes_1.DispatchAttributeName.PeerAddress) { this._peerAddress = value; } else if (key === observabilitytypes_1.DispatchAttributeName.PeerPort) { this._peerPort = value; } else if (key === observabilitytypes_1.DispatchAttributeName.ServerAddress) { this._remoteAddress = value; } else if (key === observabilitytypes_1.DispatchAttributeName.ServerPort) { this._remotePort = value; } else { this._attributes[key] = value; propagatedToParent = false; } if (propagatedToParent && this._parentSpan) { this._parentSpan.setAttribute(key, value); } } /** * Adds a timestamped event to the span. */ addEvent() { } /** * Sets the status of the span. * * @param status - The span status containing code and optional message. */ setStatus(status) { this._status = status; } /** * Marks the span as complete and calculates the total duration. * * The total duration is computed from the start time to the end time * and stored in microseconds. * * @param endTime - Optional end time; defaults to current time if not provided. */ end(endTime) { if (this._endTime) { // prevent ending the span multiple times return; } this._endTime = this._getTime(endTime); this._totalDuration = (0, observabilityutilities_1.hiResTimeToMicros)((0, observabilityutilities_1.getHiResTimeDelta)(this._startTime, this._endTime)); this._snapshot = new ThresholdLoggingSpanSnapshot(this); if (this._name == observabilitytypes_1.OpAttributeName.EncodingSpanName) { this.encodeDuration = this._totalDuration; } else if (this._name == observabilitytypes_1.OpAttributeName.DispatchSpanName) { this.dispatchDuration = this._totalDuration; } else if (this._parentSpan && IGNORED_PARENT_SPAN_VALUES.has(this._parentSpan.name)) { this._tracer.checkThreshold(this._snapshot); } else if (!this._parentSpan && !IGNORED_PARENT_SPAN_VALUES.has(this._name)) { this._tracer.checkThreshold(this._snapshot); } } /** * Converts various time input formats to HiResTime. * * @param input - Time input (Date, number, HiResTime, or undefined for current time). * @returns {HiResTime} High-resolution time value. */ _getTime(input) { if (typeof input === 'undefined') { return (0, observabilityutilities_1.timeInputToHiResTime)(); } else if (input instanceof Date) { return (0, observabilityutilities_1.millisToHiResTime)(input.getTime()); } else if (typeof input === 'number') { // TODO: is this accurate? return (0, observabilityutilities_1.millisToHiResTime)(input); } return input; } } exports.ThresholdLoggingSpan = ThresholdLoggingSpan; /** * A RequestTracer implementation that identifies and logs slow operations. */ class ThresholdLoggingTracer { /** * Creates a new threshold logging tracer. * * @param logger - The logger to be used by the ThresholdLoggingReporter. * @param config - Tracing configuration with thresholds and reporting settings. * If null, default values are used for all settings. */ constructor(logger, config) { var _a, _b, _c, _d, _e, _f, _g, _h, _j; this._emitInterval = (_a = config === null || config === void 0 ? void 0 : config.emitInterval) !== null && _a !== void 0 ? _a : 10000; this._sampleSize = (_b = config === null || config === void 0 ? void 0 : config.sampleSize) !== null && _b !== void 0 ? _b : 10; this._serviceThresholds = new Map(); this._serviceThresholds.set(generaltypes_1.ServiceType.KeyValue, (_c = config === null || config === void 0 ? void 0 : config.kvThreshold) !== null && _c !== void 0 ? _c : 500); this._serviceThresholds.set(generaltypes_1.ServiceType.Query, (_d = config === null || config === void 0 ? void 0 : config.queryThreshold) !== null && _d !== void 0 ? _d : 1000); this._serviceThresholds.set(generaltypes_1.ServiceType.Search, (_e = config === null || config === void 0 ? void 0 : config.searchThreshold) !== null && _e !== void 0 ? _e : 1000); this._serviceThresholds.set(generaltypes_1.ServiceType.Analytics, (_f = config === null || config === void 0 ? void 0 : config.analyticsThreshold) !== null && _f !== void 0 ? _f : 1000); this._serviceThresholds.set(generaltypes_1.ServiceType.Views, (_g = config === null || config === void 0 ? void 0 : config.viewsThreshold) !== null && _g !== void 0 ? _g : 1000); this._serviceThresholds.set(generaltypes_1.ServiceType.Management, (_h = config === null || config === void 0 ? void 0 : config.managementThreshold) !== null && _h !== void 0 ? _h : 1000); this._serviceThresholds.set(generaltypes_1.ServiceType.Eventing, (_j = config === null || config === void 0 ? void 0 : config.eventingThreshold) !== null && _j !== void 0 ? _j : 1000); this._reporter = new ThresholdLoggingReporter(logger, this._emitInterval, this._sampleSize); this._reporter.start(); } /** * Gets the tracer's ThresholdLoggingReporter. * * @internal */ get reporter() { return this._reporter; } /** * Gets the tracer's service thresholds. * * @internal */ get serviceThresholds() { return this._serviceThresholds; } /** * Stops the threshold logging reporter and cleans up resources. * * This method should be called when shutting down the application or when * the tracer is no longer needed to ensure the periodic reporting timer * is properly cleared. */ cleanup() { this._reporter.stop(); } /** * Creates a new threshold logging span to trace an operation. * * @param name - The name of the operation being traced. * @param parentSpan - Optional parent span for hierarchical tracing. * @param startTime - Optional start time; defaults to current time if not provided. * @returns {ThresholdLoggingSpan} A new ThresholdLoggingSpan instance. */ requestSpan(name, parentSpan, startTime) { return new ThresholdLoggingSpan(name, this, parentSpan, startTime); } /** * Checks if an operation exceeded its threshold and collects diagnostic information. * * If the operation's duration exceeds the configured threshold for its service type, * detailed timing and connection information is extracted from the span and its * associated core spans (network dispatch spans) and added to the reporter's queue. * * @param span - The completed threshold logging span to check. * @param coreSpans - Optional array of low-level network dispatch spans containing. * detailed timing and connection information. * * @internal */ checkThreshold(spanSnapshot) { var _a; if (typeof spanSnapshot.serviceType === 'undefined') { return; } const serviceThreshold = this._getServiceTypeThreshold(spanSnapshot.serviceType); const spanTotalDuration = (_a = spanSnapshot.totalDuration) !== null && _a !== void 0 ? _a : 0; if (!serviceThreshold || spanTotalDuration <= serviceThreshold) { return; } const thresholdLogRecord = this._buildThresholdLogRecord(spanSnapshot, spanTotalDuration); this._reporter.addLogRecord(spanSnapshot.serviceType, thresholdLogRecord, spanTotalDuration); } /** * Converts the request span and all child spans into single ThresholdLogRecord. * * @param requestSpan - The request span. * @param spanTotalDuration - The request spans duration. * @returns {ThresholdLogRecord} The converted spans to a single ThresholdLogRecord. * * @internal */ _buildThresholdLogRecord(spanSnapshot, spanTotalDuration) { const thresholdLogRecord = new Map(); thresholdLogRecord.set(ThresholdLoggingAttributeName.OperationName, spanSnapshot.name); thresholdLogRecord.set(ThresholdLoggingAttributeName.TotalDuration, spanTotalDuration); if (spanSnapshot.encodeDuration) { thresholdLogRecord.set(ThresholdLoggingAttributeName.EncodeDuration, spanSnapshot.encodeDuration); } const isKeyValueOp = spanSnapshot.serviceType == generaltypes_1.ServiceType.KeyValue; if (spanSnapshot.totalDispatchDuration) { thresholdLogRecord.set(ThresholdLoggingAttributeName.TotalDispatchDuration, spanSnapshot.totalDispatchDuration); } if (isKeyValueOp && spanSnapshot.totalServerDuration) { thresholdLogRecord.set(ThresholdLoggingAttributeName.TotalServerDuration, spanSnapshot.totalServerDuration); } if (spanSnapshot.dispatchDuration) { thresholdLogRecord.set(ThresholdLoggingAttributeName.LastDispatchDuration, spanSnapshot.dispatchDuration); } if (isKeyValueOp && spanSnapshot.serverDuration) { thresholdLogRecord.set(ThresholdLoggingAttributeName.LastServerDuration, spanSnapshot.serverDuration); } if (spanSnapshot.localId) { thresholdLogRecord.set(ThresholdLoggingAttributeName.LastLocalId, spanSnapshot.localId); } if (spanSnapshot.operationId) { thresholdLogRecord.set(ThresholdLoggingAttributeName.OperationId, spanSnapshot.operationId); } if (spanSnapshot.localSocket) { thresholdLogRecord.set(ThresholdLoggingAttributeName.LastLocalSocket, spanSnapshot.localSocket); } if (spanSnapshot.remoteSocket) { thresholdLogRecord.set(ThresholdLoggingAttributeName.LastRemoteSocket, spanSnapshot.remoteSocket); } return thresholdLogRecord; } /** * Gets the configured threshold for a specific service type. * * @param serviceType - The Couchbase service type. * @returns The threshold in microseconds (millisecond config value * 1000). * * @internal */ _getServiceTypeThreshold(serviceType) { var _a; const baseThreshold = (_a = this._serviceThresholds.get(serviceType)) !== null && _a !== void 0 ? _a : 0; // convert to micros return baseThreshold * 1000; } } exports.ThresholdLoggingTracer = ThresholdLoggingTracer;