pf2 0.5.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/pf2/reporter.rb CHANGED
@@ -1,393 +1,5 @@
1
- require 'json'
1
+ require_relative './reporter/firefox_profiler'
2
2
 
3
3
  module Pf2
4
- # Generates Firefox Profiler's "processed profile format"
5
- # https://github.com/firefox-devtools/profiler/blob/main/docs-developer/processed-profile-format.md
6
- class Reporter
7
- def initialize(profile)
8
- @profile = Reporter.deep_intize_keys(profile)
9
- end
10
-
11
- def inspect
12
- "" # TODO: provide something better
13
- end
14
-
15
- def emit
16
- report = {
17
- meta: {
18
- interval: 10, # ms; TODO: replace with actual interval
19
- start_time: 0,
20
- process_type: 0,
21
- product: 'ruby',
22
- stackwalk: 0,
23
- version: 28,
24
- preprocessed_profile_version: 47,
25
- symbolicated: true,
26
- categories: [
27
- {
28
- name: "Logs",
29
- color: "grey",
30
- subcategories: ["Unused"],
31
- },
32
- {
33
- name: "Ruby",
34
- color: "red",
35
- subcategories: ["Code"],
36
- },
37
- {
38
- name: "Native",
39
- color: "blue",
40
- subcategories: ["Code"],
41
- },
42
- {
43
- name: "Native",
44
- color: "lightblue",
45
- subcategories: ["Code"],
46
- },
47
- ],
48
- marker_schema: [],
49
- },
50
- libs: [],
51
- counters: [],
52
- threads: @profile[:threads].values.map {|th| ThreadReport.new(th).emit }
53
- }
54
- Reporter.deep_camelize_keys(report)
55
- end
56
-
57
- class ThreadReport
58
- def initialize(thread)
59
- @thread = thread
60
-
61
- # Populated in other methods
62
- @func_id_map = {}
63
- @frame_id_map = {}
64
- @stack_tree_id_map = {}
65
-
66
- @string_table = {}
67
- end
68
-
69
- def inspect
70
- "" # TODO: provide something better
71
- end
72
-
73
- def emit
74
- x = weave_native_stack(@thread[:stack_tree])
75
- @thread[:stack_tree] = x
76
- func_table = build_func_table
77
- frame_table = build_frame_table
78
- stack_table = build_stack_table(func_table, frame_table)
79
- samples = build_samples
80
-
81
- string_table = build_string_table
82
-
83
- {
84
- process_type: 'default',
85
- process_name: 'ruby',
86
- process_startup_time: 0,
87
- process_shutdown_time: nil,
88
- register_time: 0,
89
- unregister_time: nil,
90
- paused_ranges: [],
91
- name: "Thread (tid: #{@thread[:thread_id]})",
92
- is_main_thread: true,
93
- is_js_tracer: true,
94
- # FIXME: We can fill the correct PID only after we correctly fill is_main_thread
95
- # (only one thread could be marked as is_main_thread in a single process)
96
- pid: @thread[:thread_id],
97
- tid: @thread[:thread_id],
98
- samples: samples,
99
- markers: markers,
100
- stack_table: stack_table,
101
- frame_table: frame_table,
102
- string_array: build_string_table,
103
- func_table: func_table,
104
- resource_table: {
105
- lib: [],
106
- name: [],
107
- host: [],
108
- type: [],
109
- length: 0,
110
- },
111
- native_symbols: [],
112
- }
113
- end
114
-
115
- def build_samples
116
- ret = {
117
- event_delay: [],
118
- stack: [],
119
- time: [],
120
- duration: [],
121
- # weight: nil,
122
- # weight_type: 'samples',
123
- }
124
-
125
- @thread[:samples].each do |sample|
126
- ret[:stack] << @stack_tree_id_map[sample[:stack_tree_id]]
127
- ret[:time] << sample[:elapsed_ns] / 1000000 # ns -> ms
128
- ret[:duration] << 1
129
- ret[:event_delay] << 0
130
- end
131
-
132
- ret[:length] = ret[:stack].length
133
- ret
134
- end
135
-
136
- def build_frame_table
137
- ret = {
138
- address: [],
139
- category: [],
140
- subcategory: [],
141
- func: [],
142
- inner_window_id: [],
143
- implementation: [],
144
- line: [],
145
- column: [],
146
- optimizations: [],
147
- inline_depth: [],
148
- native_symbol: [],
149
- }
150
-
151
- @thread[:frames].each.with_index do |(id, frame), i|
152
- ret[:address] << frame[:address].to_s
153
- ret[:category] << 1
154
- ret[:subcategory] << 1
155
- ret[:func] << i # TODO
156
- ret[:inner_window_id] << nil
157
- ret[:implementation] << nil
158
- ret[:line] << frame[:callsite_lineno]
159
- ret[:column] << nil
160
- ret[:optimizations] << nil
161
- ret[:inline_depth] << 0
162
- ret[:native_symbol] << nil
163
-
164
- @frame_id_map[id] = i
165
- end
166
-
167
- ret[:length] = ret[:address].length
168
- ret
169
- end
170
-
171
- def build_func_table
172
- ret = {
173
- name: [],
174
- is_js: [],
175
- relevant_for_js: [],
176
- resource: [],
177
- file_name: [],
178
- line_number: [],
179
- column_number: [],
180
- }
181
-
182
- @thread[:frames].each.with_index do |(id, frame), i|
183
- native = (frame[:entry_type] == 'Native')
184
- label = "#{native ? "Native: " : ""}#{frame[:full_label]}"
185
- ret[:name] << string_id(label)
186
- ret[:is_js] << !native
187
- ret[:relevant_for_js] << false
188
- ret[:resource] << -1
189
- ret[:file_name] << string_id(frame[:file_name])
190
- ret[:line_number] << frame[:function_first_lineno]
191
- ret[:column_number] << nil
192
-
193
- @func_id_map[id] = i
194
- end
195
-
196
- ret[:length] = ret[:name].length
197
- ret
198
- end
199
-
200
- # "Weave" the native stack into the Ruby stack.
201
- #
202
- # Strategy:
203
- # - Split the stack into Ruby and Native parts
204
- # - Start from the root of the Native stack
205
- # - Dig in to the native stack until we hit a rb_vm_exec(), which marks a call into Ruby code
206
- # - Switch to Ruby stack. Keep digging until we hit a Cfunc call, then switch back to Native stack
207
- # - Repeat until we consume the entire stack
208
- def weave_native_stack(stack_tree)
209
- collected_paths = []
210
- tree_to_array_of_paths(stack_tree, @thread[:frames], [], collected_paths)
211
- collected_paths = collected_paths.map do |path|
212
- next if path.size == 0
213
-
214
- new_path = []
215
- new_path << path.shift # root
216
-
217
- # Split the stack into Ruby and Native parts
218
- native_path, ruby_path = path.partition do |frame|
219
- frame_id = frame[:frame_id]
220
- @thread[:frames][frame_id][:entry_type] == 'Native'
221
- end
222
-
223
- mode = :native
224
-
225
- loop do
226
- break if ruby_path.size == 0 && native_path.size == 0
227
-
228
- case mode
229
- when :ruby
230
- if ruby_path.size == 0
231
- mode = :native
232
- next
233
- end
234
-
235
- next_node = ruby_path[0]
236
- new_path << ruby_path.shift
237
- next_node_frame = @thread[:frames][next_node[:frame_id]]
238
- if native_path.size > 0
239
- # Search the remainder of the native stack for the same address
240
- # Note: This isn't a very efficient way for the job... but it still works
241
- ruby_addr = next_node_frame[:address]
242
- native_path[0..].each do |native_node|
243
- native_addr = @thread[:frames][native_node[:frame_id]][:address]
244
- if ruby_addr && native_addr && ruby_addr == native_addr
245
- # A match has been found. Switch to native mode
246
- mode = :native
247
- break
248
- end
249
- end
250
- end
251
- when :native
252
- if native_path.size == 0
253
- mode = :ruby
254
- next
255
- end
256
-
257
- # Dig until we meet a rb_vm_exec
258
- next_node = native_path[0]
259
- new_path << native_path.shift
260
- if @thread[:frames][next_node[:frame_id]][:full_label] =~ /vm_exec_core/ # VM_EXEC in vm_exec.h
261
- mode = :ruby
262
- end
263
- end
264
- end
265
-
266
- new_path
267
- end
268
-
269
- # reconstruct stack_tree
270
- new_stack_tree = array_of_paths_to_tree(collected_paths)
271
- new_stack_tree
272
- end
273
-
274
- def tree_to_array_of_paths(stack_tree, frames, path, collected_paths)
275
- new_path = path + [{ frame_id: stack_tree[:frame_id], node_id: stack_tree[:node_id] }]
276
- if stack_tree[:children].empty?
277
- collected_paths << new_path
278
- else
279
- stack_tree[:children].each do |frame_id, child|
280
- tree_to_array_of_paths(child, frames, new_path, collected_paths)
281
- end
282
- end
283
- end
284
-
285
- def array_of_paths_to_tree(paths)
286
- new_stack_tree = { children: {}, node_id: 0, frame_id: 0 }
287
- paths.each do |path|
288
- current = new_stack_tree
289
- path[1..].each do |frame|
290
- frame_id = frame[:frame_id]
291
- node_id = frame[:node_id]
292
- current[:children][frame_id] ||= { children: {}, node_id: node_id, frame_id: frame_id }
293
- current = current[:children][frame_id]
294
- end
295
- end
296
- new_stack_tree
297
- end
298
-
299
- def build_stack_table(func_table, frame_table)
300
- ret = {
301
- frame: [],
302
- category: [],
303
- subcategory: [],
304
- prefix: [],
305
- }
306
-
307
- queue = []
308
-
309
- @thread[:stack_tree][:children].each {|_, c| queue << [nil, c] }
310
-
311
- loop do
312
- break if queue.size == 0
313
-
314
- prefix, node = queue.shift
315
- ret[:frame] << @frame_id_map[node[:frame_id]]
316
- ret[:category] << (build_string_table[func_table[:name][frame_table[:func][@frame_id_map[node[:frame_id]]]]].start_with?('Native:') ? 2 : 1)
317
- ret[:subcategory] << nil
318
- ret[:prefix] << prefix
319
-
320
- # The index of this frame - children can refer to this frame using this index as prefix
321
- frame_index = ret[:frame].length - 1
322
- @stack_tree_id_map[node[:node_id]] = frame_index
323
-
324
- # Enqueue children nodes
325
- node[:children].each {|_, c| queue << [frame_index, c] }
326
- end
327
-
328
- ret[:length] = ret[:frame].length
329
- ret
330
- end
331
-
332
- def build_string_table
333
- @string_table.sort_by {|_, v| v}.map {|s| s[0] }
334
- end
335
-
336
- def string_id(str)
337
- return @string_table[str] if @string_table.has_key?(str)
338
- @string_table[str] = @string_table.length
339
- @string_table[str]
340
- end
341
-
342
- def markers
343
- {
344
- data: [],
345
- name: [],
346
- time: [],
347
- start_time: [],
348
- end_time: [],
349
- phase: [],
350
- category: [],
351
- length: 0
352
- }
353
- end
354
- end
355
-
356
- # Util functions
357
- class << self
358
- def snake_to_camel(s)
359
- return "isJS" if s == "is_js"
360
- return "relevantForJS" if s == "relevant_for_js"
361
- return "innerWindowID" if s == "inner_window_id"
362
- s.split('_').inject([]) {|buffer, p| buffer.push(buffer.size == 0 ? p : p.capitalize) }.join
363
- end
364
-
365
- def deep_transform_keys(value, &block)
366
- case value
367
- when Array
368
- value.map {|v| deep_transform_keys(v, &block) }
369
- when Hash
370
- Hash[value.map {|k, v| [yield(k), deep_transform_keys(v, &block)] }]
371
- else
372
- value
373
- end
374
- end
375
-
376
- def deep_camelize_keys(value)
377
- deep_transform_keys(value) do |key|
378
- snake_to_camel(key.to_s).to_sym
379
- end
380
- end
381
-
382
- def deep_intize_keys(value)
383
- deep_transform_keys(value) do |key|
384
- if key.to_s.to_i.to_s == key.to_s
385
- key.to_s.to_i
386
- else
387
- key
388
- end
389
- end
390
- end
391
- end
392
- end
4
+ module Reporter; end
393
5
  end
