Skip to content
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,31 @@ Environment Variable | Description | Default
| `SW_AWS_SQS_CHECK_BODY` | Incoming SQS messages check inside the body for trace ID in order to allow linking outgoing SNS messages to incoming SQS. | `false` |
| `SW_AGENT_MAX_BUFFER_SIZE` | The maximum buffer size before sending the segment data to backend | `'1000'` |
| `SW_AGENT_TRACE_TIMEOUT` | The timeout for trace requests to backend services | `'10000'` |
| `SW_AGENT_NODEJS_RUNTIME_METRICS_REPORTER_ACTIVE` | Whether to report Node.js runtime metrics through MeterReportService (default: collect 1s, report 1s) | `true` |
| `SW_AGENT_NODEJS_RUNTIME_METRICS_COLLECT_PERIOD` | Runtime metric sample interval in milliseconds | `1000` |
| `SW_AGENT_NODEJS_RUNTIME_METRICS_REPORT_PERIOD` | Runtime metric report interval in milliseconds (aligned with Java JVM metrics upload interval) | `1000` |
| `SW_AGENT_NODEJS_RUNTIME_METRICS_BUFFER_SIZE` | Maximum buffered runtime metric samples before dropping oldest | `600` |

Legacy env names `SW_AGENT_RUNTIME_METRICS_*`, `SW_AGENT_NVM_METRICS_*` and `SW_AGENT_NVM_JVM_*` are still accepted as deprecated aliases.


Note that the various ignore options like `SW_IGNORE_SUFFIX`, `SW_TRACE_IGNORE_PATH` and `SW_HTTP_IGNORE_METHOD` as well as endpoints which are not recorded due to exceeding `SW_AGENT_MAX_BUFFER_SIZE` all propagate their ignored status downstream to any other endpoints they may call. If that endpoint is running the Node Skywalking agent then regardless of its ignore settings it will not be recorded since its upstream parent was not recorded. This allows the elimination of entire trees of endpoints you are not interested in as well as eliminating partial traces if a span in the chain is ignored but calls out to other endpoints which are recorded as children of ROOT instead of the actual parent.

## Node.js Runtime Metrics

The agent reports six process-level meters (`instance_nodejs_*`) via `MeterReportService` by default (collect 1s, report 1s). Set `SW_AGENT_NODEJS_RUNTIME_METRICS_REPORTER_ACTIVE=false` to disable. Process CPU combines `process.cpuUsage()` user + system, normalized by logical CPU count (0–100%).

| Node.js source | Meter name | Notes |
| :--- | :--- | :--- |
| `process.cpuUsage()` user + system | `instance_nodejs_process_cpu` | % |
| `process.memoryUsage().heapUsed` | `instance_nodejs_heap_used` | bytes |
| `process.memoryUsage().heapTotal` | `instance_nodejs_heap_total` | bytes |
| `v8.getHeapStatistics().heap_size_limit` | `instance_nodejs_heap_limit` | bytes |
| `process.memoryUsage().rss` | `instance_nodejs_rss` | bytes |
| `process.memoryUsage().external` | `instance_nodejs_external_memory` | bytes |

Custom business metrics are not available through a public API; use [OpenTelemetry metrics](https://skywalking.apache.org/docs/main/latest/en/setup/backend/opentelemetry-receiver/) if you need those.

## Supported Libraries

Some built-in plugins support automatic instrumentation of NodeJS libraries, the complete list is as follows:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,15 @@
*
*/

/**
* The transport protocol between the agent and the backend (OAP).
*/
export default interface Protocol {
isConnected: boolean;
/** Boot lifecycle contract (Java {@code BootService}). */
export default interface BootService {
prepare(): void;

heartbeat(): this;
boot(): void;

report(): this;
onComplete(): void;

flush(): Promise<any> | null;
shutdown(): void;

destroy?(): void;
priority(): number;
}
133 changes: 133 additions & 0 deletions src/agent/core/boot/ServiceManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/*!
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 config from '../../../config/AgentConfig';
import { createLogger } from '../../../logging';
import BootService from './BootService';
import MeterSender from '../meter/MeterSender';
import GRPCChannelManager from '../remote/GRPCChannelManager';
import ServiceManagementClient from '../remote/ServiceManagementClient';
import TraceSegmentServiceClient from '../remote/TraceSegmentServiceClient';

const logger = createLogger(__filename);

/** Service registry and boot orchestrator (Java {@code ServiceManager}). */
class ServiceManager {
private static readonly instance = new ServiceManager();

private bootedServices = new Map<new (...args: never[]) => BootService, BootService>();

private booted = false;

static get INSTANCE(): ServiceManager {
return ServiceManager.instance;
}

boot(): void {
if (this.booted) {
return;
}

this.loadServices();
const services = this.sortByPriority(true);

for (const service of services) {
try {
service.prepare();
} catch (error) {
logger.error('ServiceManager prepare failed: %s', error);
}
}

for (const service of services) {
try {
service.boot();
} catch (error) {
logger.error('ServiceManager boot failed: %s', error);
}
}

for (const service of services) {
try {
service.onComplete();
} catch (error) {
logger.error('ServiceManager onComplete failed: %s', error);
}
}

this.booted = true;
}

shutdown(): void {
for (const service of this.sortByPriority(false)) {
try {
service.shutdown();
} catch (error) {
logger.error('ServiceManager shutdown failed: %s', error);
}
}
this.bootedServices.clear();
this.booted = false;
}

flush(): Promise<unknown> | null {
const traceFlush = this.findService(TraceSegmentServiceClient)?.flush() ?? null;
const meterSender = this.findService(MeterSender);
const meterFlush = meterSender?.flush() ?? null;

if (!traceFlush && !meterFlush) {
return null;
}
if (!traceFlush) {
return meterFlush;
}
if (!meterFlush) {
return traceFlush;
}

return Promise.all([traceFlush, meterFlush]).then(() => null);
}

findService<T extends BootService>(serviceClass: new (...args: never[]) => T): T | undefined {
return this.bootedServices.get(serviceClass) as T | undefined;
}

private register<T extends BootService>(serviceClass: new (...args: never[]) => T, service: T): void {
this.bootedServices.set(serviceClass, service);
}

private loadServices(): void {
this.register(GRPCChannelManager, new GRPCChannelManager());
this.register(TraceSegmentServiceClient, new TraceSegmentServiceClient());
this.register(ServiceManagementClient, new ServiceManagementClient());
if (config.runtimeMetricsReporterActive) {
this.register(MeterSender, new MeterSender());
}
}

private sortByPriority(ascending: boolean): BootService[] {
const services = Array.from(this.bootedServices.values());
services.sort((left, right) =>
ascending ? left.priority() - right.priority() : right.priority() - left.priority(),
);
return services;
}
}

export default ServiceManager;
Loading
Loading