@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
JavaScript
;
/*
* 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