data/lib/pf2/serve.rb CHANGED
@@ -28,7 +28,7 @@ module Pf2
28
28
  profile = JSON.parse(profile, symbolize_names: true, max_nesting: false)
29
29
  res.header['Content-Type'] = 'application/json'
30
30
  res.header['Access-Control-Allow-Origin'] = '*'
31
- res.body = JSON.generate(Pf2::Reporter.new((profile)).emit)
31
+ res.body = JSON.generate(Pf2::Reporter::FirefoxProfiler.new((profile)).emit)
32
32
  Pf2.start
33
33
  end
34
34
 
data/lib/pf2/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Pf2
2
- VERSION = '0.5.1'
2
+ VERSION = '0.6.0'
3
3
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pf2
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daisuke Aritomo
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2024-03-24 00:00:00.000000000 Z
10
+ date: 2024-07-14 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: rake-compiler
@@ -171,6 +171,9 @@ files:
171
171
  - ext/pf2/src/ruby_internal_apis.rs
172
172
  - ext/pf2/src/sample.rs
173
173
  - ext/pf2/src/scheduler.rs
174
+ - ext/pf2/src/serialization.rs
175
+ - ext/pf2/src/serialization/profile.rs
176
+ - ext/pf2/src/serialization/serializer.rs
174
177
  - ext/pf2/src/session.rs
175
178
  - ext/pf2/src/session/configuration.rs
176
179
  - ext/pf2/src/session/new_thread_watcher.rs
@@ -183,6 +186,7 @@ files:
183
186
  - lib/pf2.rb
184
187
  - lib/pf2/cli.rb
185
188
  - lib/pf2/reporter.rb
189
+ - lib/pf2/reporter/firefox_profiler.rb
186
190
  - lib/pf2/serve.rb
187
191
  - lib/pf2/session.rb
188
192
  - lib/pf2/version.rb