Skip to content

Commit

Permalink
Add CPU Profiling capability that works async (#336)
Browse files Browse the repository at this point in the history
Co-authored-by: zzheng <zzheng@atlassian.com>
  • Loading branch information
kiddkai and kiddkai committed Feb 13, 2023
1 parent 905abbe commit cc0aebb
Show file tree
Hide file tree
Showing 17 changed files with 652 additions and 292 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ build/
node_modules/
package-lock.json
out/
.cache/
compile_commands.json
43 changes: 19 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -253,10 +253,16 @@ when you transfer a `Reference` instance via some method which accepts transfera
will also include underlying reference handles created by isolated-vm like `Script` or `Context`
objects.

##### `isolate.createCpuProfiler()` *[CpuProfiler]*
Create a CPU profiler, for performance profiling
##### `isolate.startCpuProfiler(title)` *[void]*
Start a CPU profiler in the isolate, for performance profiling. It only collects cpu profiles when
the isolate is active in a thread.

* **return** A [`CpuProfiler`](#class-cpu-profiler) object.
##### `isolate.stopCpuProfiler(title)` *[Promise<Array<ThreadCpuProfile>>]*
Stop a CPU profiler previously started using the same title. It returns an array of profiles dependening
on how many times the isolate get activated in a thread.


* **return** An array of [`ThreadCpuProfile`](#thread-cpu-profile) objects.

### Class: `Context` *[transferable]*
A context is a sandboxed execution environment within an isolate. Each context contains its own
Expand Down Expand Up @@ -562,26 +568,6 @@ will still remain in memory, but this handle will no longer be active. Disposing
instances isn't super important, v8 is a lot better at cleaning these up automatically because
there's no inter-isolate dependencies.

### Class: `CpuProfiler`
A CpuProfiler is a cpu profiler allows you to run cpu profiling against the application
running inside a specific `isolate`. Which works similar to `console.profile` & `console.profileEnd`.

##### `profiler.startProfiling(title, recordSamples)` *[v8 CPUProfiler::StartProfiling](https://v8docs.nodesource.com/node-16.0/d2/d34/classv8_1_1_cpu_profiler.html#adc48f6de278c03fde38e74e6f1bd63a6)*

* `title` *[string]*
* `recordSamples` *[boolean]*

#### `profiler.stopProfiling(title)`
Stop the CPU profiling, the title should be the same as the one used in `startProfiling`.
After calling this API, the profiler will be disposed. If you want to run cpu profiling
again, you will need to call `isolate.createCpuProfiler()` again for a new CPU Profiler.

* `title` *[string]* should be the same string as `startProfiling`
* **return** *[CpuProfile]*

#### `profiler.dispose()`
Cleanup the cpu profiler from `v8` to release the resources occupied by the CPU profiler.

### Shared Options
Many methods in this library accept common options between them. They are documented here instead of
being colocated with each instance.
Expand Down Expand Up @@ -629,11 +615,20 @@ More advanced situations like transferring ownership of `ArrayBuffer` instances
use of [`ExternalCopy`](#class-externalcopy-transferable) or
[`Reference`](#class-reference-transferable).



##### `ThreadCpuProfile`
It's a object that contains a thread id and a [CpuProfile](#cpuprofile) info.

* `threadId` *[number]* - The thread that isolate runs on.
* `profile` *[CpuProfile]* - The [CpuProfile](#cpuprofile).

##### `CpuProfile`
The CpuProfile Object that can be `JSON.stringify(cpuProfile)`, and save to any external file system
for later reloaded into chrome dev tool or any other js performance tool to review.

* `title` *[string]* - The profile title.
The format should matches the definition in: https://chromedevtools.github.io/devtools-protocol/tot/Profiler/#type-Profile

* `startTime` *[number]* - The start timestamp when calling `.startProfiling`.
* `endTime` *[number]* - The end timestamp when calling `.stopProfiling`,
* `samples` *[Array<number>]* - All sample node id has been collected.
Expand Down
4 changes: 2 additions & 2 deletions binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
'src/external_copy/string.cc',
'src/isolate/allocator_nortti.cc',
'src/isolate/environment.cc',
"src/isolate/cpu_profile_manager.cc",
'src/isolate/executor.cc',
'src/isolate/holder.cc',
'src/isolate/inspector.cc',
Expand All @@ -86,8 +87,7 @@
'src/module/reference_handle.cc',
'src/module/script_handle.cc',
'src/module/session_handle.cc',
'src/module/transferable.cc',
"src/module/cpu_profiler_handle.cc"
'src/module/transferable.cc'
],
'conditions': [
[ 'OS != "win"', {
Expand Down
43 changes: 17 additions & 26 deletions isolated-vm.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,15 +108,23 @@ declare module "isolated-vm" {
getHeapStatisticsSync(): HeapStatistics;

/**
* Returns a Cpu Profiler from v8 for this specific isolate.
* Start profiling against the isolate with a specific title
*
* This function only returns a `single-use` CpuProfiler, once the
* `stopProfiling` is called, it will automatically dispose the
* `CpuProfiler`.
* @param title the profile title
*/
startCpuProfiler(title: string): void;

/**
* Stop profiling against the isolate with a specific title
* that started via `startCpuProfiler`. It will return more
* than one cpu profiles because isolate can be run in different
* threads. The `ThreadCpuProfile` contains the `thread_id` that
* the isolate was running in.
*
* Any new CPU profiling require to call this function again.
* @param title
*/
createCpuProfiler(): CpuProfiler;
stopCpuProfiler(title: string): Promise<ThreadCpuProfile[]>;

}

export type IsolateOptions = {
Expand Down Expand Up @@ -598,29 +606,12 @@ declare module "isolated-vm" {
createSync(context: Context): Reference<any>;
}

export class CpuProfiler {

/**
* Similar to `console.profile(title)`
* @param title The title of the cpu profile
* @param recordSample Should record sample or not, `true` to genereate data.
*/
startProfiling(title: string, recordSample: boolean);

/**
* Similar to `console.profileEnd(title)`
* @param title The title of cpu profile
*/
stopProfiling(title: string): CpuProfile;

/**
* Dispose the CpuProfiler
*/
dispose(): void;
export type ThreadCpuProfile = {
threadId: number;
profile: CpuProfile;
}

export type CpuProfile = {
title: string;
startTime: number;
endTime: number;
samples: number[];
Expand Down
Loading

0 comments on commit cc0aebb

Please sign in to comment.