Skip to content
Open
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
34 changes: 27 additions & 7 deletions bindings/profilers/wall.cc
Original file line number Diff line number Diff line change
Expand Up @@ -511,7 +511,8 @@ void WallProfiler::Cleanup(Isolate* isolate) {

ContextsByNode WallProfiler::GetContextsByNode(CpuProfile* profile,
ContextBuffer& contexts,
int64_t startCpuTime) {
int64_t startCpuTime,
int64_t V8toEpochOffset) {
ContextsByNode contextsByNode;

auto sampleCount = profile->GetSamplesCount();
Expand Down Expand Up @@ -551,7 +552,6 @@ ContextsByNode WallProfiler::GetContextsByNode(CpuProfile* profile,
return object;
};
#endif
auto V8toEpochOffset = GetV8ToEpochOffset();
auto lastCpuTime = startCpuTime;

// skip first sample because it's the one taken on profiler start, outside of
Expand Down Expand Up @@ -1160,6 +1160,9 @@ Result WallProfiler::StopCore(bool restart, ProfileBuilder&& buildProfile) {
ContextsByNode* contextsByNodePtr = nullptr;
int64_t nonJSThreadsCpuTime = 0;
bool hasCpuTime = false;
// One offset for both the sample timestamps and the profile's epochOffset,
// so that they agree.
auto v8ToEpochOffset = GetV8ToEpochOffset();

if (withContexts_) {
if (isMainThread_ && collectCpuTime_) {
Expand All @@ -1173,13 +1176,17 @@ Result WallProfiler::StopCore(bool restart, ProfileBuilder&& buildProfile) {
std::max(processCpu - totalWorkerCpu, ProcessCpuClock::duration{})
.count();
}
contextsByNode =
GetContextsByNode(v8_profile, contexts, startThreadCpuTime);
contextsByNode = GetContextsByNode(
v8_profile, contexts, startThreadCpuTime, v8ToEpochOffset);
contextsByNodePtr = &contextsByNode;
hasCpuTime = collectCpuTime_;
}

buildProfile(v8_profile, hasCpuTime, nonJSThreadsCpuTime, contextsByNodePtr);
buildProfile(v8_profile,
hasCpuTime,
nonJSThreadsCpuTime,
contextsByNodePtr,
v8ToEpochOffset);

v8_profile->Delete();

Expand All @@ -1197,17 +1204,28 @@ Result WallProfiler::StopCore(bool restart, ProfileBuilder&& buildProfile) {
return {};
}

// Sets the offset that converts the profile's V8 timer values (startTime,
// endTime) to microseconds since the UNIX epoch, like sample timestamps.
static void SetEpochOffset(v8::Local<v8::Value> profile,
int64_t v8ToEpochOffset) {
Nan::Set(profile.As<v8::Object>(),
Nan::New("epochOffset").ToLocalChecked(),
Nan::New<v8::Number>(static_cast<double>(v8ToEpochOffset)));
}

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) {
ContextsByNode* contextsByNodePtr,
int64_t v8ToEpochOffset) {
profile = TranslateTimeProfile(v8_profile,
includeLines_,
contextsByNodePtr,
hasCpuTime,
nonJSThreadsCpuTime);
SetEpochOffset(profile, v8ToEpochOffset);
});
}

Expand All @@ -1219,11 +1237,13 @@ Result WallProfiler::StopAndCollectImpl(bool restart,
[&](const v8::CpuProfile* v8_profile,
bool hasCpuTime,
int64_t nonJSThreadsCpuTime,
ContextsByNode* contextsByNodePtr) {
ContextsByNode* contextsByNodePtr,
int64_t v8ToEpochOffset) {
auto* isolate = Isolate::GetCurrent();
TimeProfileViewState state{includeLines_, contextsByNodePtr, {}};
auto profile_view = BuildTimeProfileView(
v8_profile, hasCpuTime, nonJSThreadsCpuTime, state);
SetEpochOffset(profile_view, v8ToEpochOffset);
v8::Local<v8::Value> argv[] = {profile_view};
auto cb_result = Nan::Call(
callback, isolate->GetCurrentContext()->Global(), 1, argv);
Expand Down
3 changes: 2 additions & 1 deletion bindings/profilers/wall.hh
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,8 @@ class WallProfiler : public Nan::ObjectWrap {

ContextsByNode GetContextsByNode(v8::CpuProfile* profile,
ContextBuffer& contexts,
int64_t startCpuTime);
int64_t startCpuTime,
int64_t v8ToEpochOffset);

bool waitForSignal(uint64_t targetCallCount = 0);
static void CleanupHook(void* data);
Expand Down
114 changes: 73 additions & 41 deletions ts/src/heap-profiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
DEFAULT_COLUMN_NUMBERS,
serializeHeapProfile,
} from './profile-serializer';
import {NormalizedProfile, normalizeHeapProfile} from './profile-normalizer';
import {SourceMapper} from './sourcemapper/sourcemapper';
import {
AllocationProfileNode,
Expand Down Expand Up @@ -103,6 +104,31 @@ export function profile(
);
}

/**
* Collects a profile and returns it normalized into a format-neutral
* representation. Throws if heap profiler is not enabled.
*/
export function normalizedProfile(
ignoreSamplePath?: string,
sourceMapper?: SourceMapper,
generateLabels?: GenerateAllocationLabelsFunction,
): NormalizedProfile {
return collectProfile(root => {
const startTimeNanos = BigInt(Date.now()) * 1_000_000n;
return normalizeHeapProfile(
addExternalMemoryNode(root, startedWithAllocations),
{
startTimeNanos,
intervalBytes: heapIntervalBytes,
ignoreSamplesPath: ignoreSamplePath,
sourceMapper,
generateLabels,
allocations: startedWithAllocations,
},
);
});
}

function collectProfile<T>(
processProfile: (
root: AllocationProfileNode | AllocationProfileNodeWithStats,
Expand Down Expand Up @@ -134,48 +160,9 @@ export function convertProfile(
columnNumbers: ColumnNumbers = DEFAULT_COLUMN_NUMBERS,
): Profile {
const allocations = startedWithAllocations;
const startTimeNanos = Date.now() * 1000 * 1000;
// Add node for external memory usage.
// Current type definitions do not have external.
// TODO: remove any once type definition is updated to include external.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const {external}: {external: number} = process.memoryUsage() as any;
let root: AllocationProfileNode | AllocationProfileNodeWithStats;
if (external > 0) {
if (allocations) {
const externalNode: AllocationProfileNodeWithStats = {
name: '(external)',
scriptName: '',
children: [],
allocations: [
{
inuseObjects: 1,
inuseSpaceBytes: external,
allocObjects: 1,
allocSpaceBytes: external,
},
],
};
const allocationRoot = rootNode as AllocationProfileNodeWithStats;
root = {
...allocationRoot,
children: [...allocationRoot.children, externalNode],
};
} else {
const externalNode: AllocationProfileNode = {
name: '(external)',
scriptName: '',
children: [],
allocations: [{sizeBytes: external, count: 1}],
};
const heapRoot = rootNode as AllocationProfileNode;
root = {...heapRoot, children: [...heapRoot.children, externalNode]};
}
} else {
root = rootNode;
}
const startTimeNanos = BigInt(Date.now()) * 1_000_000n;
return serializeHeapProfile(
root,
addExternalMemoryNode(rootNode, allocations),
startTimeNanos,
heapIntervalBytes,
ignoreSamplePath,
Expand All @@ -186,6 +173,51 @@ export function convertProfile(
);
}

/**
* Returns the profile with a node for external memory usage added under the
* root, if there is any external memory. Does not modify the input.
*/
function addExternalMemoryNode(
rootNode: AllocationProfileNode | AllocationProfileNodeWithStats,
allocations: boolean,
): AllocationProfileNode | AllocationProfileNodeWithStats {
// Current type definitions do not have external.
// TODO: remove any once type definition is updated to include external.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const {external}: {external: number} = process.memoryUsage() as any;
if (external <= 0) {
return rootNode;
}
if (allocations) {
const externalNode: AllocationProfileNodeWithStats = {
name: '(external)',
scriptName: '',
children: [],
allocations: [
{
inuseObjects: 1,
inuseSpaceBytes: external,
allocObjects: 1,
allocSpaceBytes: external,
},
],
};
const allocationRoot = rootNode as AllocationProfileNodeWithStats;
return {
...allocationRoot,
children: [...allocationRoot.children, externalNode],
};
}
const externalNode: AllocationProfileNode = {
name: '(external)',
scriptName: '',
children: [],
allocations: [{sizeBytes: external, count: 1}],
};
const heapRoot = rootNode as AllocationProfileNode;
return {...heapRoot, children: [...heapRoot.children, externalNode]};
}

/**
* Collects a profile and returns it serialized in pprof format using lazy V2 API.
* Throws if heap profiler is not enabled.
Expand Down
22 changes: 22 additions & 0 deletions ts/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,26 @@ export {
TimeProfileNode,
ProfileNode,
LabelSet,
TimeProfile,
} from './v8-types';
export {
Frame,
frameFunctionName,
HeapProfileNormalizerOptions,
NormalizedProfile,
NormalizedSample,
normalizeHeapProfile,
normalizeTimeProfile,
ProfileMetadata,
ProfileVisitor,
SampleLabel,
SampleLabelValue,
SampleType,
TimeProfileNormalizerOptions,
visitHeapProfile,
visitTimeProfile,
VisitResult,
} from './profile-normalizer';

export {HeapLimitExtensionSize} from './heap-profiler';

Expand All @@ -37,6 +56,8 @@ export const time = {
profile: timeProfiler.profile,
start: timeProfiler.start,
stop: timeProfiler.stop,
stopNormalized: timeProfiler.stopNormalized,
stopRaw: timeProfiler.stopRaw,
profileV2: timeProfiler.profileV2,
getContext: timeProfiler.getContext,
setContext: timeProfiler.setContext,
Expand All @@ -54,6 +75,7 @@ export const heap = {
stop: heapProfiler.stop,
profile: heapProfiler.profile,
profileV2: heapProfiler.profileV2,
normalizedProfile: heapProfiler.normalizedProfile,
convertProfile: heapProfiler.convertProfile,
v8Profile: heapProfiler.v8Profile,
monitorOutOfMemory: heapProfiler.monitorOutOfMemory,
Expand Down
Loading
Loading