Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 3 additions & 23 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
asan:
strategy:
matrix:
version: [18, 20, 22, 24, 25]
version: [18, 20, 22, 24, 25, 26]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
Expand All @@ -23,7 +23,7 @@ jobs:
valgrind:
strategy:
matrix:
version: [18, 20, 22, 24, 25]
version: [18, 20, 22, 24, 25, 26]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
Expand All @@ -42,27 +42,7 @@ jobs:
cache: true # enable caching of dependencies based on lockfile
min-node-version: 18
skip: 'linux-arm,linux-ia32' # skip building for these platforms

dev_publish:
needs: build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
environment: npm
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
steps:
- uses: actions/checkout@v2
- uses: actions/download-artifact@v4
- uses: actions/setup-node@v3
with:
registry-url: 'https://registry.npmjs.org'
- run: npm install
- id: pkg
run: |
content=`cat ./package.json | tr '\n' ' '`
echo "json=$content" >> $GITHUB_OUTPUT
- run: npm version --no-git-tag-version ${{ fromJson(steps.pkg.outputs.json).version }}-$(git rev-parse --short HEAD)+${{ github.run_id }}.${{ github.run_attempt }}
- run: npm publish --tag dev
node-gyp-build-major: 4

build-successful:
if: always()
Expand Down
28 changes: 27 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ name: Release
on:
push:
branches:
- main
- v[0-9]+.x

jobs:
Expand All @@ -15,9 +16,11 @@ jobs:
min-node-version: 18
skip: 'linux-arm,linux-ia32' # skip building for these platforms

publish:
publish_release:
needs: build
runs-on: ubuntu-latest
# release.yml also runs on main for dev publishes, so keep release publishes on version branches only.
if: startsWith(github.ref, 'refs/heads/v')
environment: npm
permissions:
id-token: write # Required for OIDC
Expand All @@ -43,3 +46,26 @@ jobs:
- run: |
git tag v${{ fromJson(steps.pkg.outputs.json).version }}
git push https://x-access-token:${{ steps.octo-sts.outputs.token }}@github.com/${{ github.repository }}.git v${{ fromJson(steps.pkg.outputs.json).version }}

