pf2 0.5.2 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +20 -0
  3. data/Cargo.lock +7 -27
  4. data/README.md +1 -1
  5. data/crates/backtrace-sys2/build.rs +1 -4
  6. data/crates/backtrace-sys2/src/libbacktrace/Makefile.am +116 -31
  7. data/crates/backtrace-sys2/src/libbacktrace/Makefile.in +295 -141
  8. data/crates/backtrace-sys2/src/libbacktrace/README.md +11 -1
  9. data/crates/backtrace-sys2/src/libbacktrace/alloc.c +1 -1
  10. data/crates/backtrace-sys2/src/libbacktrace/allocfail.c +1 -1
  11. data/crates/backtrace-sys2/src/libbacktrace/allocfail.sh +1 -1
  12. data/crates/backtrace-sys2/src/libbacktrace/atomic.c +1 -1
  13. data/crates/backtrace-sys2/src/libbacktrace/backtrace-supported.h.in +1 -1
  14. data/crates/backtrace-sys2/src/libbacktrace/backtrace.c +1 -1
  15. data/crates/backtrace-sys2/src/libbacktrace/backtrace.h +12 -12
  16. data/crates/backtrace-sys2/src/libbacktrace/btest.c +24 -8
  17. data/crates/backtrace-sys2/src/libbacktrace/config/libtool.m4 +162 -53
  18. data/crates/backtrace-sys2/src/libbacktrace/config.h.in +3 -0
  19. data/crates/backtrace-sys2/src/libbacktrace/configure +255 -66
  20. data/crates/backtrace-sys2/src/libbacktrace/configure.ac +27 -8
  21. data/crates/backtrace-sys2/src/libbacktrace/dwarf.c +37 -30
  22. data/crates/backtrace-sys2/src/libbacktrace/edtest.c +2 -2
  23. data/crates/backtrace-sys2/src/libbacktrace/edtest2.c +1 -1
  24. data/crates/backtrace-sys2/src/libbacktrace/elf.c +98 -76
  25. data/crates/backtrace-sys2/src/libbacktrace/fileline.c +1 -1
  26. data/crates/backtrace-sys2/src/libbacktrace/install-debuginfo-for-buildid.sh.in +2 -2
  27. data/crates/backtrace-sys2/src/libbacktrace/instrumented_alloc.c +1 -1
  28. data/crates/backtrace-sys2/src/libbacktrace/internal.h +41 -2
  29. data/crates/backtrace-sys2/src/libbacktrace/macho.c +25 -19
  30. data/crates/backtrace-sys2/src/libbacktrace/mmap.c +1 -1
  31. data/crates/backtrace-sys2/src/libbacktrace/mmapio.c +1 -1
  32. data/crates/backtrace-sys2/src/libbacktrace/mtest.c +4 -4
  33. data/crates/backtrace-sys2/src/libbacktrace/nounwind.c +1 -1
  34. data/crates/backtrace-sys2/src/libbacktrace/pecoff.c +192 -26
  35. data/crates/backtrace-sys2/src/libbacktrace/posix.c +1 -1
  36. data/crates/backtrace-sys2/src/libbacktrace/print.c +41 -16
  37. data/crates/backtrace-sys2/src/libbacktrace/read.c +1 -1
  38. data/crates/backtrace-sys2/src/libbacktrace/simple.c +1 -1
  39. data/crates/backtrace-sys2/src/libbacktrace/sort.c +1 -1
  40. data/crates/backtrace-sys2/src/libbacktrace/state.c +1 -1
  41. data/crates/backtrace-sys2/src/libbacktrace/stest.c +1 -1
  42. data/crates/backtrace-sys2/src/libbacktrace/test_format.c +1 -1
  43. data/crates/backtrace-sys2/src/libbacktrace/testlib.c +1 -1
  44. data/crates/backtrace-sys2/src/libbacktrace/testlib.h +1 -1
  45. data/crates/backtrace-sys2/src/libbacktrace/ttest.c +1 -1
  46. data/crates/backtrace-sys2/src/libbacktrace/unittest.c +1 -1
  47. data/crates/backtrace-sys2/src/libbacktrace/unknown.c +1 -1
  48. data/crates/backtrace-sys2/src/libbacktrace/xcoff.c +43 -32
  49. data/crates/backtrace-sys2/src/libbacktrace/xztest.c +2 -2
  50. data/crates/backtrace-sys2/src/libbacktrace/zstdtest.c +1 -1
  51. data/crates/backtrace-sys2/src/libbacktrace/ztest.c +1 -1
  52. data/ext/pf2/Cargo.toml +1 -1
  53. data/ext/pf2/src/lib.rs +1 -0
  54. data/ext/pf2/src/profile.rs +7 -3
  55. data/ext/pf2/src/profile_serializer.rs +6 -13
  56. data/ext/pf2/src/ringbuffer.rs +1 -3
  57. data/ext/pf2/src/ruby_init.rs +1 -4
  58. data/ext/pf2/src/sample.rs +1 -0
  59. data/ext/pf2/src/serialization/profile.rs +47 -0
  60. data/ext/pf2/src/serialization/serializer.rs +325 -0
  61. data/ext/pf2/src/serialization.rs +2 -0
  62. data/ext/pf2/src/session/configuration.rs +2 -1
  63. data/ext/pf2/src/session/new_thread_watcher.rs +1 -1
  64. data/ext/pf2/src/session/ruby_object.rs +1 -5
  65. data/ext/pf2/src/session.rs +20 -19
  66. data/ext/pf2/src/signal_scheduler.rs +12 -7
  67. data/ext/pf2/src/timer_thread_scheduler.rs +11 -3
  68. data/lib/pf2/cli.rb +3 -1
  69. data/lib/pf2/reporter/firefox_profiler.rb +397 -0
  70. data/lib/pf2/reporter/stack_weaver.rb +81 -0
  71. data/lib/pf2/reporter.rb +3 -392
  72. data/lib/pf2/serve.rb +3 -1
  73. data/lib/pf2/session.rb +2 -0
  74. data/lib/pf2/version.rb +3 -1
  75. data/lib/pf2.rb +4 -1
  76. data/rustfmt.toml +1 -0
  77. metadata +13 -12
  78. data/crates/backtrace-sys2/src/libbacktrace/libtool.m4 +0 -7436
  79. data/crates/backtrace-sys2/src/libbacktrace/ltoptions.m4 +0 -369
  80. data/crates/backtrace-sys2/src/libbacktrace/ltsugar.m4 +0 -123
  81. data/crates/backtrace-sys2/src/libbacktrace/ltversion.m4 +0 -23
  82. data/crates/backtrace-sys2/src/libbacktrace/lt~obsolete.m4 +0 -98
