Support stackprof object mode (#391)

This PR attempts to support stackprof's object mode which tracks the number of allocated objects. This differs from the other modes (cpu and wall) by taking samples every time a Ruby object is allocated using Ruby's [`NEWOBJ` tracepoint](df24b85953/ext/stackprof/stackprof.c (L198-L199)).

When importing an object mode profile into speedscope today it still works but what you see is a profile using time units. The profile will only have samples for when an object was allocated which means even if time is reported, the profile is not really meaningful when looking at time.

To address this I've done three things when `mode` is `object`:
+ adjusted the total size of the `StackListProfileBuilder` to use the number of samples (since each sample is one allocation)
+ adjusted the weight of each sample to be `nSamples` (which I believe is always `1` but I'm not positive)
+ do not set the value formatter to a time formatter

Here's what it looks like before and after my changes (note the units and weight of samples):

wall (before) | object (before) | object (after)
-- | -- | --
<img width="1624" alt="Screen Shot 2022-05-11 at 4 51 31 PM" src="https://user-images.githubusercontent.com/898172/167945635-2401ca73-4de7-4559-b884-cf8947ca9738.png"> | <img width="1624" alt="Screen Shot 2022-05-11 at 4 51 34 PM" src="https://user-images.githubusercontent.com/898172/167945641-ef302a60-730b-4afd-8e44-5f02e54b3cb7.png"> | <img width="1624" alt="Screen Shot 2022-05-11 at 4 51 42 PM" src="https://user-images.githubusercontent.com/898172/167945643-5611b267-f8b2-4227-a2bf-7145c4030aa2.png">

<details>
<summary>Test code</summary>

```ruby
require 'stackprof'
require 'json'

def do_test
  5.times do
    make_a_word
  end
end

def make_a_word
  ('a'..'z').to_a.shuffle.map(&:upcase).join
end

StackProf.start(mode: :object, interval: 1, raw: true)
do_test
StackProf.stop

File.write('tmp/object_profile.json', JSON.generate(StackProf.results))

StackProf.start(mode: :wall, interval: 1, raw: true)
do_test
StackProf.stop

File.write('tmp/wall_profile.json', JSON.generate(StackProf.results))
```
</details>
This commit is contained in:
Alex Coco 2022-05-17 03:05:49 -04:00 committed by GitHub
parent 63f3bc0395
commit ca8fcb48cc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 245 additions and 8 deletions

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,25 @@
require 'json'
require 'stackprof'
def a
for i in 0..2 do
b
c
end
end
def b
Object.new
end
def c
for i in 0..5
(1..10).to_a.sample(3).sort
end
end
profile = StackProf.run(mode: :object, raw: true) do
a
end
puts JSON.generate(profile)

View File

@ -851,6 +851,201 @@ Object {
}
`;
exports[`importFromStackprof object mode 1`] = `
Object {
"frames": Array [
Frame {
"col": undefined,
"file": "../../alexcoco/speedscope/sample/programs/ruby/object.rb",
"key": 4317728280,
"line": undefined,
"name": "<main>",
"selfWeight": 0,
"totalWeight": 103,
},
Frame {
"col": undefined,
"file": "/Users/alex/src/github.com/alexcoco/speedscope/sample/programs/ruby/object.rb",
"key": 4376547240,
"line": undefined,
"name": "<main>",
"selfWeight": 0,
"totalWeight": 103,
},
Frame {
"col": undefined,
"file": "<cfunc>",
"key": 4379035920,
"line": null,
"name": "StackProf.run",
"selfWeight": 0,
"totalWeight": 103,
},
Frame {
"col": undefined,
"file": "/Users/alex/src/github.com/alexcoco/speedscope/sample/programs/ruby/object.rb",
"key": 4317464320,
"line": 21,
"name": "block in <main>",
"selfWeight": 1,
"totalWeight": 103,
},
Frame {
"col": undefined,
"file": "/Users/alex/src/github.com/alexcoco/speedscope/sample/programs/ruby/object.rb",
"key": 4379033920,
"line": 4,
"name": "Object#a",
"selfWeight": 2,
"totalWeight": 102,
},
Frame {
"col": undefined,
"file": "<cfunc>",
"key": 4317869400,
"line": null,
"name": "Range#each",
"selfWeight": 0,
"totalWeight": 102,
},
Frame {
"col": undefined,
"file": "/Users/alex/src/github.com/alexcoco/speedscope/sample/programs/ruby/object.rb",
"key": 4379033880,
"line": 11,
"name": "Object#b",
"selfWeight": 1,
"totalWeight": 5,
},
Frame {
"col": undefined,
"file": "<cfunc>",
"key": 4318054440,
"line": null,
"name": "Class#new",
"selfWeight": 4,
"totalWeight": 4,
},
Frame {
"col": undefined,
"file": "/Users/alex/src/github.com/alexcoco/speedscope/sample/programs/ruby/object.rb",
"key": 4379033840,
"line": 15,
"name": "Object#c",
"selfWeight": 2,
"totalWeight": 95,
},
Frame {
"col": undefined,
"file": "<cfunc>",
"key": 4317868920,
"line": null,
"name": "Range#to_a",
"selfWeight": 1,
"totalWeight": 37,
},
Frame {
"col": undefined,
"file": "<cfunc>",
"key": 4379016640,
"line": null,
"name": "Enumerable#to_a",
"selfWeight": 36,
"totalWeight": 36,
},
Frame {
"col": undefined,
"file": "<internal:array>",
"key": 4317563560,
"line": 60,
"name": "Array#sample",
"selfWeight": 20,
"totalWeight": 20,
},
Frame {
"col": undefined,
"file": "<cfunc>",
"key": 4317922760,
"line": null,
"name": "Array#sort",
"selfWeight": 36,
"totalWeight": 36,
},
],
"name": "object-stackprof.json",
"stacks": Array [
"<main>;<main>;StackProf.run;block in <main> 1",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a 1",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#b 1",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#b;Class#new 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a 1",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c 1",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Range#to_a 1",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Range#to_a;Enumerable#to_a 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c 1",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Array#sample 3",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Array#sort 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Range#to_a;Enumerable#to_a 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Array#sample 1",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Array#sort 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Range#to_a;Enumerable#to_a 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Array#sample 1",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Array#sort 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Range#to_a;Enumerable#to_a 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Array#sample 1",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Array#sort 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Range#to_a;Enumerable#to_a 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Array#sample 1",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Array#sort 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Range#to_a;Enumerable#to_a 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Array#sample 1",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Array#sort 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#b;Class#new 1",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Range#to_a;Enumerable#to_a 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Array#sample 1",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Array#sort 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Range#to_a;Enumerable#to_a 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Array#sample 1",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Array#sort 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Range#to_a;Enumerable#to_a 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Array#sample 1",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Array#sort 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Range#to_a;Enumerable#to_a 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Array#sample 1",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Array#sort 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Range#to_a;Enumerable#to_a 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Array#sample 1",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Array#sort 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Range#to_a;Enumerable#to_a 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Array#sample 1",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Array#sort 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#b;Class#new 1",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Range#to_a;Enumerable#to_a 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Array#sample 1",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Array#sort 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Range#to_a;Enumerable#to_a 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Array#sample 1",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Array#sort 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Range#to_a;Enumerable#to_a 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Array#sample 1",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Array#sort 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Range#to_a;Enumerable#to_a 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Array#sample 1",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Array#sort 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Range#to_a;Enumerable#to_a 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Array#sample 1",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Array#sort 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Range#to_a;Enumerable#to_a 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Array#sample 1",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Array#sort 2",
],
}
`;
exports[`importFromStackprof object mode: indexToView 1`] = `0`;
exports[`importFromStackprof object mode: profileGroup.name 1`] = `"object-stackprof.json"`;
exports[`importFromStackprof: indexToView 1`] = `0`;
exports[`importFromStackprof: profileGroup.name 1`] = `"simple-stackprof.json"`;

View File

@ -3,3 +3,7 @@ import {checkProfileSnapshot} from '../lib/test-utils'
test('importFromStackprof', async () => {
await checkProfileSnapshot('./sample/profiles/stackprof/simple-stackprof.json')
})
test('importFromStackprof object mode', async () => {
await checkProfileSnapshot('./sample/profiles/stackprof/object-stackprof.json')
})

View File

@ -11,15 +11,19 @@ interface StackprofFrame {
export interface StackprofProfile {
frames: {[number: string]: StackprofFrame}
mode: string
raw: number[]
raw_timestamp_deltas: number[]
samples: number
}
export function importFromStackprof(stackprofProfile: StackprofProfile): Profile {
const duration = stackprofProfile.raw_timestamp_deltas.reduce((a, b) => a + b, 0)
const profile = new StackListProfileBuilder(duration)
const {frames, mode, raw, raw_timestamp_deltas, samples} = stackprofProfile
const objectMode = mode == 'object'
const size = objectMode ? samples : stackprofProfile.raw_timestamp_deltas.reduce((a, b) => a + b, 0)
const profile = new StackListProfileBuilder(size)
const {frames, raw, raw_timestamp_deltas} = stackprofProfile
let sampleIndex = 0
let prevStack: FrameInfo[] = []
@ -40,15 +44,23 @@ export function importFromStackprof(stackprofProfile: StackprofProfile): Profile
}
const nSamples = raw[i++]
let sampleDuration = 0
for (let j = 0; j < nSamples; j++) {
sampleDuration += raw_timestamp_deltas[sampleIndex++]
if (objectMode) {
profile.appendSampleWithWeight(stack, nSamples)
} else {
let sampleDuration = 0
for (let j = 0; j < nSamples; j++) {
sampleDuration += raw_timestamp_deltas[sampleIndex++]
}
profile.appendSampleWithWeight(stack, sampleDuration)
}
profile.appendSampleWithWeight(stack, sampleDuration)
prevStack = stack
}
profile.setValueFormatter(new TimeFormatter('microseconds'))
if (!objectMode) {
profile.setValueFormatter(new TimeFormatter('microseconds'))
}
return profile.build()
}