publish_dev:
needs: build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
environment: npm
permissions:
id-token: write # Required for OIDC
contents: read
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- uses: actions/download-artifact@v4
- uses: actions/setup-node@v3
with:
node-version: '24'
registry-url: 'https://registry.npmjs.org'
- run: npm install
- id: pkg
run: |
content=`cat ./package.json | tr '\n' ' '`
echo "json=$content" >> $GITHUB_OUTPUT
- run: npm version --no-git-tag-version ${{ fromJson(steps.pkg.outputs.json).version }}-$(git rev-parse --short HEAD)+${{ github.run_id }}.${{ github.run_attempt }}
- run: npm publish --tag dev
3 changes: 1 addition & 2 deletions .gitlab/benchmarks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@ benchmarks:
- MAJOR_NODE_VERSION: 20
- MAJOR_NODE_VERSION: 22
- MAJOR_NODE_VERSION: 24
# TODO: Re-enable this once support for Node 25 is merged on main
# - MAJOR_NODE_VERSION: 25
- MAJOR_NODE_VERSION: 25
artifacts:
name: "reports"
paths:
Expand Down
8 changes: 2 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,14 +97,10 @@ Install [`pprof`][npm-url] with `npm` or add to your `package.json`.
pprof -http=: heap.pb.gz
```

* Collecting a heap profile with V8 allocation profile format:
* Collecting a heap profile with V8 allocation profile format:
```javascript
const profile = pprof.heap.v8Profile(pprof.heap.convertProfile);
const profile = await pprof.heap.v8Profile();
```
`v8Profile` accepts a callback and returns its result. Allocation nodes
are only valid during the callback, so copy/transform what you need
before returning. `heap.convertProfile` performs that conversion during
the callback, and `heap.profile()` uses it under the hood.

[build-image]: https://github.com/Datadog/pprof-nodejs/actions/workflows/build.yml/badge.svg?branch=main
[build-url]: https://github.com/Datadog/pprof-nodejs/actions/workflows/build.yml
Expand Down
1 change: 1 addition & 0 deletions benchmark/sirun/runall.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ if [ -n "${MAJOR_NODE_VERSION:-}" ]; then
source "${NVM_DIR:-usr/local/nvm}/nvm.sh"
fi

nvm install "${MAJOR_NODE_VERSION}"
nvm use "${MAJOR_NODE_VERSION}"

pushd ../../
Expand Down
18 changes: 18 additions & 0 deletions bindings/profilers/heap.cc
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,23 @@ NAN_METHOD(HeapProfiler::StopSamplingHeapProfiler) {
}
}

// Signature:
// getAllocationProfile(): AllocationProfileNode
NAN_METHOD(HeapProfiler::GetAllocationProfile) {
auto isolate = info.GetIsolate();
std::unique_ptr<v8::AllocationProfile> profile(
isolate->GetHeapProfiler()->GetAllocationProfile());
if (!profile) {
return Nan::ThrowError("Heap profiler is not enabled.");
}
v8::AllocationProfile::Node* root = profile->GetRootNode();
auto state = PerIsolateData::For(isolate)->GetHeapProfilerState();
if (state) {
state->OnNewProfile();
}
info.GetReturnValue().Set(TranslateAllocationProfile(root));
}

// mapAllocationProfile(callback): callback result
NAN_METHOD(HeapProfiler::MapAllocationProfile) {
if (info.Length() < 1 || !info[0]->IsFunction()) {
Expand Down Expand Up @@ -579,6 +596,7 @@ NAN_MODULE_INIT(HeapProfiler::Init) {
heapProfiler, "startSamplingHeapProfiler", StartSamplingHeapProfiler);
Nan::SetMethod(
heapProfiler, "stopSamplingHeapProfiler", StopSamplingHeapProfiler);
Nan::SetMethod(heapProfiler, "getAllocationProfile", GetAllocationProfile);
Nan::SetMethod(heapProfiler, "mapAllocationProfile", MapAllocationProfile);
Nan::SetMethod(heapProfiler, "monitorOutOfMemory", MonitorOutOfMemory);
Nan::Set(target,
Expand Down
4 changes: 4 additions & 0 deletions bindings/profilers/heap.hh
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ class HeapProfiler {
// stopSamplingHeapProfiler()
static NAN_METHOD(StopSamplingHeapProfiler);

// Signature:
// getAllocationProfile(): AllocationProfileNode
static NAN_METHOD(GetAllocationProfile);

// Signature:
// mapAllocationProfile(callback): callback result
static NAN_METHOD(MapAllocationProfile);
Expand Down
48 changes: 47 additions & 1 deletion bindings/profilers/wall.cc
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,15 @@ class PersistentContextPtr : public node::ObjectWrap {
}
};

inline void* GetAlignedPointerFromInternalField(Object* object, int index) {
#if NODE_MAJOR_VERSION >= 26
return object->GetAlignedPointerFromInternalField(
index, kEmbedderDataTypeTagDefault);
#else
return object->GetAlignedPointerFromInternalField(index);
#endif
}

// Maximum number of rounds in the GetV8ToEpochOffset
static constexpr int MAX_EPOCH_OFFSET_ATTEMPTS = 20;

Expand Down Expand Up @@ -920,6 +929,28 @@ v8::ProfilerId WallProfiler::StartInternal() {
return result.id;
}

NAN_METHOD(WallProfiler::Stop) {
if (info.Length() != 1) {
return Nan::ThrowTypeError("Stop must have one argument.");
}
if (!info[0]->IsBoolean()) {
return Nan::ThrowTypeError("Restart must be a boolean.");
}

bool restart = info[0].As<Boolean>()->Value();

WallProfiler* wallProfiler =
Nan::ObjectWrap::Unwrap<WallProfiler>(info.This());

v8::Local<v8::Value> profile;
auto err = wallProfiler->StopImpl(restart, profile);

if (!err.success) {
return Nan::ThrowTypeError(err.msg.c_str());
}
info.GetReturnValue().Set(profile);
}

// stopAndCollect(restart, callback): callback result
NAN_METHOD(WallProfiler::StopAndCollect) {
if (info.Length() != 2) {
Expand Down Expand Up @@ -1078,6 +1109,20 @@ Result WallProfiler::StopCore(bool restart, ProfileBuilder&& buildProfile) {
return {};
}

Result WallProfiler::StopImpl(bool restart, v8::Local<v8::Value>& profile) {
return StopCore(restart,
[&](const v8::CpuProfile* v8_profile,
bool hasCpuTime,
int64_t nonJSThreadsCpuTime,
ContextsByNode* contextsByNodePtr) {
profile = TranslateTimeProfile(v8_profile,
includeLines_,
contextsByNodePtr,
hasCpuTime,
nonJSThreadsCpuTime);
});
}

Result WallProfiler::StopAndCollectImpl(bool restart,
v8::Local<v8::Function> callback,
v8::Local<v8::Value>& result) {
Expand Down Expand Up @@ -1112,6 +1157,7 @@ NAN_MODULE_INIT(WallProfiler::Init) {
SetContext);

Nan::SetPrototypeMethod(tpl, "start", Start);
Nan::SetPrototypeMethod(tpl, "stop", Stop);
Nan::SetPrototypeMethod(tpl, "stopAndCollect", StopAndCollect);
Nan::SetPrototypeMethod(tpl, "dispose", Dispose);
Nan::SetPrototypeMethod(tpl,
Expand Down Expand Up @@ -1274,7 +1320,7 @@ ContextPtr WallProfiler::GetContextPtr(Isolate* isolate) {
auto wrapObj = reinterpret_cast<Object*>(wrapValue);
if (wrapObj->InternalFieldCount() > 0) {
return static_cast<PersistentContextPtr*>(
wrapObj->GetAlignedPointerFromInternalField(0))
GetAlignedPointerFromInternalField(wrapObj, 0))
->Get();
}
}
Expand Down
2 changes: 2 additions & 0 deletions bindings/profilers/wall.hh
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ class WallProfiler : public Nan::ObjectWrap {
v8::ProfilerId StartInternal();
template <typename ProfileBuilder>
Result StopCore(bool restart, ProfileBuilder&& buildProfile);
Result StopImpl(bool restart, v8::Local<v8::Value>& profile);
Result StopAndCollectImpl(bool restart,
v8::Local<v8::Function> callback,
v8::Local<v8::Value>& result);
Expand Down Expand Up @@ -188,6 +189,7 @@ class WallProfiler : public Nan::ObjectWrap {

static NAN_METHOD(New);
static NAN_METHOD(Start);
static NAN_METHOD(Stop);
static NAN_METHOD(StopAndCollect);
static NAN_METHOD(V8ProfilerStuckEventLoopDetected);
static NAN_METHOD(Dispose);
Expand Down
29 changes: 29 additions & 0 deletions bindings/translate-heap-profile.cc
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,30 @@ class HeapProfileTranslator : ProfileTranslator {
#undef X

public:
v8::Local<v8::Value> TranslateAllocationProfile(
v8::AllocationProfile::Node* node) {
v8::Local<v8::Array> children = NewArray(node->children.size());
for (size_t i = 0; i < node->children.size(); i++) {
Set(children, i, TranslateAllocationProfile(node->children[i]));
}

v8::Local<v8::Array> allocations = NewArray(node->allocations.size());
for (size_t i = 0; i < node->allocations.size(); i++) {
auto alloc = node->allocations[i];
Set(allocations,
i,
CreateAllocation(NewNumber(alloc.count), NewNumber(alloc.size)));
}

return CreateNode(node->name,
node->script_name,
NewInteger(node->script_id),
NewInteger(node->line_number),
NewInteger(node->column_number),
children,
allocations);
}

v8::Local<v8::Value> TranslateAllocationProfile(Node* node) {
v8::Local<v8::Array> children = NewArray(node->children.size());
for (size_t i = 0; i < node->children.size(); i++) {
Expand Down Expand Up @@ -118,6 +142,11 @@ std::shared_ptr<Node> TranslateAllocationProfileToCpp(
return new_node;
}

v8::Local<v8::Value> TranslateAllocationProfile(
v8::AllocationProfile::Node* node) {
return HeapProfileTranslator().TranslateAllocationProfile(node);
}

v8::Local<v8::Value> TranslateAllocationProfile(Node* node) {
return HeapProfileTranslator().TranslateAllocationProfile(node);
}
Expand Down
3 changes: 3 additions & 0 deletions bindings/translate-heap-profile.hh
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,7 @@ std::shared_ptr<Node> TranslateAllocationProfileToCpp(
v8::AllocationProfile::Node* node);

v8::Local<v8::Value> TranslateAllocationProfile(Node* node);
v8::Local<v8::Value> TranslateAllocationProfile(
v8::AllocationProfile::Node* node);

} // namespace dd
Loading
Loading