data/lib/pf2/reporter.rb CHANGED
@@ -1,393 +1,4 @@
1
- require 'json'
1
+ # frozen_string_literal: true
2
2
 
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
393
- end
3
+ require_relative './reporter/stack_weaver'
4
+ require_relative './reporter/firefox_profiler'
data/lib/pf2/serve.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'json'
2
4
  require 'logger'
3
5
  require 'uri'
@@ -28,7 +30,7 @@ module Pf2
28
30
  profile = JSON.parse(profile, symbolize_names: true, max_nesting: false)
29
31
  res.header['Content-Type'] = 'application/json'
30
32
  res.header['Access-Control-Allow-Origin'] = '*'
31
- res.body = JSON.generate(Pf2::Reporter.new((profile)).emit)
33
+ res.body = JSON.generate(Pf2::Reporter::FirefoxProfiler.new((profile)).emit)
32
34
  Pf2.start
33
35
  end
34
36
 
data/lib/pf2/session.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Pf2
2
4
  class Session
3
5
  attr_reader :configuration
data/lib/pf2/version.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Pf2
2
- VERSION = '0.5.2'
4
+ VERSION = '0.7.0'
3
5
  end
data/lib/pf2.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'pf2/pf2'
2
4
  require_relative 'pf2/session'
3
5
  require_relative 'pf2/version'
