vernier 1.5.0 → 1.7.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/README.md +43 -14
- data/exe/vernier +14 -0
- data/ext/vernier/vernier.cc +10 -8
- data/lib/vernier/autorun.rb +8 -1
- data/lib/vernier/collector.rb +3 -0
- data/lib/vernier/middleware.rb +1 -1
- data/lib/vernier/output/file_listing.rb +53 -14
- data/lib/vernier/output/firefox.rb +22 -7
- data/lib/vernier/stack_table_helpers.rb +12 -1
- data/lib/vernier/version.rb +1 -1
- metadata +3 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2419244a8929351d5c81f18b90766f20d7d9e29d4909c876deb06d2a9ad39f2d
|
4
|
+
data.tar.gz: 94ead7ad3797a5cca2311cebc0ba0a57de369b7a6d09a07cbe4b6fad1f8826ac
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 25551c8b2d537ca00de410e0bbd2173a2cd0452462f8c8ed5c27fd9b6592fa60f7ff1eceb01b429eba69b1989287a7244acef7448b4335b3e2ef87e731af59df
|
7
|
+
data.tar.gz: e510284f293ae3efd17e82c8e643b800ccbce5d84ffca04ef35fb16b5edf796672080465384cb40ea59eeacc7cc3cea57cb25e73805183956359a4e58e464f8c
|
data/README.md
CHANGED
@@ -4,9 +4,11 @@ Next-generation Ruby 3.2.1+ sampling profiler. Tracks multiple threads, GVL acti
|
|
4
4
|
|
5
5
|
<img width="500" alt="Screenshot 2024-02-29 at 22 47 43" src="https://github.com/jhawthorn/vernier/assets/131752/aa995a41-d74f-405f-8ada-2522dd72c2c8">
|
6
6
|
|
7
|
-
## Examples
|
7
|
+
## Demos and Examples
|
8
8
|
|
9
|
-
[Livestreamed demo: Pairin' with Aaron (YouTube)](https://www.youtube.com/watch?v=9nvX3OHykGQ#t=
|
9
|
+
[Livestreamed demo: Pairin' with Aaron (YouTube)](https://www.youtube.com/watch?v=9nvX3OHykGQ#t=27m40)
|
10
|
+
|
11
|
+
[Overview at RubyKaigi 2024 (YouTube)](https://youtu.be/QSjN-H4hGsM)
|
10
12
|
|
11
13
|
Sidekiq jobs from Mastodon (time, threaded)
|
12
14
|
: https://share.firefox.dev/44jZRf3
|
@@ -22,7 +24,7 @@ Rails benchmark - lobste.rs (time)
|
|
22
24
|
|
23
25
|
## Installation
|
24
26
|
|
25
|
-
Vernier requires Ruby version 3.2.1 or greater.
|
27
|
+
Vernier requires Ruby version 3.2.1 or greater and a Unix-like operating system.
|
26
28
|
|
27
29
|
```ruby
|
28
30
|
gem "vernier", "~> 1.0"
|
@@ -30,7 +32,7 @@ gem "vernier", "~> 1.0"
|
|
30
32
|
|
31
33
|
## Usage
|
32
34
|
|
33
|
-
The output can be viewed in the web app at https://vernier.prof, locally using the [`profile-viewer` gem](https://github.com/tenderlove/profiler/tree/ruby) (both lightly customized versions of the firefox profiler frontend
|
35
|
+
The output can be viewed in the web app at https://vernier.prof, locally using the [`profile-viewer` gem](https://github.com/tenderlove/profiler/tree/ruby) (both lightly customized versions of the firefox profiler frontend which profiles are compatible with), or by using the `vernier view` command in the CLI.
|
34
36
|
|
35
37
|
- **Flame Graph**: Shows proportionally how much time is spent within particular stack frames. Frames are grouped together, which means that x-axis / left-to-right order is not meaningful.
|
36
38
|
- **Stack Chart**: Shows the stack at each sample with the x-axis representing time and can be read left-to-right.
|
@@ -41,14 +43,14 @@ The output can be viewed in the web app at https://vernier.prof, locally using t
|
|
41
43
|
|
42
44
|
The easiest way to record a program or script is via the CLI:
|
43
45
|
|
44
|
-
```
|
46
|
+
```sh
|
45
47
|
$ vernier run -- ruby -e 'sleep 1'
|
46
48
|
starting profiler with interval 500 and allocation interval 0
|
47
49
|
#<Vernier::Result 1.001589 seconds, 1 threads, 1 samples, 1 unique>
|
48
50
|
written to /tmp/profile20240328-82441-gkzffc.vernier.json.gz
|
49
51
|
```
|
50
52
|
|
51
|
-
```
|
53
|
+
```sh
|
52
54
|
$ vernier run --interval 100 --allocation-interval 10 -- ruby -e '10.times { Object.new }'
|
53
55
|
starting profiler with interval 100 and allocation interval 10
|
54
56
|
#<Vernier::Result 0.00067 seconds, 1 threads, 1 samples, 1 unique>
|
@@ -85,13 +87,39 @@ some_other_slow_method
|
|
85
87
|
Vernier.stop_profile
|
86
88
|
```
|
87
89
|
|
90
|
+
#### Rack middleware
|
91
|
+
|
92
|
+
You can also use `Vernier::Middleware` to profile a Rack application:
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
# config.ru
|
96
|
+
|
97
|
+
require "vernier"
|
98
|
+
|
99
|
+
use Vernier::Middleware
|
100
|
+
|
101
|
+
run ->(env) { [200, { "Content-Type" => "text/plain" }, ["Hello, Profiling World!"]] }
|
102
|
+
```
|
103
|
+
|
104
|
+
If you're using Rails, you can add the middleware to your `config/application.rb`:
|
105
|
+
|
106
|
+
```ruby
|
107
|
+
config.middleware.use Vernier::Middleware, permit: ->(env) { env["PATH_INFO"].start_with?("/api") }
|
108
|
+
```
|
109
|
+
|
110
|
+
You can then enable profiling and configure options with query parameters:
|
111
|
+
|
112
|
+
```sh
|
113
|
+
curl http://localhost:3000?vernier=true&vernier_interval=100&vernier_allocation_interval=10
|
114
|
+
```
|
115
|
+
|
88
116
|
### Retained memory
|
89
117
|
|
90
118
|
#### Block of code
|
91
119
|
|
92
120
|
Record a flamegraph of all **retained** allocations from loading `irb`:
|
93
121
|
|
94
|
-
```
|
122
|
+
```sh
|
95
123
|
ruby -r vernier -e 'Vernier.trace_retained(out: "irb_profile.json") { require "irb" }'
|
96
124
|
```
|
97
125
|
|
@@ -100,13 +128,14 @@ ruby -r vernier -e 'Vernier.trace_retained(out: "irb_profile.json") { require "i
|
|
100
128
|
|
101
129
|
### Options
|
102
130
|
|
103
|
-
Option | Description
|
104
|
-
|
105
|
-
`mode` |
|
106
|
-
`out` |
|
107
|
-
`interval` |
|
108
|
-
`allocation_interval` |
|
109
|
-
`gc` |
|
131
|
+
| Option | Middleware Param | Description | Default (Middleware Default) |
|
132
|
+
|-----------------------|-------------------------------|---------------------------------------------------------------|------------------------------|
|
133
|
+
| `mode` | N/A | Sampling mode: `:wall`, `:retained`, or `:custom`. | `:wall` (`:wall`) |
|
134
|
+
| `out` | N/A | File to write the profile to. | N/A (Auto-generated) |
|
135
|
+
| `interval` | `vernier_interval` | Sampling interval (µs). Only in `:wall` mode. | `500` (`200`) |
|
136
|
+
| `allocation_interval` | `vernier_allocation_interval` | Allocation sampling interval. Only in `:wall` mode. | `0`/disabled (`200`) |
|
137
|
+
| `gc` | N/A | Run full GC cycle before profiling. Only in `:retained` mode. | `true` (N/A) |
|
138
|
+
| `metadata` | N/A | Metadata key-value pairs to include in the profile. | `{}` (N/A) |
|
110
139
|
|
111
140
|
## Development
|
112
141
|
|
data/exe/vernier
CHANGED
@@ -7,6 +7,15 @@ require "vernier/version"
|
|
7
7
|
|
8
8
|
module Vernier
|
9
9
|
module CLI
|
10
|
+
class Metadata < Array
|
11
|
+
require 'json'
|
12
|
+
require 'base64'
|
13
|
+
|
14
|
+
def to_s
|
15
|
+
Base64.encode64(to_json)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
10
19
|
def self.run(options)
|
11
20
|
banner = <<-END
|
12
21
|
Usage: vernier run [FLAGS] -- COMMAND
|
@@ -38,6 +47,11 @@ FLAGS:
|
|
38
47
|
o.on('--hooks [HOOKS]', String, "enable instrumentation hooks, currently supported: rails") do |s|
|
39
48
|
options[:hooks] = s
|
40
49
|
end
|
50
|
+
o.on("--metadata KEY=VALUE", String, "Set metadata key-value pairs (can be specified multiple times)") do |kv|
|
51
|
+
key, value = kv.split('=')
|
52
|
+
options[:metadata] ||= Metadata.new
|
53
|
+
options[:metadata] << [key, value]
|
54
|
+
end
|
41
55
|
end
|
42
56
|
end
|
43
57
|
|
data/ext/vernier/vernier.cc
CHANGED
@@ -74,10 +74,12 @@ static const char *gvl_event_name(rb_event_flag_t event) {
|
|
74
74
|
return "no-event";
|
75
75
|
}
|
76
76
|
|
77
|
-
|
78
|
-
struct FrameInfo {
|
77
|
+
struct FuncInfo {
|
79
78
|
static const char *label_cstr(VALUE frame) {
|
80
79
|
VALUE label = rb_profile_frame_full_label(frame);
|
80
|
+
// Currently (2025-03-22, Ruby 3.4.2) this occurs when an iseq method
|
81
|
+
// entry is replaced with a refinement
|
82
|
+
if (NIL_P(label)) return "(nil)";
|
81
83
|
return StringValueCStr(label);
|
82
84
|
}
|
83
85
|
|
@@ -86,7 +88,7 @@ struct FrameInfo {
|
|
86
88
|
if (NIL_P(file))
|
87
89
|
file = rb_profile_frame_path(frame);
|
88
90
|
if (NIL_P(file)) {
|
89
|
-
return "";
|
91
|
+
return "(nil)";
|
90
92
|
} else {
|
91
93
|
return StringValueCStr(file);
|
92
94
|
}
|
@@ -97,7 +99,7 @@ struct FrameInfo {
|
|
97
99
|
return NIL_P(first_lineno) ? 0 : FIX2INT(first_lineno);
|
98
100
|
}
|
99
101
|
|
100
|
-
|
102
|
+
FuncInfo(VALUE frame) :
|
101
103
|
label(label_cstr(frame)),
|
102
104
|
file(file_cstr(frame)),
|
103
105
|
first_lineno(first_lineno_int(frame)) { }
|
@@ -107,7 +109,7 @@ struct FrameInfo {
|
|
107
109
|
int first_lineno;
|
108
110
|
};
|
109
111
|
|
110
|
-
bool operator==(const
|
112
|
+
bool operator==(const FuncInfo& lhs, const FuncInfo& rhs) noexcept {
|
111
113
|
return
|
112
114
|
lhs.label == rhs.label &&
|
113
115
|
lhs.file == rhs.file &&
|
@@ -263,13 +265,13 @@ struct StackTable {
|
|
263
265
|
|
264
266
|
struct FrameWithInfo {
|
265
267
|
Frame frame;
|
266
|
-
|
268
|
+
FuncInfo info;
|
267
269
|
};
|
268
270
|
|
269
271
|
IndexMap<Frame> frame_map;
|
270
272
|
|
271
273
|
IndexMap<VALUE> func_map;
|
272
|
-
std::vector<
|
274
|
+
std::vector<FuncInfo> func_info_list;
|
273
275
|
|
274
276
|
struct StackNode {
|
275
277
|
std::unordered_map<Frame, int> children;
|
@@ -360,7 +362,7 @@ struct StackTable {
|
|
360
362
|
for (int i = func_info_list.size(); i < func_map.size(); i++) {
|
361
363
|
const auto &func = func_map[i];
|
362
364
|
// must not hold a mutex here
|
363
|
-
func_info_list.push_back(
|
365
|
+
func_info_list.push_back(FuncInfo(func));
|
364
366
|
}
|
365
367
|
}
|
366
368
|
|
data/lib/vernier/autorun.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
require "tempfile"
|
2
2
|
require "vernier"
|
3
|
+
require "base64"
|
4
|
+
require "json"
|
3
5
|
|
4
6
|
module Vernier
|
5
7
|
module Autorun
|
@@ -19,10 +21,15 @@ module Vernier
|
|
19
21
|
interval = options.fetch(:interval, 500).to_i
|
20
22
|
allocation_interval = options.fetch(:allocation_interval, 0).to_i
|
21
23
|
hooks = options.fetch(:hooks, "").split(",")
|
24
|
+
metadata = if options[:metadata]
|
25
|
+
JSON.parse(Base64.decode64(@options[:metadata])).to_h { |k, v| [k.to_sym, v] }
|
26
|
+
else
|
27
|
+
{}
|
28
|
+
end
|
22
29
|
|
23
30
|
STDERR.puts("starting profiler with interval #{interval} and allocation interval #{allocation_interval}")
|
24
31
|
|
25
|
-
@collector = Vernier::Collector.new(:wall, interval:, allocation_interval:, hooks:)
|
32
|
+
@collector = Vernier::Collector.new(:wall, interval:, allocation_interval:, hooks:, metadata:)
|
26
33
|
@collector.start
|
27
34
|
end
|
28
35
|
|
data/lib/vernier/collector.rb
CHANGED
@@ -25,6 +25,8 @@ module Vernier
|
|
25
25
|
@hooks.each do |hook|
|
26
26
|
hook.enable
|
27
27
|
end
|
28
|
+
|
29
|
+
@user_metadata = options[:metadata] || {}
|
28
30
|
end
|
29
31
|
|
30
32
|
private def add_hook(hook)
|
@@ -79,6 +81,7 @@ module Vernier
|
|
79
81
|
result.meta[:mode] = @mode
|
80
82
|
result.meta[:out] = @out
|
81
83
|
result.meta[:gc] = @gc
|
84
|
+
result.meta[:user_metadata] = @user_metadata
|
82
85
|
|
83
86
|
result.stack_table = stack_table
|
84
87
|
@thread_names.finish
|
data/lib/vernier/middleware.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative "filename_filter"
|
4
|
+
require "cgi/util"
|
4
5
|
|
5
6
|
module Vernier
|
6
7
|
module Output
|
@@ -23,29 +24,24 @@ module Vernier
|
|
23
24
|
@profile = profile
|
24
25
|
end
|
25
26
|
|
26
|
-
def
|
27
|
-
output = +""
|
28
|
-
|
27
|
+
def samples_by_file
|
29
28
|
thread = @profile.main_thread
|
30
29
|
if Hash === thread
|
31
30
|
# live profile
|
32
31
|
stack_table = @profile._stack_table
|
33
|
-
weights = thread[:weights]
|
34
|
-
samples = thread[:samples]
|
35
32
|
filename_filter = FilenameFilter.new
|
36
33
|
else
|
37
34
|
stack_table = thread.stack_table
|
38
|
-
weights = thread.weights
|
39
|
-
samples = thread.samples
|
40
35
|
filename_filter = ->(x) { x }
|
41
36
|
end
|
42
37
|
|
38
|
+
weights = thread[:weights]
|
39
|
+
samples = thread[:samples]
|
40
|
+
|
43
41
|
self_samples_by_frame = Hash.new do |h, k|
|
44
42
|
h[k] = SamplesByLocation.new
|
45
43
|
end
|
46
44
|
|
47
|
-
total = weights.sum
|
48
|
-
|
49
45
|
samples.zip(weights).each do |stack_idx, weight|
|
50
46
|
# self time
|
51
47
|
top_frame_index = stack_table.stack_frame_idx(stack_idx)
|
@@ -76,6 +72,10 @@ module Vernier
|
|
76
72
|
samples_by_file.transform_keys! do |filename|
|
77
73
|
filename_filter.call(filename)
|
78
74
|
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def output(template: nil)
|
78
|
+
output = +""
|
79
79
|
|
80
80
|
relevant_files = samples_by_file.select do |k, v|
|
81
81
|
next if k.start_with?("gem:")
|
@@ -83,13 +83,23 @@ module Vernier
|
|
83
83
|
next if k.start_with?("<")
|
84
84
|
v.values.map(&:total).sum > total * 0.01
|
85
85
|
end
|
86
|
-
|
86
|
+
|
87
|
+
if template == "html"
|
88
|
+
html_output(output, relevant_files)
|
89
|
+
else
|
90
|
+
relevant_files.keys.sort.each do |filename|
|
91
|
+
output << "="*80 << "\n"
|
92
|
+
output << filename << "\n"
|
93
|
+
output << "-"*80 << "\n"
|
94
|
+
format_file(output, filename, samples_by_file, total: total)
|
95
|
+
end
|
87
96
|
output << "="*80 << "\n"
|
88
|
-
output << filename << "\n"
|
89
|
-
output << "-"*80 << "\n"
|
90
|
-
format_file(output, filename, samples_by_file, total: total)
|
91
97
|
end
|
92
|
-
|
98
|
+
end
|
99
|
+
|
100
|
+
def total
|
101
|
+
thread = @profile.main_thread
|
102
|
+
thread[:weights].sum
|
93
103
|
end
|
94
104
|
|
95
105
|
def format_file(output, filename, all_samples, total:)
|
@@ -108,6 +118,35 @@ module Vernier
|
|
108
118
|
end
|
109
119
|
end
|
110
120
|
end
|
121
|
+
|
122
|
+
def html_output(output, relevant_files)
|
123
|
+
output << "<pre>"
|
124
|
+
output << " SELF FILE\n"
|
125
|
+
relevant_files.sort_by {|k, v| -v.values.map(&:self).sum }.each do |filename, file_contents|
|
126
|
+
tmpl = "<details style=\"display:inline-block;vertical-align:top;\"><summary>%s</summary>"
|
127
|
+
output << sprintf("% 5.1f%% #{tmpl}\n", file_contents.values.map(&:self).sum * 100 / total.to_f, filename)
|
128
|
+
format_file_html(output, filename, relevant_files)
|
129
|
+
output << "</details>\n"
|
130
|
+
end
|
131
|
+
output << "</pre>"
|
132
|
+
end
|
133
|
+
|
134
|
+
def format_file_html(output, filename, relevant_files)
|
135
|
+
samples = relevant_files[filename]
|
136
|
+
|
137
|
+
# file_name, lines, file_wall, file_cpu, file_idle, file_sort
|
138
|
+
output << sprintf(" TOTAL | SELF | LINE SOURCE\n")
|
139
|
+
File.readlines(filename).each_with_index do |line, i|
|
140
|
+
lineno = i + 1
|
141
|
+
calls = samples[lineno]
|
142
|
+
|
143
|
+
if calls && calls.total > 0
|
144
|
+
output << sprintf("%5.1f%% | %5.1f%% | % 4i %s", 100 * calls.total / total.to_f, 100 * calls.self / total.to_f, lineno, CGI::escapeHTML(line))
|
145
|
+
else
|
146
|
+
output << sprintf(" | | % 4i %s", lineno, CGI::escapeHTML(line))
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
111
150
|
end
|
112
151
|
end
|
113
152
|
end
|
@@ -145,7 +145,18 @@ module Vernier
|
|
145
145
|
end,
|
146
146
|
sourceCodeIsNotOnSearchfox: true,
|
147
147
|
initialVisibleThreads: threads.each_index.to_a,
|
148
|
-
initialSelectedThreads: Array(threads.find_index(&:is_start))
|
148
|
+
initialSelectedThreads: Array(threads.find_index(&:is_start)),
|
149
|
+
vernierUserMetadata: profile.meta[:user_metadata],
|
150
|
+
extra: [
|
151
|
+
label: "User-Supplied Metadata",
|
152
|
+
entries: profile.meta[:user_metadata].map do |k, v|
|
153
|
+
{
|
154
|
+
label: k,
|
155
|
+
format: "string",
|
156
|
+
value: v
|
157
|
+
}
|
158
|
+
end
|
159
|
+
]
|
149
160
|
},
|
150
161
|
counters: counter_data,
|
151
162
|
libs: [],
|
@@ -487,17 +498,21 @@ module Vernier
|
|
487
498
|
def frame_table
|
488
499
|
funcs = @stack_table_hash[:frame_table].fetch(:func)
|
489
500
|
lines = @stack_table_hash[:frame_table].fetch(:line)
|
490
|
-
size
|
501
|
+
raise unless lines.size == funcs.size
|
502
|
+
|
503
|
+
size = funcs.size
|
491
504
|
none = [nil] * size
|
492
|
-
|
505
|
+
default = [0] * size
|
506
|
+
unidentified = [-1] * size
|
493
507
|
|
494
|
-
|
508
|
+
categories = @frame_categories.map(&:idx)
|
509
|
+
subcategories = @frame_subcategories
|
495
510
|
|
496
511
|
{
|
497
|
-
address:
|
498
|
-
inlineDepth:
|
512
|
+
address: unidentified,
|
513
|
+
inlineDepth: default,
|
499
514
|
category: categories,
|
500
|
-
subcategory:
|
515
|
+
subcategory: subcategories,
|
501
516
|
func: funcs,
|
502
517
|
nativeSymbol: none,
|
503
518
|
innerWindowID: none,
|
@@ -90,7 +90,7 @@ module Vernier
|
|
90
90
|
end
|
91
91
|
|
92
92
|
class Stack < BaseType
|
93
|
-
def
|
93
|
+
def each
|
94
94
|
return enum_for(__method__) unless block_given?
|
95
95
|
|
96
96
|
stack_idx = idx
|
@@ -100,6 +100,17 @@ module Vernier
|
|
100
100
|
stack_idx = stack_table.stack_parent_idx(stack_idx)
|
101
101
|
end
|
102
102
|
end
|
103
|
+
alias each_frame each
|
104
|
+
|
105
|
+
def [](n)
|
106
|
+
raise RangeError if n < 0
|
107
|
+
stack_idx = idx
|
108
|
+
while n > 0
|
109
|
+
stack_idx = stack_table.stack_parent_idx(stack_idx)
|
110
|
+
n -= 1
|
111
|
+
end
|
112
|
+
Frame.new(stack_table, stack_table.stack_frame_idx(stack_idx))
|
113
|
+
end
|
103
114
|
|
104
115
|
def leaf_frame_idx
|
105
116
|
stack_table.stack_frame_idx(idx)
|
data/lib/vernier/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: vernier
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Hawthorn
|
8
|
-
autorequire:
|
9
8
|
bindir: exe
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 2025-04-03 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: activesupport
|
@@ -112,7 +111,6 @@ metadata:
|
|
112
111
|
homepage_uri: https://github.com/jhawthorn/vernier
|
113
112
|
source_code_uri: https://github.com/jhawthorn/vernier
|
114
113
|
changelog_uri: https://github.com/jhawthorn/vernier
|
115
|
-
post_install_message:
|
116
114
|
rdoc_options: []
|
117
115
|
require_paths:
|
118
116
|
- lib
|
@@ -127,8 +125,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
127
125
|
- !ruby/object:Gem::Version
|
128
126
|
version: '0'
|
129
127
|
requirements: []
|
130
|
-
rubygems_version: 3.
|
131
|
-
signing_key:
|
128
|
+
rubygems_version: 3.6.2
|
132
129
|
specification_version: 4
|
133
130
|
summary: A next generation CRuby profiler
|
134
131
|
test_files: []
|