UNPKG

@splunk/otel

Version:

The Splunk distribution of OpenTelemetry Node Instrumentation provides a Node agent that automatically instruments your Node application to capture and report distributed traces to Splunk APM.

220 lines 9.24 kB
"use strict"; /* * Copyright Splunk Inc. * * 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. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.allowedProfilingOptions = void 0; exports.defaultExporterFactory = defaultExporterFactory; exports.isProfilingContextManagerSet = isProfilingContextManagerSet; exports.ensureProfilingContextManager = ensureProfilingContextManager; exports.startProfiling = startProfiling; exports.loadExtension = loadExtension; exports.noopExtension = noopExtension; exports._setDefaultOptions = _setDefaultOptions; const api_1 = require("@opentelemetry/api"); const resources_1 = require("@opentelemetry/resources"); const utils_1 = require("../utils"); const debug_metrics_1 = require("../metrics/debug_metrics"); const resource_1 = require("../resource"); const semantic_conventions_1 = require("@opentelemetry/semantic-conventions"); const ProfilingContextManager_1 = require("./ProfilingContextManager"); const OtlpHttpProfilingExporter_1 = require("./OtlpHttpProfilingExporter"); const tracing_1 = require("../tracing"); /* The following are wrappers around native functions to give more context to profiling samples. */ function extStopProfiling(handle, extension) { api_1.diag.debug('profiling: Stopping'); return extension.stop(handle); } function extStopMemoryProfiling(extension) { return extension.stopMemoryProfiling(); } function extStartProfiling(extension, opts) { api_1.diag.debug('profiling: Starting'); return extension.start(opts); } function extStartMemoryProfiling(extension, options) { return extension.startMemoryProfiling(options); } function extCollectHeapProfile(extension) { return extension.collectHeapProfile(); } function extCollectCpuProfile(handle, extension) { api_1.diag.debug('profiling: Collecting CPU profile'); return extension.collect(handle); } function defaultExporterFactory(options) { var _a; const endpoint = (_a = (0, utils_1.ensureResourcePath)(options.endpoint, '/v1/logs')) !== null && _a !== void 0 ? _a : options.endpoint; const exporters = [ new OtlpHttpProfilingExporter_1.OtlpHttpProfilingExporter({ endpoint, callstackInterval: options.callstackInterval, resource: options.resource, instrumentationSource: 'continuous', }), ]; return exporters; } let profilingContextManagerEnabled = false; function isProfilingContextManagerSet() { return profilingContextManagerEnabled; } function ensureProfilingContextManager() { if (profilingContextManagerEnabled === true) { return; } const contextManager = new ProfilingContextManager_1.ProfilingContextManager(); contextManager.enable(); api_1.context.setGlobalContextManager(contextManager); profilingContextManagerEnabled = true; } function startProfiling(options) { const extension = loadExtension(); if (extension === undefined) { return { stop: async () => { }, }; } if ((0, tracing_1.isTracingContextManagerEnabled)()) { api_1.diag.warn(`Splunk profiling: unable to set up context manager due to tracing's context manager being active. Traces won't be correlated to profiling data. Please start profiling before tracing.`); } else if (!profilingContextManagerEnabled) { ensureProfilingContextManager(); } const samplingIntervalMicroseconds = options.callstackInterval * 1000; const startOptions = { name: 'splunk-otel-js-profiler', samplingIntervalMicroseconds, maxSampleCutoffDelayMicroseconds: samplingIntervalMicroseconds / 2, recordDebugInfo: false, }; const handle = extStartProfiling(extension, startOptions); let cpuSamplesCollectInterval; let memSamplesCollectInterval; let exporters = []; // Tracing needs to be started after profiling, setting up the profiling exporter // causes @grpc/grpc-js to be loaded, but to avoid any loads before tracing's setup // has finished, load it next event loop. setImmediate(() => { exporters = options.exporterFactory(options); cpuSamplesCollectInterval = setInterval(async () => { const cpuProfile = extCollectCpuProfile(handle, extension); if (cpuProfile) { (0, debug_metrics_1.recordCpuProfilerMetrics)(cpuProfile); const sends = exporters.map((exporter) => exporter.send(cpuProfile)); await Promise.allSettled(sends); } }, options.collectionDuration); cpuSamplesCollectInterval.unref(); if (options.memoryProfilingEnabled) { extStartMemoryProfiling(extension, options.memoryProfilingOptions); memSamplesCollectInterval = setInterval(async () => { const heapProfile = extCollectHeapProfile(extension); if (heapProfile) { (0, debug_metrics_1.recordHeapProfilerMetrics)(heapProfile); const sends = exporters.map((exporter) => exporter.sendHeapProfile(heapProfile)); await Promise.allSettled(sends); } }, options.collectionDuration); memSamplesCollectInterval.unref(); } }); return { stop: async () => { if (options.memoryProfilingEnabled) { clearInterval(memSamplesCollectInterval); extStopMemoryProfiling(extension); } clearInterval(cpuSamplesCollectInterval); const cpuProfile = extStopProfiling(handle, extension); if (cpuProfile) { const sends = exporters.map((e) => e.send(cpuProfile)); await Promise.allSettled(sends).then((results) => { for (const result of results) { if (result.status === 'rejected') { api_1.diag.error('Failed sending CPU profile on shutdown', result.reason); } } }); } }, }; } function loadExtension() { try { api_1.diag.debug('profiling: Loading'); return require('../native_ext').profiling; } catch (e) { api_1.diag.error('profiling: Unable to load extension. Profiling data will not be reported', e); } return undefined; } function noopExtension() { return { createCpuProfiler: (_options) => -1, startCpuProfiler: (_handle) => false, addTraceIdFilter: (_handle, _traceId) => { }, removeTraceIdFilter: (_handle, _traceId) => { }, start: (_options) => -1, stop: (_handle) => null, collect: (_handle) => null, enterContext: (_context, _traceId, _spanId) => { }, exitContext: (_context) => { }, startMemoryProfiling: (_options) => { }, stopMemoryProfiling: () => { }, collectHeapProfile: () => null, }; } function _setDefaultOptions(options = {}) { var _a, _b; const endpoint = options.endpoint || (0, utils_1.getNonEmptyEnvVar)('SPLUNK_PROFILER_LOGS_ENDPOINT') || (0, utils_1.getNonEmptyEnvVar)('OTEL_EXPORTER_OTLP_ENDPOINT') || 'http://localhost:4318'; const envResource = (0, resource_1.getDetectedResource)(); const serviceName = String(options.serviceName || (0, utils_1.getNonEmptyEnvVar)('OTEL_SERVICE_NAME') || envResource.attributes[semantic_conventions_1.ATTR_SERVICE_NAME] || (0, utils_1.defaultServiceName)()); const resourceFactory = options.resourceFactory || ((r) => r); const resource = resourceFactory(envResource).merge(new resources_1.Resource({ [semantic_conventions_1.ATTR_SERVICE_NAME]: serviceName, })); const memoryProfilingEnabled = (_a = options.memoryProfilingEnabled) !== null && _a !== void 0 ? _a : (0, utils_1.getEnvBoolean)('SPLUNK_PROFILER_MEMORY_ENABLED', false); return { serviceName: serviceName, endpoint, callstackInterval: options.callstackInterval || (0, utils_1.getEnvNumber)('SPLUNK_PROFILER_CALL_STACK_INTERVAL', 1000), collectionDuration: options.collectionDuration || 30000, resource, exporterFactory: (_b = options.exporterFactory) !== null && _b !== void 0 ? _b : defaultExporterFactory, memoryProfilingEnabled, memoryProfilingOptions: options.memoryProfilingOptions, }; } exports.allowedProfilingOptions = [ 'callstackInterval', 'collectionDuration', 'endpoint', 'accessToken', 'resourceFactory', 'serviceName', 'exporterFactory', 'memoryProfilingEnabled', 'memoryProfilingOptions', ]; //# sourceMappingURL=index.js.map