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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -0
- data/README.md +11 -0
- data/Rakefile +9 -2
- data/doc/development.md +11 -0
- data/examples/mandelbrot.rb +69 -0
- data/examples/mandelbrot_ractor.rb +77 -0
- data/ext/pf2/build.rs +7 -0
- data/ext/pf2/src/ruby_c_api_helper.c +6 -0
- data/ext/pf2/src/serialization/profile.rs +1 -0
- data/ext/pf2/src/serialization/serializer.rs +4 -0
- data/ext/pf2/src/signal_scheduler.rs +1 -1
- data/ext/pf2/src/util.rs +2 -1
- data/ext/pf2c/backtrace_state.c +10 -0
- data/ext/pf2c/backtrace_state.h +10 -0
- data/ext/pf2c/configuration.c +90 -0
- data/ext/pf2c/configuration.h +23 -0
- data/ext/pf2c/extconf.rb +21 -0
- data/ext/pf2c/pf2.c +17 -0
- data/ext/pf2c/pf2.h +8 -0
- data/ext/pf2c/ringbuffer.c +74 -0
- data/ext/pf2c/ringbuffer.h +24 -0
- data/ext/pf2c/sample.c +70 -0
- data/ext/pf2c/sample.h +22 -0
- data/ext/pf2c/serializer.c +377 -0
- data/ext/pf2c/serializer.h +58 -0
- data/ext/pf2c/session.c +344 -0
- data/ext/pf2c/session.h +51 -0
- data/lib/pf2/cli.rb +33 -2
- data/lib/pf2/reporter/annotate.rb +101 -0
- data/lib/pf2/reporter/firefox_profiler.rb +1 -1
- data/lib/pf2/reporter/firefox_profiler_ser2.rb +308 -0
- data/lib/pf2/reporter.rb +2 -0
- data/lib/pf2/version.rb +1 -1
- data/vendor/libbacktrace/.gitignore +5 -0
- data/vendor/libbacktrace/Isaac.Newton-Opticks.txt +9286 -0
- data/vendor/libbacktrace/LICENSE +29 -0
- data/vendor/libbacktrace/Makefile.am +708 -0
- data/vendor/libbacktrace/Makefile.in +2820 -0
- data/vendor/libbacktrace/README.md +46 -0
- data/vendor/libbacktrace/aclocal.m4 +864 -0
- data/vendor/libbacktrace/alloc.c +167 -0
- data/vendor/libbacktrace/allocfail.c +136 -0
- data/vendor/libbacktrace/allocfail.sh +104 -0
- data/vendor/libbacktrace/atomic.c +113 -0
- data/vendor/libbacktrace/backtrace-supported.h.in +66 -0
- data/vendor/libbacktrace/backtrace.c +129 -0
- data/vendor/libbacktrace/backtrace.h +189 -0
- data/vendor/libbacktrace/btest.c +517 -0
- data/vendor/libbacktrace/compile +348 -0
- data/vendor/libbacktrace/config/enable.m4 +38 -0
- data/vendor/libbacktrace/config/lead-dot.m4 +31 -0
- data/vendor/libbacktrace/config/libtool.m4 +7545 -0
- data/vendor/libbacktrace/config/ltoptions.m4 +369 -0
- data/vendor/libbacktrace/config/ltsugar.m4 +123 -0
- data/vendor/libbacktrace/config/ltversion.m4 +23 -0
- data/vendor/libbacktrace/config/lt~obsolete.m4 +98 -0
- data/vendor/libbacktrace/config/multi.m4 +68 -0
- data/vendor/libbacktrace/config/override.m4 +117 -0
- data/vendor/libbacktrace/config/unwind_ipinfo.m4 +37 -0
- data/vendor/libbacktrace/config/warnings.m4 +227 -0
- data/vendor/libbacktrace/config.guess +1700 -0
- data/vendor/libbacktrace/config.h.in +185 -0
- data/vendor/libbacktrace/config.sub +1885 -0
- data/vendor/libbacktrace/configure +15952 -0
- data/vendor/libbacktrace/configure.ac +642 -0
- data/vendor/libbacktrace/dwarf.c +4593 -0
- data/vendor/libbacktrace/edtest.c +120 -0
- data/vendor/libbacktrace/edtest2.c +43 -0
- data/vendor/libbacktrace/elf.c +7471 -0
- data/vendor/libbacktrace/fileline.c +407 -0
- data/vendor/libbacktrace/filenames.h +52 -0
- data/vendor/libbacktrace/filetype.awk +13 -0
- data/vendor/libbacktrace/install-debuginfo-for-buildid.sh.in +65 -0
- data/vendor/libbacktrace/install-sh +501 -0
- data/vendor/libbacktrace/instrumented_alloc.c +114 -0
- data/vendor/libbacktrace/internal.h +428 -0
- data/vendor/libbacktrace/ltmain.sh +8636 -0
- data/vendor/libbacktrace/macho.c +1361 -0
- data/vendor/libbacktrace/missing +215 -0
- data/vendor/libbacktrace/mmap.c +331 -0
- data/vendor/libbacktrace/mmapio.c +110 -0
- data/vendor/libbacktrace/move-if-change +83 -0
- data/vendor/libbacktrace/mtest.c +410 -0
- data/vendor/libbacktrace/nounwind.c +66 -0
- data/vendor/libbacktrace/pecoff.c +1123 -0
- data/vendor/libbacktrace/posix.c +104 -0
- data/vendor/libbacktrace/print.c +117 -0
- data/vendor/libbacktrace/read.c +110 -0
- data/vendor/libbacktrace/simple.c +108 -0
- data/vendor/libbacktrace/sort.c +108 -0
- data/vendor/libbacktrace/state.c +72 -0
- data/vendor/libbacktrace/stest.c +137 -0
- data/vendor/libbacktrace/test-driver +148 -0
- data/vendor/libbacktrace/test_format.c +55 -0
- data/vendor/libbacktrace/testlib.c +234 -0
- data/vendor/libbacktrace/testlib.h +110 -0
- data/vendor/libbacktrace/ttest.c +161 -0
- data/vendor/libbacktrace/unittest.c +92 -0
- data/vendor/libbacktrace/unknown.c +65 -0
- data/vendor/libbacktrace/xcoff.c +1617 -0
- data/vendor/libbacktrace/xztest.c +508 -0
- data/vendor/libbacktrace/zstdtest.c +523 -0
- data/vendor/libbacktrace/ztest.c +541 -0
- 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
data/lib/pf2/version.rb
CHANGED