pf2 0.7.1 → 0.9.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.
Files changed (105) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +19 -0
  3. data/README.md +11 -0
  4. data/Rakefile +9 -2
  5. data/doc/development.md +11 -0
  6. data/examples/mandelbrot.rb +69 -0
  7. data/examples/mandelbrot_ractor.rb +77 -0
  8. data/ext/pf2/build.rs +7 -0
  9. data/ext/pf2/src/ruby_c_api_helper.c +6 -0
  10. data/ext/pf2/src/serialization/profile.rs +1 -0
  11. data/ext/pf2/src/serialization/serializer.rs +4 -0
  12. data/ext/pf2/src/signal_scheduler.rs +1 -1
  13. data/ext/pf2/src/util.rs +2 -1
  14. data/ext/pf2c/backtrace_state.c +10 -0
  15. data/ext/pf2c/backtrace_state.h +10 -0
  16. data/ext/pf2c/configuration.c +90 -0
  17. data/ext/pf2c/configuration.h +23 -0
  18. data/ext/pf2c/extconf.rb +21 -0
  19. data/ext/pf2c/pf2.c +17 -0
  20. data/ext/pf2c/pf2.h +8 -0
  21. data/ext/pf2c/ringbuffer.c +74 -0
  22. data/ext/pf2c/ringbuffer.h +24 -0
  23. data/ext/pf2c/sample.c +70 -0
  24. data/ext/pf2c/sample.h +22 -0
  25. data/ext/pf2c/serializer.c +377 -0
  26. data/ext/pf2c/serializer.h +58 -0
  27. data/ext/pf2c/session.c +344 -0
  28. data/ext/pf2c/session.h +51 -0
  29. data/lib/pf2/cli.rb +33 -2
  30. data/lib/pf2/reporter/annotate.rb +101 -0
  31. data/lib/pf2/reporter/firefox_profiler.rb +1 -1
  32. data/lib/pf2/reporter/firefox_profiler_ser2.rb +308 -0
  33. data/lib/pf2/reporter.rb +2 -0
  34. data/lib/pf2/version.rb +1 -1
  35. data/vendor/libbacktrace/.gitignore +5 -0
  36. data/vendor/libbacktrace/Isaac.Newton-Opticks.txt +9286 -0
  37. data/vendor/libbacktrace/LICENSE +29 -0
  38. data/vendor/libbacktrace/Makefile.am +708 -0
  39. data/vendor/libbacktrace/Makefile.in +2820 -0
  40. data/vendor/libbacktrace/README.md +46 -0
  41. data/vendor/libbacktrace/aclocal.m4 +864 -0
  42. data/vendor/libbacktrace/alloc.c +167 -0
  43. data/vendor/libbacktrace/allocfail.c +136 -0
  44. data/vendor/libbacktrace/allocfail.sh +104 -0
  45. data/vendor/libbacktrace/atomic.c +113 -0
  46. data/vendor/libbacktrace/backtrace-supported.h.in +66 -0
  47. data/vendor/libbacktrace/backtrace.c +129 -0
  48. data/vendor/libbacktrace/backtrace.h +189 -0
  49. data/vendor/libbacktrace/btest.c +517 -0
  50. data/vendor/libbacktrace/compile +348 -0
  51. data/vendor/libbacktrace/config/enable.m4 +38 -0
  52. data/vendor/libbacktrace/config/lead-dot.m4 +31 -0
  53. data/vendor/libbacktrace/config/libtool.m4 +7545 -0
  54. data/vendor/libbacktrace/config/ltoptions.m4 +369 -0
  55. data/vendor/libbacktrace/config/ltsugar.m4 +123 -0
  56. data/vendor/libbacktrace/config/ltversion.m4 +23 -0
  57. data/vendor/libbacktrace/config/lt~obsolete.m4 +98 -0
  58. data/vendor/libbacktrace/config/multi.m4 +68 -0
  59. data/vendor/libbacktrace/config/override.m4 +117 -0
  60. data/vendor/libbacktrace/config/unwind_ipinfo.m4 +37 -0
  61. data/vendor/libbacktrace/config/warnings.m4 +227 -0
  62. data/vendor/libbacktrace/config.guess +1700 -0
  63. data/vendor/libbacktrace/config.h.in +185 -0
  64. data/vendor/libbacktrace/config.sub +1885 -0
  65. data/vendor/libbacktrace/configure +15952 -0
  66. data/vendor/libbacktrace/configure.ac +642 -0
  67. data/vendor/libbacktrace/dwarf.c +4593 -0
  68. data/vendor/libbacktrace/edtest.c +120 -0
  69. data/vendor/libbacktrace/edtest2.c +43 -0
  70. data/vendor/libbacktrace/elf.c +7471 -0
  71. data/vendor/libbacktrace/fileline.c +407 -0
  72. data/vendor/libbacktrace/filenames.h +52 -0
  73. data/vendor/libbacktrace/filetype.awk +13 -0
  74. data/vendor/libbacktrace/install-debuginfo-for-buildid.sh.in +65 -0
  75. data/vendor/libbacktrace/install-sh +501 -0
  76. data/vendor/libbacktrace/instrumented_alloc.c +114 -0
  77. data/vendor/libbacktrace/internal.h +428 -0
  78. data/vendor/libbacktrace/ltmain.sh +8636 -0
  79. data/vendor/libbacktrace/macho.c +1361 -0
  80. data/vendor/libbacktrace/missing +215 -0
  81. data/vendor/libbacktrace/mmap.c +331 -0
  82. data/vendor/libbacktrace/mmapio.c +110 -0
  83. data/vendor/libbacktrace/move-if-change +83 -0
  84. data/vendor/libbacktrace/mtest.c +410 -0
  85. data/vendor/libbacktrace/nounwind.c +66 -0
  86. data/vendor/libbacktrace/pecoff.c +1123 -0
  87. data/vendor/libbacktrace/posix.c +104 -0
  88. data/vendor/libbacktrace/print.c +117 -0
  89. data/vendor/libbacktrace/read.c +110 -0
  90. data/vendor/libbacktrace/simple.c +108 -0
  91. data/vendor/libbacktrace/sort.c +108 -0
  92. data/vendor/libbacktrace/state.c +72 -0
  93. data/vendor/libbacktrace/stest.c +137 -0
  94. data/vendor/libbacktrace/test-driver +148 -0
  95. data/vendor/libbacktrace/test_format.c +55 -0
  96. data/vendor/libbacktrace/testlib.c +234 -0
  97. data/vendor/libbacktrace/testlib.h +110 -0
  98. data/vendor/libbacktrace/ttest.c +161 -0
  99. data/vendor/libbacktrace/unittest.c +92 -0
  100. data/vendor/libbacktrace/unknown.c +65 -0
  101. data/vendor/libbacktrace/xcoff.c +1617 -0
  102. data/vendor/libbacktrace/xztest.c +508 -0
  103. data/vendor/libbacktrace/zstdtest.c +523 -0
  104. data/vendor/libbacktrace/ztest.c +541 -0
  105. metadata +122 -3
