Skip to content

Commit 455d77a

Browse files
committed
new package
1 parent 48f5253 commit 455d77a

File tree

13 files changed

+1027
-0
lines changed

13 files changed

+1027
-0
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
"packages/nestjs",
6969
"packages/nextjs",
7070
"packages/node",
71+
"packages/node-stalled",
7172
"packages/nuxt",
7273
"packages/opentelemetry",
7374
"packages/profiling-node",

packages/node-stalled/.eslintrc.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
module.exports = {
2+
env: {
3+
node: true,
4+
},
5+
extends: ['../../.eslintrc.js'],
6+
7+
ignorePatterns: ['build/**/*', 'examples/**/*', 'vitest.config.ts'],
8+
rules: {
9+
'@sentry-internal/sdk/no-class-field-initializers': 'off',
10+
},
11+
};

packages/node-stalled/LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2022 Functional Software, Inc. dba Sentry
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy of
6+
this software and associated documentation files (the "Software"), to deal in
7+
the Software without restriction, including without limitation the rights to
8+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
9+
of the Software, and to permit persons to whom the Software is furnished to do
10+
so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

packages/node-stalled/README.md

Lines changed: 316 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,316 @@
1+
<p align="center">
2+
<a href="https://sentry.io/?utm_source=github&utm_medium=logo" target="_blank">
3+
<img src="https://sentry-brand.storage.googleapis.com/sentry-wordmark-dark-280x84.png" alt="Sentry" width="280" height="84">
4+
</a>
5+
</p>
6+
7+
# Official Sentry Profiling SDK for NodeJS
8+
9+
[![npm version](https://img.shields.io/npm/v/@sentry/profiling-node.svg)](https://www.npmjs.com/package/@sentry/profiling-node)
10+
[![npm dm](https://img.shields.io/npm/dm/@sentry/profiling-node.svg)](https://www.npmjs.com/package/@sentry/profiling-node)
11+
[![npm dt](https://img.shields.io/npm/dt/@sentry/profiling-node.svg)](https://www.npmjs.com/package/@sentry/profiling-node)
12+
13+
## Installation
14+
15+
Profiling works as an extension of tracing so you will need both @sentry/node and @sentry/profiling-node installed.
16+
17+
```bash
18+
# Using yarn
19+
yarn add @sentry/node @sentry/profiling-node
20+
21+
# Using npm
22+
npm install --save @sentry/node @sentry/profiling-node
23+
```
24+
25+
## Usage
26+
27+
```javascript
28+
import * as Sentry from '@sentry/node';
29+
import { nodeProfilingIntegration } from '@sentry/profiling-node';
30+
31+
Sentry.init({
32+
dsn: 'https://7fa19397baaf433f919fbe02228d5470@o1137848.ingest.sentry.io/6625302',
33+
debug: true,
34+
tracesSampleRate: 1,
35+
profilesSampleRate: 1, // Set profiling sampling rate.
36+
integrations: [nodeProfilingIntegration()],
37+
});
38+
```
39+
40+
Sentry SDK will now automatically profile all root spans, even the ones which may be started as a result of using an
41+
automatic instrumentation integration.
42+
43+
```javascript
44+
Sentry.startSpan({ name: 'some workflow' }, () => {
45+
// The code in here will be profiled
46+
});
47+
```
48+
49+
### Building the package from source
50+
51+
Profiling uses native modules to interop with the v8 javascript engine which means that you may be required to build it
52+
from source. The libraries required to successfully build the package from source are often the same libraries that are
53+
already required to build any other package which uses native modules and if your codebase uses any of those modules,
54+
there is a fairly good chance this will work out of the box. The required packages are python, make and g++.
55+
56+
**Windows:** If you are building on windows, you may need to install windows-build-tools
57+
58+
**_Python:_** Python 3.12 is not supported yet so you will need a version of python that is lower than 3.12
59+
60+
```bash
61+
62+
# using yarn package manager
63+
yarn global add windows-build-tools
64+
# or npm package manager
65+
npm i -g windows-build-tools
66+
```
67+
68+
After you have installed the toolchain, you should be able to build the binaries from source
69+
70+
```bash
71+
# configure node-gyp using yarn
72+
yarn build:bindings:configure
73+
# or using npm
74+
npm run build:bindings:configure
75+
76+
# compile the binaries using yarn
77+
yarn build:bindings
78+
# or using npm
79+
npm run build:bindings
80+
```
81+
82+
After the binaries are built, you should see them inside the profiling-node/lib folder.
83+
84+
### Prebuilt binaries
85+
86+
We currently ship prebuilt binaries for a few of the most common platforms and node versions (v18-24).
87+
88+
- macOS x64
89+
- Linux ARM64 (musl)
90+
- Linux x64 (glibc)
91+
- Windows x64
92+
93+
For a more detailed list, see job_compile_bindings_profiling_node job in our build.yml github action workflow.
94+
95+
### Bundling
96+
97+
If you are looking to squeeze some extra performance or improve cold start in your application (especially true for
98+
serverless environments where modules are often evaluates on a per request basis), then we recommend you look into
99+
bundling your code. Modern JS engines are much faster at parsing and compiling JS than following long module resolution
100+
chains and reading file contents from disk. Because @sentry/profiling-node is a package that uses native node modules,
101+
bundling it is slightly different than just bundling javascript. In other words, the bundler needs to recognize that a
102+
.node file is node native binding and move it to the correct location so that it can later be used. Failing to do so
103+
will result in a MODULE_NOT_FOUND error.
104+
105+
The easiest way to make bundling work with @sentry/profiling-node and other modules which use native nodejs bindings is
106+
to mark the package as external - this will prevent the code from the package from being bundled, but it means that you
107+
will now need to rely on the package to be installed in your production environment.
108+
109+
To mark the package as external, use the following configuration:
110+
111+
[Next.js 13+](https://nextjs.org/docs/app/api-reference/next-config-js/serverComponentsExternalPackages)
112+
113+
```js
114+
const { withSentryConfig } = require('@sentry/nextjs');
115+
116+
/** @type {import('next').NextConfig} */
117+
const nextConfig = {
118+
experimental: {
119+
// Add the "@sentry/profiling-node" to serverComponentsExternalPackages.
120+
serverComponentsExternalPackages: ['@sentry/profiling-node'],
121+
},
122+
};
123+
124+
module.exports = withSentryConfig(nextConfig, {
125+
/* ... */
126+
});
127+
```
128+
129+
[webpack](https://webpack.js.org/configuration/externals/#externals)
130+
131+
```js
132+
externals: {
133+
"@sentry/profiling-node": "commonjs @sentry/profiling-node",
134+
},
135+
```
136+
137+
[esbuild](https://esbuild.github.io/api/#external)
138+
139+
```js
140+
{
141+
entryPoints: ['index.js'],
142+
platform: 'node',
143+
external: ['@sentry/profiling-node'],
144+
}
145+
```
146+
147+
[Rollup](https://rollupjs.org/configuration-options/#external)
148+
149+
```js
150+
{
151+
entry: 'index.js',
152+
external: '@sentry/profiling-node'
153+
}
154+
```
155+
156+
[serverless-esbuild (serverless.yml)](https://www.serverless.com/plugins/serverless-esbuild#external-dependencies)
157+
158+
```yml
159+
custom:
160+
esbuild:
161+
external:
162+
- @sentry/profiling-node
163+
packagerOptions:
164+
scripts:
165+
- npm install @sentry/profiling-node
166+
```
167+
168+
[vercel-ncc](https://github.com/vercel/ncc#programmatically-from-nodejs)
169+
170+
```js
171+
{
172+
externals: ["@sentry/profiling-node"],
173+
}
174+
```
175+
176+
[vite](https://vitejs.dev/config/ssr-options.html#ssr-external)
177+
178+
```js
179+
ssr: {
180+
external: ['@sentry/profiling-node'];
181+
}
182+
```
183+
184+
Marking the package as external is the simplest and most future proof way of ensuring it will work, however if you want
185+
to bundle it, it is possible to do so as well. Bundling has the benefit of improving your script startup time as all of
186+
the code is (usually) inside a single executable .js file, which saves time on module resolution.
187+
188+
In general, when attempting to bundle .node native file extensions, you will need to tell your bundler how to treat
189+
these, as by default it does not know how to handle them. The required approach varies between build tools and you will
190+
need to find which one will work for you.
191+
192+
The result of bundling .node files correctly is that they are placed into your bundle output directory with their
193+
require paths updated to reflect their final location.
194+
195+
Example of bundling @sentry/profiling-node with esbuild and .copy loader
196+
197+
```json
198+
// package.json
199+
{
200+
"scripts": "node esbuild.serverless.js"
201+
}
202+
```
203+
204+
```js
205+
// esbuild.serverless.js
206+
const { sentryEsbuildPlugin } = require('@sentry/esbuild-plugin');
207+
208+
require('esbuild').build({
209+
entryPoints: ['./index.js'],
210+
outfile: './dist',
211+
platform: 'node',
212+
bundle: true,
213+
minify: true,
214+
sourcemap: true,
215+
// This is no longer necessary
216+
// external: ["@sentry/profiling-node"],
217+
loader: {
218+
// ensures .node binaries are copied to ./dist
219+
'.node': 'copy',
220+
},
221+
plugins: [
222+
// See https://docs.sentry.io/platforms/javascript/sourcemaps/uploading/esbuild/
223+
sentryEsbuildPlugin({
224+
project: '',
225+
org: '',
226+
authToken: '',
227+
release: '',
228+
sourcemaps: {
229+
// Specify the directory containing build artifacts
230+
assets: './dist/**',
231+
},
232+
}),
233+
],
234+
});
235+
```
236+
237+
Once you run `node esbuild.serverless.js` esbuild wil bundle and output the files to ./dist folder, but note that all of
238+
the binaries will be copied. This is wasteful as you will likely only need one of these libraries to be available during
239+
runtime.
240+
241+
To prune the other libraries, profiling-node ships with a small utility script that helps you prune unused binaries. The
242+
script can be invoked via `sentry-prune-profiler-binaries`, use `--help` to see a list of available options or
243+
`--dry-run` if you want it to log the binaries that would have been deleted.
244+
245+
Example of only preserving a binary to run node16 on linux x64 musl.
246+
247+
```bash
248+
sentry-prune-profiler-binaries --target_dir_path=./dist --target_platform=linux --target_node=16 --target_stdlib=musl --target_arch=x64
249+
```
250+
251+
Which will output something like
252+
253+
```
254+
Sentry: pruned ./dist/sentry_cpu_profiler-darwin-x64-108-IFGH3SUR.node (90.41 KiB)
255+
Sentry: pruned ./dist/sentry_cpu_profiler-darwin-x64-93-Q7KBVHSP.node (74.16 KiB)
256+
Sentry: pruned ./dist/sentry_cpu_profiler-linux-arm64-glibc-108-NXSISRTB.node (52.17 KiB)
257+
Sentry: pruned ./dist/sentry_cpu_profiler-linux-arm64-glibc-83-OEQT5HUK.node (52.08 KiB)
258+
Sentry: pruned ./dist/sentry_cpu_profiler-linux-arm64-glibc-93-IIXXW2PN.node (52.06 KiB)
259+
Sentry: pruned ./dist/sentry_cpu_profiler-linux-arm64-musl-108-DSILNYHA.node (48.46 KiB)
260+
Sentry: pruned ./dist/sentry_cpu_profiler-linux-arm64-musl-83-4CNOBNC3.node (48.37 KiB)
261+
Sentry: pruned ./dist/sentry_cpu_profiler-linux-arm64-musl-93-JA5PKNWQ.node (48.38 KiB)
262+
Sentry: pruned ./dist/sentry_cpu_profiler-linux-x64-glibc-108-NXSISRTB.node (52.17 KiB)
263+
Sentry: pruned ./dist/sentry_cpu_profiler-linux-x64-glibc-83-OEQT5HUK.node (52.08 KiB)
264+
Sentry: pruned ./dist/sentry_cpu_profiler-linux-x64-glibc-93-IIXXW2PN.node (52.06 KiB)
265+
Sentry: pruned ./dist/sentry_cpu_profiler-linux-x64-musl-108-CX7SL27U.node (51.50 KiB)
266+
Sentry: pruned ./dist/sentry_cpu_profiler-linux-x64-musl-83-YD7ZQK2E.node (51.53 KiB)
267+
Sentry: pruned ./dist/sentry_cpu_profiler-win32-x64-108-P7V3URQV.node (181.50 KiB)
268+
Sentry: pruned ./dist/sentry_cpu_profiler-win32-x64-93-3PKQDSGE.node (181.50 KiB)
269+
✅ Sentry: pruned 15 binaries, saved 1.06 MiB in total.
270+
```
271+
272+
### Environment flags
273+
274+
The default mode of the v8 CpuProfiler is kEagerLoggin which enables the profiler even when no profiles are active -
275+
this is good because it makes calls to startProfiling fast at the tradeoff for constant CPU overhead. The behavior can
276+
be controlled via the `SENTRY_PROFILER_LOGGING_MODE` environment variable with values of `eager|lazy`. If you opt to use
277+
the lazy logging mode, calls to startProfiling may be slow (depending on environment and node version, it can be in the
278+
order of a few hundred ms).
279+
280+
Example of starting a server with lazy logging mode.
281+
282+
```javascript
283+
SENTRY_PROFILER_LOGGING_MODE=lazy node server.js
284+
```
285+
286+
## FAQ 💭
287+
288+
### Can the profiler leak PII to Sentry?
289+
290+
The profiler does not collect function arguments so leaking any PII is unlikely. We only collect a subset of the values
291+
which may identify the device and os that the profiler is running on (if you are already using tracing, it is likely
292+
that these values are already being collected by the SDK).
293+
294+
There is one way a profiler could leak pii information, but this is unlikely and would only happen for cases where you
295+
might be creating or naming functions which might contain pii information such as
296+
297+
```js
298+
eval('function scriptFor${PII_DATA}....');
299+
```
300+
301+
In that case it is possible that the function name may end up being reported to Sentry.
302+
303+
### Are worker threads supported?
304+
305+
No. All instances of the profiler are scoped per thread In practice, this means that starting a transaction on thread A
306+
and delegating work to thread B will only result in sample stacks being collected from thread A. That said, nothing
307+
should prevent you from starting a transaction on thread B concurrently which will result in two independent profiles
308+
being sent to the Sentry backend. We currently do not do any correlation between such transactions, but we would be open
309+
to exploring the possibilities. Please file an issue if you have suggestions or specific use-cases in mind.
310+
311+
### How much overhead will this profiler add?
312+
313+
The profiler uses the kEagerLogging option by default which trades off fast calls to startProfiling for a small amount
314+
of constant CPU overhead. If you are using kEagerLogging then the tradeoff is reversed and there will be a small CPU
315+
overhead while the profiler is not running, but calls to startProfiling could be slow (in our tests, this varies by
316+
environments and node versions, but could be in the order of a couple 100ms).

0 commit comments

Comments
 (0)