couchbase
Version:
The official Couchbase Node.js Client Library.
658 lines (657 loc) • 25.1 kB
JavaScript
"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;