@@ -0,0 +1,308 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Pf2
6
+ module Reporter
7
+ # Generates Firefox Profiler's "processed profile format"
8
+ # https://github.com/firefox-devtools/profiler/blob/main/docs-developer/processed-profile-format.md
9
+ class FirefoxProfilerSer2
10
+ def initialize(profile)
11
+ @profile = profile
12
+ end
13
+
14
+ def inspect
15
+ "#<#{self.class.name} (#{@profile[:samples].length} samples)>"
16
+ end
17
+
18
+ def emit
19
+ grouped_threads = @profile[:samples].group_by {|s| s[:ruby_thread_id] }
20
+ thread_reports = grouped_threads.map do |thread_id, samples|
21
+ ThreadReport.new(@profile, thread_id, samples).emit
22
+ end
23
+
24
+ report = {
25
+ meta: {
26
+ interval: 10, # ms; TODO: replace with actual interval
27
+ start_time: 0,
28
+ process_type: 0,
29
+ product: 'ruby',
30
+ stackwalk: 0,
31
+ version: 28,
32
+ preprocessed_profile_version: 47,
33
+ symbolicated: true,
34
+ categories: [
35
+ {
36
+ name: "Logs",
37
+ color: "grey",
38
+ subcategories: ["Unused"],
39
+ },
40
+ {
41
+ name: "Ruby",
42
+ color: "red",
43
+ subcategories: ["Code"],
44
+ },
45
+ {
46
+ name: "Native",
47
+ color: "blue",
48
+ subcategories: ["Code"],
49
+ },
50
+ {
51
+ name: "Native",
52
+ color: "lightblue",
53
+ subcategories: ["Code"],
54
+ },
55
+ ],
56
+ marker_schema: [],
57
+ },
58
+ libs: [],
59
+ counters: [],
60
+ threads: thread_reports,
61
+ }
62
+ FirefoxProfiler.deep_camelize_keys(report)
63
+ end
64
+
65
+ class ThreadReport
66
+ def initialize(profile, thread_id, samples)
67
+ @profile = profile
68
+ @thread_id = thread_id
69
+ @samples = samples
70
+
71
+ # Global state
72
+ @stack_tree = { :stack_id => nil }
73
+ @reverse_stack_tree = []
74
+ @string_table = { nil => 0 }
75
+ end
76
+
77
+ def inspect
78
+ "#<#{self.class.name} (#{@samples.length} samples)>"
79
+ end
80
+
81
+ def emit
82
+ # TODO: weave?
83
+ # @thread[:stack_tree] = x
84
+
85
+ # Build func table from profile[:functions]
86
+ func_table = build_func_table
87
+ # Build frame table from profile[:locations]
88
+ frame_table = build_frame_table
89
+ # Build stack table from profile[:samples][][:stack]
90
+ stack_table = build_stack_table(func_table, frame_table)
91
+ # Build samples from profile[:samples]
92
+ samples = build_samples
93
+
94
+ string_table = build_string_table
95
+
96
+ {
97
+ process_type: 'default',
98
+ process_name: 'ruby',
99
+ process_startup_time: 0,
100
+ process_shutdown_time: nil,
101
+ register_time: 0,
102
+ unregister_time: nil,
103
+ paused_ranges: [],
104
+ name: "Thread (tid: #{@thread_id})",
105
+ is_main_thread: true,
106
+ is_js_tracer: true,
107
+ # FIXME: We can fill the correct PID only after we correctly fill is_main_thread
108
+ # (only one thread could be marked as is_main_thread in a single process)
109
+ pid: @thread_id,
110
+ tid: @thread_id,
111
+ samples: samples,
112
+ markers: markers,
113
+ stack_table: stack_table,
114
+ frame_table: frame_table,
115
+ string_array: string_table,
116
+ func_table: func_table,
117
+ resource_table: {
118
+ lib: [],
119
+ name: [],
120
+ host: [],
121
+ type: [],
122
+ length: 0,
123
+ },
124
+ native_symbols: [],
125
+ }
126
+ end
127
+
128
+ def build_samples
129
+ ret = {
130
+ event_delay: [],
131
+ stack: [],
132
+ time: [],
133
+ duration: [],
134
+ # weight: nil,
135
+ # weight_type: 'samples',
136
+ }
137
+
138
+ @samples.each do |sample|
139
+ stack = sample[:stack].reverse
140
+ stack_id = @stack_tree.dig(*stack, :stack_id)
141
+
142
+ ret[:stack] << stack_id
143
+ ret[:time] << sample[:elapsed_ns] / 1_000_000 # ns -> ms
144
+ ret[:duration] << 100
145
+ ret[:event_delay] << 0
146
+ end
147
+
148
+ ret[:length] = ret[:stack].length
149
+ ret
150
+ end
151
+
152
+ def build_frame_table
153
+ ret = {
154
+ address: [],
155
+ category: [],
156
+ subcategory: [],
157
+ func: [],
158
+ inner_window_id: [],
159
+ implementation: [],
160
+ line: [],
161
+ column: [],
162
+ optimizations: [],
163
+ inline_depth: [],
164
+ native_symbol: [],
165
+ }
166
+
167
+ @profile[:locations].each.with_index do |location, i|
168
+ ret[:address] << location[:address]
169
+ ret[:category] << 1
170
+ ret[:subcategory] << 1
171
+ ret[:func] << location[:function_index]
172
+ ret[:inner_window_id] << nil
173
+ ret[:implementation] << nil
174
+ ret[:line] << location[:lineno]
175
+ ret[:column] << nil
176
+ ret[:optimizations] << nil
177
+ ret[:inline_depth] << 0
178
+ ret[:native_symbol] << nil
179
+ end
180
+
181
+ ret[:length] = ret[:address].length
182
+ ret
183
+ end
184
+
185
+ def build_func_table
186
+ ret = {
187
+ name: [],
188
+ is_js: [],
189
+ relevant_for_js: [],
190
+ resource: [],
191
+ file_name: [],
192
+ line_number: [],
193
+ column_number: [],
194
+ }
195
+
196
+ @profile[:functions].each do |function|
197
+ is_ruby = (function[:implementation] == :ruby)
198
+
199
+ ret[:name] << string_id(function[:name] || "<unknown>")
200
+ ret[:is_js] << is_ruby
201
+ ret[:relevant_for_js] << false
202
+ ret[:resource] << -1
203
+ ret[:file_name] << string_id(function[:filename])
204
+ ret[:line_number] << function[:start_lineno]
205
+ ret[:column_number] << nil
206
+ end
207
+
208
+ ret[:length] = ret[:name].length
209
+ ret
210
+ end
211
+
212
+ def build_stack_table(func_table, frame_table)
213
+ ret = {
214
+ frame: [],
215
+ category: [],
216
+ subcategory: [],
217
+ prefix: [],
218
+ }
219
+
220
+ @profile[:samples].each do |sample|
221
+ # Stack (Array of location indices) recorded in sample, reversed
222
+ # example: [1, 2, 9] (1 is the root)
223
+ stack = sample[:stack].reverse
224
+
225
+ # Build the stack_table Array which Firefox Profiler requires.
226
+ # At the same time, build the stack tree for efficient traversal.
227
+
228
+ current_node = @stack_tree # the stack tree root
229
+ stack.each do |location_index|
230
+ if current_node[location_index].nil?
231
+ # The tree node is unknown. Create it.
232
+ new_stack_id = ret[:frame].length # The position of the new stack in the stack_table array
233
+ current_node[location_index] = { stack_id: new_stack_id }
234
+
235
+ # Retrieve the corresponding location and function from the profile
236
+ location = @profile[:locations][location_index]
237
+ function = @profile[:functions][location[:function_index]]
238
+
239
+ # Register the stack in the stack_table Array
240
+ ret[:frame] << location_index
241
+ ret[:category] << (function[:implementation] == :ruby ? 2 : 1)
242
+ ret[:subcategory] << nil
243
+ ret[:prefix] << current_node[:stack_id] # the parent's position in the stack_table array
244
+ end
245
+
246
+ # Update the current node to the child node
247
+ current_node = current_node[location_index]
248
+ end
249
+ end
250
+
251
+ ret[:length] = ret[:frame].length
252
+ ret
253
+ end
254
+
255
+ def build_string_table
256
+ @string_table.sort_by {|_, v| v}.map {|s| s[0] }
257
+ end
258
+
259
+ # @string_table is a hash of { string => index }
260
+ def string_id(str)
261
+ id = @string_table[str]
262
+ return id if id
263
+ @string_table[str] = @string_table.length
264
+ end
265
+
266
+ def markers
267
+ {
268
+ data: [],
269
+ name: [],
270
+ time: [],
271
+ start_time: [],
272
+ end_time: [],
273
+ phase: [],
274
+ category: [],
275
+ length: 0
276
+ }
277
+ end
278
+ end
279
+
280
+ # Util functions
281
+ class << self
282
+ def snake_to_camel(s)
283
+ return "isJS" if s == "is_js"
284
+ return "relevantForJS" if s == "relevant_for_js"
285
+ return "innerWindowID" if s == "inner_window_id"
286
+ s.split('_').inject([]) {|buffer, p| buffer.push(buffer.size == 0 ? p : p.capitalize) }.join
287
+ end
288
+
289
+ def deep_transform_keys(value, &block)
290
+ case value
291
+ when Array
292
+ value.map {|v| deep_transform_keys(v, &block) }
293
+ when Hash
294
+ Hash[value.map {|k, v| [yield(k), deep_transform_keys(v, &block)] }]
295
+ else
296
+ value
297
+ end
298
+ end
299
+
300
+ def deep_camelize_keys(value)
301
+ deep_transform_keys(value) do |key|
302
+ snake_to_camel(key.to_s).to_sym
303
+ end
304
+ end
305
+ end
306
+ end
307
+ end
308
+ end
data/lib/pf2/reporter.rb CHANGED
@@ -1,4 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative './reporter/annotate'
3
4
  require_relative './reporter/stack_weaver'
4
5
  require_relative './reporter/firefox_profiler'
6
+ require_relative './reporter/firefox_profiler_ser2'
data/lib/pf2/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pf2
4
- VERSION = '0.7.1'
4
+ VERSION = '0.9.0'
5
5
  end
@@ -0,0 +1,5 @@
1
+ *~
2
+ *.o
3
+ *.lo
4
+ *.a
5
+ *.la