N|Solid JS API
The N|Solid Agent also exposes a JavaScript API for programmatic use or extension of its behavior.
These extensions allow the Observability features of the N|Solid Runtime's Agent to be triggered manually or enhanced with additional information. None of this is required for the default operation of N|Solid, which does not require any changes to your JS applications to start using.
The nsolid
agent is the same singleton reference to the N|Solid agent when used anywhere. Simply use const nsolid = require('nsolid')
to retrieve the nsolid agent.
nsolid.start()
Manually start and configure the N|Solid Agent. For example, if you have a configuration management platform or database that knows the hostname and port to your Console, you may obtain it and then invoke nsolid.start()
to connect and begin reporting metrics. The agent can be started at any time. The keys are the same as the package.json configuration keys with the following difference:
version
is specified asappVersion
Example:
nsolid.start({
command: 'nsolid-command-host.local:9001',
tags: ['experiment-4a', 'Auth service'],
app: 'nsolid-awesome',
appVersion: '1.0.0'
})
nsolid.appName
/ nsolid.app
Get the N|Solid app name. These are aliases.
nsolid.appName
'nsolid-awesome'
nsolid.app
'nsolid-asesome'
nsolid.appVersion
Get the application version string if defined in the package.json
file.
nsolid.appVersion
'1.0.0'
nsolid.config
Get the N|Solid application config as loaded by the agent. Note that this can be changed at runtime via restarting the agent with new configuration or with a configuration change sent from a connected N|Solid Console Server.
nsolid.config
{
app: 'docs',
appVersion: '1.0.0',
blockedLoopThreshold: 200,
disablePackageScan: false,
env: 'prod',
hostname: 'anoxia',
iisNode: false,
interval: 3000,
pauseMetrics: false,
promiseTracking: false,
pubkey: ' ... ',
statsdBucket: 'nsolid.${env}.${app}.${hostname}.${shortId}',
tags: [ 'beta-service', 'experiment-4a' ],
tracingEnabled: false,
tracingModulesBlacklist: 0,
trackGlobalPackages: false
}
nsolid.getThreadName()
Get an N|Solid worker thread name. This allows labelling of worker threads inside of the N|Solid Pro UI for easier identification. All metrics payloads will include this name if set.
const { Worker, isMainThread } = require('worker_threads');
const { getThreadName, setThreadName } = require('nsolid')
if (!isMainThread) {
console.log(getThreadName()) // ''
setThreadName('worker-parser')
console.log(getThreadName()) // worker-parser
return setTimeout(() => {}, 1000)
}
const worker = new Worker(__filename)
nsolid.heapProfile(duration, trackAllocations)
Begins the collection of a Heap Profile. Intended to be run in the thread you want to profile. Accepts duration
in milliseconds and a boolean flag trackAllocations
to enable allocation tracking. Upon completion, it will be sent to the N|Solid Console Server. Read more here.
nsolid.heapProfile(120, true)
nsolid.heapProfileEnd(callback)
Allows the premature halt of an ongoing Heap Profile in this thread. Callback will return an error if there was no active Heap Profile to be stopped. This is the same as shortening the duration and the resulting Heap Profile will still be sent to the N|Solid Console Server.
nsolid.heapProfileEnd((err) => {
if (err) {
console.error('no Heap Profile to stop')
}
})
nsolid.heapProfileStream(threadId, duration, trackAllocations)
Allows the collection of a Heap Profile from any thread into your local application. This can be used to save Heap Profiles locally without sending them to the N|Solid Console Server. Accepts threadId
which is the integer thread id of the worker thread to Profile. The default main thread is thread 0
and must be specified in order to Profile even if there are no other worker threads. Also accepts duration
in milliseconds and a trackAllocations
boolean. See more about Heap Profiling here
let profile = ''
const profileStream = nsolid.heapProfileStream(0, 20000, true)
profileStream.on('data', (data) => {
profile += data
})
profileStream.on('end', () => {
// handle the JSON contents now in ${profile}
})
nsolid.heapSampling(duration, settings)
Starts the collection of a Sampling Heap Profile on the current thread and sends it to the N|Solid Console Server. This a lighter-weight Heap Profile that provides higher-level statistics without as much impact on the application itself. It accepts a duration
in milliseconds (default of 600000 or 10 minutes) and an options
object to define how to perform the Sampling.
options
structure:
sampleInterval
(default 512 * 1024): The Heap is sampled everysampleInterval
bytes. Use a smaller number for higher resolution but more overhead.stackDepth
(default 16): The maximum stap depth to capture when tracking allocations.flags
(default 0): The V8 flags that specify additional collection options. These Flags define the following options that are not exclusive of each other:- Force Garbage Collection
- Include Objects collected during a Major GC event
- Include Objects collected during a Minor GC event
These flags are bitflags, here is one way to use them:
const SamplingFlags = {
noFlags: 0,
forceGC: 1 << 0,
includeObjectsCollectedByMajorGC: 1 << 1,
includeObjectsCollectedByMinorGC: 1 << 2,
}
let myflag = SamplingFlags.forceGC | SamplingFlags.includeObjectsCollectedByMajorGC
nsolid.heapSampling(20000, {flags: myFlag})
nsolid.heapSamplingEnd(cb)
Allows the premature halt of an ongoing Heap Sampling in this thread. Callback will return an error if there was no active Heap Sampling to be stopped. This is the same as shortening the duration and the resulting Heap Sampling will still be sent to the N|Solid Console Server.
nsolid.heapSamplingEnd((err) => {
if (err) {
console.error('no Heap Sample to stop')
}
})
nsolid.heapSamplingStream(threadId, duration, settings)
Allows the collection of a Heap Sampling Profile from any thread into your local application. This can be used to save Heap Samples locally without sending them to the N|Solid Console Server. Accepts threadId
which is the integer thread id of the worker thread to Profile. The default main thread is thread 0
and must be specified in order to Sample even if there are no other worker threads. Also accepts duration
in milliseconds and a trackAllocations
boolean. See more about Heap Sampling here
let profile = ''
const profileStream = nsolid.heapSamplingStream(0, 20000, {})
profileStream.on('data', (data) => {
profile += data
})
profileStream.on('end', () => {
// handle the JSON contents now in ${profile}
})
nsolid.id
Get the N|Solid agent id. This is treated as a UUID for this process inside of N|Solid systems.
nsolid.id
'9964df10bd45350092a8064727ea4c009bee1d91'
nsolid.info()
Returns identifying and static characteristic information about the process, host machine, and platform.
nsolid.info()
{
app: 'docs',
appVersion: '1.0.0',
arch: 'x64',
cpuCores: 12,
cpuModel: 'AMD Ryzen 5 2600 Six-Core Processor',
execPath: '/usr/bin/nsolid',
hostname: 'anoxia',
id: '797d1fa9fafd62002af42f919ab79900a455150d',
main: '',
nodeEnv: 'prod',
pid: 422446,
platform: 'linux',
processStart: 1716511183845,
tags: [ 'beta-service', 'experiment-4a' ],
totalMem: 8295538688,
versions: {
acorn: '8.11.3',
ada: '2.7.8',
ares: '1.28.1',
base64: '0.5.2',
brotli: '1.1.0',
cjs_module_lexer: '1.2.2',
cldr: '45.0',
icu: '75.1',
llhttp: '8.1.2',
modules: '115',
napi: '9',
nghttp2: '1.61.0',
nghttp3: '0.7.0',
ngtcp2: '1.1.0',
node: '20.13.1',
nsolid: '5.2.1',
openssl: '3.0.13+quic',
simdutf: '5.2.4',
tz: '2024a',
undici: '6.13.0',
unicode: '15.1',
uv: '1.46.0',
uvwasi: '0.0.20',
v8: '11.3.244.8-node.20',
zlib: '1.3.0.1-motley-7d77fb7'
}
}
If necessary, nsolid.info()
can be called asynchronously.
nsolid.info((err, info) => {
// ...
})
nsolid.metrics()
Returns the latest recorded metrics values. Note that this format is specific to a single thread and is not the full set of metrics sent to connected endpoints.
nsolid.metrics()
{
threadName: '',
threadId: 0,
timestamp: 1716511535286,
activeHandles: 1,
activeRequests: 0,
heapTotal: 6000640,
totalHeapSizeExecutable: 524288,
totalPhysicalSize: 5976064,
totalAvailableSize: 2193381512,
heapUsed: 5351112,
heapSizeLimit: 2197815296,
mallocedMemory: 147552,
externalMem: 2256629,
peakMallocedMemory: 521312,
numberOfNativeContexts: 2,
numberOfDetachedContexts: 0,
gcCount: 7,
gcForcedCount: 0,
gcFullCount: 0,
gcMajorCount: 4,
dnsCount: 0,
httpClientAbortCount: 0,
httpClientCount: 0,
httpServerAbortCount: 0,
httpServerCount: 0,
loopIdleTime: 351289699466,
loopIterations: 222,
loopIterWithEvents: 160,
eventsProcessed: 215,
eventsWaiting: 160,
providerDelay: 64084249,
processingDelay: 0,
loopTotalCount: 222,
pipeServerCreatedCount: 0,
pipeServerDestroyedCount: 0,
pipeSocketCreatedCount: 0,
pipeSocketDestroyedCount: 0,
tcpServerCreatedCount: 0,
tcpServerDestroyedCount: 0,
tcpSocketCreatedCount: 0,
tcpSocketDestroyedCount: 0,
udpSocketCreatedCount: 0,
udpSocketDestroyedCount: 0,
promiseCreatedCount: 0,
promiseResolvedCount: 0,
fsHandlesOpenedCount: 3,
fsHandlesClosedCount: 2,
gcDurUs99Ptile: 0,
gcDurUsMedian: 0,
dns99Ptile: 0,
dnsMedian: 0,
httpClient99Ptile: 0,
httpClientMedian: 0,
httpServer99Ptile: 0,
httpServerMedian: 0,
loopUtilization: 0.000395,
res5s: 0.001525,
res1m: 0.000173,
res5m: 0.000096,
res15m: 0.000053,
loopAvgTasks: 0,
loopEstimatedLag: 0.000056,
loopIdlePercent: 99.96048,
title: 'nsolid',
user: 'bryce',
uptime: 351,
systemUptime: 65463,
freeMem: 4299878400,
blockInputOpCount: 0,
blockOutputOpCount: 32,
ctxSwitchInvoluntaryCount: 0,
ctxSwitchVoluntaryCount: 4024,
ipcReceivedCount: 0,
ipcSentCount: 0,
pageFaultHardCount: 0,
pageFaultSoftCount: 4337,
signalCount: 0,
swapCount: 0,
rss: 56938496,
load1m: 0,
load5m: 0.03,
load15m: 0.06,
cpuUserPercent: 0.066668,
cpuSystemPercent: 0.01117,
cpuPercent: 0.077838,
cpu: 0.077838
}
If callback is passed metrics are returned asynchronously.
nsolid.metrics((err, metrics) => {
// ...
})
See Metrics in detail for descriptions of each metric.
nsolid.metricsPaused
Whether the agent is currently retrieving metrics or not. See nsolid.pauseMetrics()
and nsolid.resumeMetrics()
for changing this value.
> nsolid.metricsPaused
false
> nsolid.pauseMetrics()
undefined
> nsolid.metricsPaused
true
> nsolid.resumeMetrics()
undefined
> nsolid.metricsPaused
false
nsolid.on(customCommand, handler)
Registers a custom command listener that will be called if that custom command is sent over the NSOLID_COMMAND
channel.
Requires a customCommand
name and a handler
. When called it will contain a request
object with the optional value
sent in the command. And two continuation functions called return
and throw
.
request
object:
Field | Description |
---|---|
value | The value sent as the "data" portion of the custom command. |
return | Continuation function to complete the N |
throw | Error state continuation function to complete the N |
Your handler must call one of request.return
or request.throw
.
Custom commands can execute any JS in the handler. Use caution.
nsolid.on('my_command', (request) => {
console.log(`Value: ${request.value}`)
request.return({ok: true})
})
nsolid.otel
Returns an object holding the register
and registerInstrumentations
methods for use with the JavaScript OpenTelemetry API @opentelemetry/api
library.
The register()
function allows the use of OpenTelemetry TraceAPI.
See a very basic example in the following code. Notice that for the traces to be generated the enviroment variable NSOLID_TRACING_ENABLED
should be set.
Your application must have tracing enabled for this code example to function. Either start your application with NSOLID_TRACING_ENABLED=1
or by enabling Tracing inside of the N|Solid Pro UI.
const nsolid = require('nsolid');
const api = require('@opentelemetry/api');
if (!nsolid.otel.register(api)) {
throw new Error('Error registering api');
}
const tracer = api.trace.getTracer('Test tracer');
const span = tracer.startSpan('initial', { attributes: { a: 1, b: 2 }});
span.updateName('my name');
span.setAttributes({c: 3, d: 4 });
span.setAttribute('e', 5);
span.addEvent('my_event 1', Date.now());
span.addEvent('my_event 2', { attr1: 'val1', attr2: 'val2'}, Date.now());
span.end();
The registerInstrumentations
method allows you to register instrumentation modules that use the OpenTelemetry TraceAPI from the OpenTelemetry echosystem. We suggest using the @opentelemetry/auto-instrumentation-node
package to automatically instrument what is available to be instrumented.
const nsolid = require('nsolid')
const api = require('@opentelemetry/api')
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node')
nsolid.otel.registerInstrumentations(getNodeAutoInstrumentations())
nsolid.packages()
It retrieves the list of packages listed in the package.json
file and its dependencies, it returns a list of all packages that can be required by walking through all directories that could contain a module.
nsolid.packages()
[
{
{
path: '/home/bryce/tmp/node_modules/infinite-monkey-db',
name: 'infinite-monkey-db',
version: '1.0.2',
main: 'index.js',
dependencies: [ '../readable-stream', '../stream-meter', '../timegap' ],
required: false
},
{
path: '/home/bryce/tmp/node_modules/stats-lite',
name: 'stats-lite',
version: '2.2.0',
main: 'stats.js',
dependencies: [ '../isnumber' ],
required: false
},
...
]
nsolid.packages()
can also be called asynchronously:
nsolid.packages((err, packages) => {
// ...
})
nsolid.pauseMetrics()
Pauses the process metrics collection if unpaused, otherwise does nothing. Note: this method must be called on the main thread.
nsolid.pauseMetrics()
nsolid.processStart
Returns the start date of the process in milliseconds.
nsolid.processStart
1716575443913
nsolid.profile([duration])
This is the way to trigger a CPU profile programmatically from within your application and have the resulting profile saved on your N|Solid console. The default profile duration is 600000 milliseconds (10 minutes). The profiler can be stopped before the specified end time with the nsolid.profileEnd
command.
// Example: Start a 1 minute CPU profile from inside of the application
nsolid.profile(60000 /* milliseconds */, err => {
if (err) {
// The profile could not be started!
}
// if no error, the profiler has started running
})
The profiler can also be started synchronously:
try {
nsolid.profile(600000 /* durationMilliseconds */)
} catch (err) {
// The profile could not be started!
}
nsolid.profileEnd()
Stops the profiler if running, allowing stopping before the duration expires.
nsolid.profileEnd(err => {
if (err) {
// Error stopping profiler
}
// profiler is now stopped
})
nsolid.resumeMetrics()
If metrics were paused, resume collection. Otherwise does nothing. Note: this method must be called on the main thread.
nsolid.resumeMetrics()
nsolid.saveFatalError(error)
When your N|Solid process terminates with an error, it will attempt to send that error message to the N|Solid Console prior to exit. If you must exit asynchronously from an uncaughtException
and intercept this process, you may still report the error to the N|Solid Console via nsolid.saveFatalError()
prior to your own manual shutdown.
process.on('uncaughtException', err => {
nsolid.saveFatalError(err)
someImportantCleanup()
proces.exit(1)
})
nsolid.setThreadName(name)
For easier management of worker threads inside the N|Solid Console, you can set text labels for them.
const { Worker, isMainThread } = require('worker_threads');
const { getThreadName, setThreadName } = require('nsolid')
if (!isMainThread) {
console.log(getThreadName()) // ''
setThreadName('worker-renamed')
console.log(getThreadName()) // worker-renamed
return setTimeout(() => {}, 1000)
} else {
const worker = new Worker(__filename)
}
The main thread can also be named:
const { isMainThread } = require('worker_threads');
const { setThreadName } = require('nsolid')
if (isMainThread) setThreadName('main-thread')
The value passed to nsolid.setThreadName
must be a string, otherwise an error will be thrown.
const { Worker, isMainThread } = require('worker_threads');
const { getThreadName, setThreadName } = require('nsolid')
if (!isMainThread) {
try {
setThreadName(null)
} catch (err) { /** TypeError [ERR_INVALID_ARG_TYPE]: The "name" argument must be of type string. Received null */ }
} else {
const worker = new Worker(__filename)
}
nsolid.snapshot()
Trigger a Heap Snapshot from within your application and have the resulting snapshot(s) saved on your N|Solid console.
nsolid.snapshot(err => {
if (err) {
// The snapshot could not be created!
}
})
This can also be called asynchronously:
try {
nsolid.snapshot()
} catch (err) {
// The snapshot could not be created!
}
nsolid.startupTimes()
Retrieves the N|Solid tracked "startup times" of the process in a high resolution array of [seconds, nanoseconds]
. The default phases are:
Startup Phase Name | Description |
---|---|
timeOrigin | The starting reference point. Defines the relationship between timeOriginTimestamp and the rest of the values. |
timeOriginTimestamp | The timestamp of timeOrigin relative to UTC. |
initialized_node | The amount of time it took to initialize node and start the core process. |
initialized_v8 | To initialize the V8 engine. |
loaded_environment | To complete loading the environment and execute the initial top-level JavaScript code in core and your own main file. |
bootstrap_complete | Time at which bootstrap was complete and the application is ready to begin asynchronous operations. |
loop_start | When the first event loop iteration began relative to timeOrigin. |
nsolid.startupTimes()
{
bootstrap_complete: [ 0, 16597032 ],
initialized_node: [ 0, 390247 ],
initialized_v8: [ 0, 2975541 ],
loaded_environment: [ 0, 8607176 ],
loop_start: [ 0, 35011317 ],
timeOrigin: [ 0, 0 ],
timeOriginTimestamp: [ 1635041, 700204934 ]
}
nsolid.startupTimes()
can also be called asynchronously:
nsolid.startupTimes((err, startupTimes)) => {
// ...
}
You can add your own startup phases to the startupTimes
object with process.recordStartupTime(string)
.
// after creating a database client
process.recordStartupTime('db_initialized')
nsolid.startupTimes()
{
bootstrap_complete: [ 0, 15298719 ],
db_initialized: [ 6, 144053957 ], // DB initalization in context with the rest of the application bootstrap
initialized_node: [ 0, 297988 ],
initialized_v8: [ 0, 2603603 ],
loaded_environment: [ 0, 7437782 ],
loop_start: [ 0, 34829260 ],
timeOrigin: [ 0, 0 ],
timeOriginTimestamp: [ 1633924, 707504898 ]
}
nsolid.statsd
Accessor for the nsolid.statsd
object for working with a StatsD endpoint:
Property | Description |
---|---|
counter: | Send a "counter" type value to statsd. Will use NSOLID_STATSD_BUCKET and NSOLID_STATSD_TAGS if configured. |
format: | The statsd format object. |
gauge: | Send a "gauge" type value to statsd. Will use NSOLID_STATSD_BUCKET and NSOLID_STATSD_TAGS if configured. |
sendRaw: | Send a raw string to statsd. Caller is required to comply to statsd format (terminating newline not required). An array of strings is also permissible, they will be sent newline separated. If connected via UDP, data sent via sendRaw() will be split up into 1400Kb batches to fit within a standard MTU window, this applies to newline separated strings or an array of strings passed to this interface. |
set: | Send a "set" type value to statsd. Will use NSOLID_STATSD_BUCKET and NSOLID_STATSD_TAGS if configured. |
status: | Function that retrieves the statsd agent status. |
tcpIp: | If configured, returns the ip address tcp statsd data will be sent to. |
timing: | Send a "timing" type value to statsd. Will use NSOLID_STATSD_BUCKET and NSOLID_STATSD_TAGS if configured. |
udpIp: | If configured, returns the ip address udp statsd data will be sent to. |
By default there is no configuration and the statsd object will have unconfigured
status. The statsd statuses are:
- unconfigured
- initializing
- connecting
- ready
Example:
nsolid.statsd.status()
'unconfigured'
The nsolid.statsd.format
object:
Property | Description |
---|---|
bucket: | Returns the "bucket" string prepended to all auto-generated statsd metric strings. |
counter: | Format a "counter" string for name and value suitable for statsd. |
gauge: | Format a "gauge" string for name and value suitable for statsd. |
set: | Format a "set" string for name and value suitable for statsd. |
timing: | Format a "timing" string for name and value suitable for statsd. |
Usage example:
> nsolid.statsd.format.bucket()
'nsolid.prod.webserver.anoxia.ced675f'
nsolid.tags
Get the N|Solid app tags (taken from the package.json
file or NSOLID_TAGS
environment variable).
$ NSOLID_STATSD=localhost:8125 NSOLID_STATSD_TAGS='testmode' nsolid
Welcome to N|Solid v20.13.1+ns5.2.1.
Type ".help" for more information.
> nsolid.statsd.format.tags()
'|#testmode'
nsolid.traceStats
Returns the latest nsolid.traceStats
object getters. Note that this returns the getter functions which need to be called individually.
nsolid.traceStats
{
httpClientCount: [Getter],
httpServerCount: [Getter],
dnsCount: [Getter],
httpClientAbortCount: [Getter],
httpServerAbortCount: [Getter]
}
nsolid.traceStats.httpClientCount
1
Property | Description |
---|---|
dnsCount | The total number of DNS lookups performed during the life of the process. |
httpClientCount | ... of outgoing HTTP(S) client requests performed. |
httpClientAbortCount | ... of aborted or timed-out HTTP(S) client requests. |
httpServerCount | ... of incoming HTTP(s) requests served. |
httpServerAbortCount | ... of incoming HTTP(S) requests canceled. |
nsolid.zmq
The nsolid.zmq
object holds the status getter for the ZMQ communcation for the N|Solid Agent.
Property | Description |
---|---|
status: | Function that retrieves the zmq agent status. |
The zmq
agent statuses are:
- buffering
- connecting
- initializing
- ready
- unconfigured
Usage example:
nsolid.zmq.status()
'ready'