pf2 0.5.1 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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