@@ -18,7 +20,8 @@ module Pf2
18
20
  raise ArgumentError, "block required" unless block_given?
19
21
  start(threads: Thread.list)
20
22
  yield
21
- stop
23
+ result = stop
22
24
  @@session = nil # let GC clean up the session
25
+ result
23
26
  end
24
27
  end
data/rustfmt.toml ADDED
@@ -0,0 +1 @@
1
+ use_small_heuristics = "Max"
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.2
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daisuke Aritomo
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2024-07-12 00:00:00.000000000 Z
10
+ date: 2025-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: rake-compiler
@@ -27,16 +27,16 @@ dependencies:
27
27
  name: rb_sys
28
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
- - - "~>"
30
+ - - '='
31
31
  - !ruby/object:Gem::Version
32
- version: 0.9.63
32
+ version: 0.9.105
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
- - - "~>"
37
+ - - '='
38
38
  - !ruby/object:Gem::Version
39
- version: 0.9.63
39
+ version: 0.9.105
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: webrick
42
42
  requirement: !ruby/object:Gem::Requirement
@@ -126,12 +126,7 @@ files:
126
126
  - crates/backtrace-sys2/src/libbacktrace/install-sh
127
127
  - crates/backtrace-sys2/src/libbacktrace/instrumented_alloc.c
128
128
  - crates/backtrace-sys2/src/libbacktrace/internal.h
129
- - crates/backtrace-sys2/src/libbacktrace/libtool.m4
130
129
  - crates/backtrace-sys2/src/libbacktrace/ltmain.sh
131
- - crates/backtrace-sys2/src/libbacktrace/ltoptions.m4
132
- - crates/backtrace-sys2/src/libbacktrace/ltsugar.m4
133
- - crates/backtrace-sys2/src/libbacktrace/ltversion.m4
134
- - crates/backtrace-sys2/src/libbacktrace/lt~obsolete.m4
135
130
  - crates/backtrace-sys2/src/libbacktrace/macho.c
136
131
  - crates/backtrace-sys2/src/libbacktrace/missing
137
132
  - crates/backtrace-sys2/src/libbacktrace/mmap.c
@@ -171,6 +166,9 @@ files:
171
166
  - ext/pf2/src/ruby_internal_apis.rs
172
167
  - ext/pf2/src/sample.rs
173
168
  - ext/pf2/src/scheduler.rs
169
+ - ext/pf2/src/serialization.rs
170
+ - ext/pf2/src/serialization/profile.rs
171
+ - ext/pf2/src/serialization/serializer.rs
174
172
  - ext/pf2/src/session.rs
175
173
  - ext/pf2/src/session/configuration.rs
176
174
  - ext/pf2/src/session/new_thread_watcher.rs
@@ -183,9 +181,12 @@ files:
183
181
  - lib/pf2.rb
184
182
  - lib/pf2/cli.rb
185
183
  - lib/pf2/reporter.rb
184
+ - lib/pf2/reporter/firefox_profiler.rb
185
+ - lib/pf2/reporter/stack_weaver.rb
186
186
  - lib/pf2/serve.rb
187
187
  - lib/pf2/session.rb
188
188
  - lib/pf2/version.rb
189
+ - rustfmt.toml
189
190
  homepage: https://github.com/osyoyu/pf2
190
191
  licenses:
191
192
  - MIT
@@ -208,7 +209,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
208
209
  - !ruby/object:Gem::Version
209
210
  version: '0'
210
211
  requirements: []
211
- rubygems_version: 3.6.0.dev
212
+ rubygems_version: 3.6.2
212
213
  specification_version: 4
213
214
  summary: Yet another Ruby profiler
214
215
  test_files: []