vernier 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +33 -5
- data/examples/gvl_sleep.rb +7 -22
- data/ext/vernier/vernier.cc +69 -6
- data/lib/vernier/collector.rb +7 -4
- data/lib/vernier/middleware.rb +2 -2
- data/lib/vernier/output/firefox.rb +46 -30
- data/lib/vernier/result.rb +13 -16
- data/lib/vernier/thread_names.rb +1 -1
- data/lib/vernier/version.rb +1 -1
- data/lib/vernier.rb +2 -2
- data/vernier.gemspec +1 -0
- metadata +17 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: aa7e8ccc4ac1312ccffb820ec0c6690209725a26ff0f5a9945a508edca073975
|
4
|
+
data.tar.gz: a0f7b10b284dd8fd387a8b12e15a2c9e8535e07f9d19f37c0734b08c93693956
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7effed8c46b5110437f12d3fb8a4c4707e334c7edd51d4838198bb8dfb56cdd48c6d1700464be2d32b979f97eafc4c45a2406a4028cceadcac3bd71c1520babb
|
7
|
+
data.tar.gz: 4f11e253e293fd455d1eff820262fb6ea217bf0e3c574dd14bc15c3638e576fd3fb25ee84c3cdeb7151b2877d3f918b4157262f3268797482c192f3935b8768a
|
data/README.md
CHANGED
@@ -8,10 +8,10 @@ Next-generation Ruby 3.2.1+ sampling profiler. Tracks multiple threads, GVL acti
|
|
8
8
|
|
9
9
|
[Livestreamed demo: Pairin' with Aaron (YouTube)](https://www.youtube.com/watch?v=9nvX3OHykGQ#t=27m43)
|
10
10
|
|
11
|
-
Sidekiq jobs from Mastodon (time,
|
11
|
+
Sidekiq jobs from Mastodon (time, threaded)
|
12
12
|
: https://share.firefox.dev/44jZRf3
|
13
13
|
|
14
|
-
Puma web requests from Mastodon (time,
|
14
|
+
Puma web requests from Mastodon (time, threaded)
|
15
15
|
: https://share.firefox.dev/48FOTnF
|
16
16
|
|
17
17
|
Rails benchmark - lobste.rs (time)
|
@@ -25,7 +25,7 @@ Rails benchmark - lobste.rs (time)
|
|
25
25
|
Vernier requires Ruby version 3.2.1 or greater
|
26
26
|
|
27
27
|
```ruby
|
28
|
-
gem
|
28
|
+
gem "vernier", "~> 1.0"
|
29
29
|
```
|
30
30
|
|
31
31
|
## Usage
|
@@ -50,16 +50,34 @@ starting profiler with interval 500
|
|
50
50
|
written to /tmp/profile20240328-82441-gkzffc.vernier.json
|
51
51
|
```
|
52
52
|
|
53
|
-
|
53
|
+
#### Block of code
|
54
54
|
|
55
55
|
``` ruby
|
56
|
-
Vernier.
|
56
|
+
Vernier.profile(out: "time_profile.json") do
|
57
57
|
some_slow_method
|
58
58
|
end
|
59
59
|
```
|
60
60
|
|
61
|
+
Alternatively you can use the aliases `Vernier.run` and `Vernier.trace`.
|
62
|
+
|
63
|
+
#### Start and stop
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
Vernier.start_profile(out: "time_profile.json")
|
67
|
+
|
68
|
+
some_slow_method
|
69
|
+
|
70
|
+
# some other file
|
71
|
+
|
72
|
+
some_other_slow_method
|
73
|
+
|
74
|
+
Vernier.stop_profile
|
75
|
+
```
|
76
|
+
|
61
77
|
### Retained memory
|
62
78
|
|
79
|
+
#### Block of code
|
80
|
+
|
63
81
|
Record a flamegraph of all **retained** allocations from loading `irb`.
|
64
82
|
|
65
83
|
```
|
@@ -68,6 +86,16 @@ ruby -r vernier -e 'Vernier.trace_retained(out: "irb_profile.json") { require "i
|
|
68
86
|
|
69
87
|
Retained-memory flamegraphs must be interpreted a little differently than a typical profiling flamegraph. In a retained-memory flamegraph, the x-axis represents a proportion of memory in bytes, _not time or samples_ The topmost boxes on the y-axis represent the retained objects, with their stacktrace below; their width represents the percentage of overall retained memory each object occupies.
|
70
88
|
|
89
|
+
### Options
|
90
|
+
|
91
|
+
Option | Description
|
92
|
+
:- | :-
|
93
|
+
`mode` | The sampling mode to use. One of `:wall`, `:retained` or `:custom`. Default is `:wall`.
|
94
|
+
`out` | The file to write the profile to.
|
95
|
+
`interval` | The sampling interval in microseconds. Default is `500`. Only available in `:wall` mode.
|
96
|
+
`allocation_sample_rate` | The allocation sampling interval in number of allocations. Default is `0` (disabled). Only available in `:wall` mode.
|
97
|
+
`gc` | Initiate a full and immediate garbage collection cycle before profiling. Default is `true`. Only available in `:retained` mode.
|
98
|
+
|
71
99
|
## Development
|
72
100
|
|
73
101
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/examples/gvl_sleep.rb
CHANGED
@@ -1,34 +1,19 @@
|
|
1
1
|
# Different (bad) ways to sleep
|
2
2
|
|
3
|
-
|
4
|
-
#include <time.h>
|
5
|
-
#include <sys/errno.h>
|
3
|
+
require 'bundler/inline'
|
6
4
|
|
7
|
-
|
8
|
-
|
9
|
-
ts.tv_sec = 1;
|
10
|
-
ts.tv_nsec = 0;
|
5
|
+
gemfile do
|
6
|
+
source 'https://rubygems.org'
|
11
7
|
|
12
|
-
|
13
|
-
|
14
|
-
rc = nanosleep(&ts, &ts);
|
15
|
-
} while (rc < 0 && errno == EINTR);
|
16
|
-
}
|
17
|
-
EOF
|
18
|
-
|
19
|
-
soext = RbConfig::CONFIG["SOEXT"]
|
20
|
-
system("gcc", "-shared", "-fPIC", "#{__dir__}/my_sleep.c", "-o", "#{__dir__}/my_sleep.#{soext}")
|
21
|
-
|
22
|
-
require "fiddle"
|
23
|
-
|
24
|
-
SLEEP_LIB = Fiddle.dlopen("./my_sleep.#{soext}")
|
8
|
+
gem "gvltest"
|
9
|
+
end
|
25
10
|
|
26
11
|
def cfunc_sleep_gvl
|
27
|
-
|
12
|
+
GVLTest.sleep_holding_gvl(1)
|
28
13
|
end
|
29
14
|
|
30
15
|
def cfunc_sleep_idle
|
31
|
-
|
16
|
+
GVLTest.sleep_without_gvl(1)
|
32
17
|
end
|
33
18
|
|
34
19
|
def ruby_sleep_gvl
|
data/ext/vernier/vernier.cc
CHANGED
@@ -14,9 +14,13 @@
|
|
14
14
|
|
15
15
|
#include <sys/time.h>
|
16
16
|
#include <signal.h>
|
17
|
-
#
|
17
|
+
#if defined(__APPLE__)
|
18
18
|
/* macOS */
|
19
19
|
#include <dispatch/dispatch.h>
|
20
|
+
#elif defined(__FreeBSD__)
|
21
|
+
/* FreeBSD */
|
22
|
+
#include <pthread_np.h>
|
23
|
+
#include <semaphore.h>
|
20
24
|
#else
|
21
25
|
/* Linux */
|
22
26
|
#include <semaphore.h>
|
@@ -563,6 +567,20 @@ struct StackTable {
|
|
563
567
|
}
|
564
568
|
}
|
565
569
|
|
570
|
+
StackNode *convert_stack(StackTable &other, int original_idx) {
|
571
|
+
if (original_idx < 0) {
|
572
|
+
return &root_stack_node;
|
573
|
+
}
|
574
|
+
|
575
|
+
StackNode &original_node = other.stack_node_list[original_idx];
|
576
|
+
StackNode *parent_node = convert_stack(other, original_node.parent);
|
577
|
+
StackNode *node = next_stack_node(parent_node, original_node.frame);
|
578
|
+
|
579
|
+
return node;
|
580
|
+
}
|
581
|
+
|
582
|
+
static VALUE stack_table_convert(VALUE self, VALUE other, VALUE original_stack);
|
583
|
+
|
566
584
|
static VALUE stack_table_stack_count(VALUE self);
|
567
585
|
static VALUE stack_table_frame_count(VALUE self);
|
568
586
|
static VALUE stack_table_func_count(VALUE self);
|
@@ -661,6 +679,32 @@ StackTable::stack_table_stack_count(VALUE self) {
|
|
661
679
|
return INT2NUM(count);
|
662
680
|
}
|
663
681
|
|
682
|
+
VALUE
|
683
|
+
StackTable::stack_table_convert(VALUE self, VALUE original_tableval, VALUE original_idxval) {
|
684
|
+
StackTable *stack_table = get_stack_table(self);
|
685
|
+
StackTable *original_table = get_stack_table(original_tableval);
|
686
|
+
int original_idx = NUM2INT(original_idxval);
|
687
|
+
|
688
|
+
int original_size;
|
689
|
+
{
|
690
|
+
const std::lock_guard<std::mutex> lock(original_table->stack_mutex);
|
691
|
+
original_size = original_table->stack_node_list.size();
|
692
|
+
}
|
693
|
+
|
694
|
+
if (original_idx >= original_size || original_idx < 0) {
|
695
|
+
rb_raise(rb_eRangeError, "index out of range");
|
696
|
+
}
|
697
|
+
|
698
|
+
int result_idx;
|
699
|
+
{
|
700
|
+
const std::lock_guard<std::mutex> lock1(stack_table->stack_mutex);
|
701
|
+
const std::lock_guard<std::mutex> lock2(original_table->stack_mutex);
|
702
|
+
StackNode *node = stack_table->convert_stack(*original_table, original_idx);
|
703
|
+
result_idx = node->index;
|
704
|
+
}
|
705
|
+
return INT2NUM(result_idx);
|
706
|
+
}
|
707
|
+
|
664
708
|
VALUE
|
665
709
|
StackTable::stack_table_frame_count(VALUE self) {
|
666
710
|
StackTable *stack_table = get_stack_table(self);
|
@@ -786,11 +830,13 @@ class SampleTranslator {
|
|
786
830
|
|
787
831
|
typedef uint64_t native_thread_id_t;
|
788
832
|
static native_thread_id_t get_native_thread_id() {
|
789
|
-
#
|
833
|
+
#if defined(__APPLE__)
|
790
834
|
uint64_t thread_id;
|
791
835
|
int e = pthread_threadid_np(pthread_self(), &thread_id);
|
792
836
|
if (e != 0) rb_syserr_fail(e, "pthread_threadid_np");
|
793
837
|
return thread_id;
|
838
|
+
#elif defined(__FreeBSD__)
|
839
|
+
return pthread_getthreadid_np();
|
794
840
|
#else
|
795
841
|
// gettid() is only available as of glibc 2.30
|
796
842
|
pid_t tid = syscall(SYS_gettid);
|
@@ -1156,6 +1202,10 @@ class Thread {
|
|
1156
1202
|
return rb_thread_main() == ruby_thread;
|
1157
1203
|
}
|
1158
1204
|
|
1205
|
+
bool is_start(VALUE start_thread) {
|
1206
|
+
return start_thread == ruby_thread;
|
1207
|
+
}
|
1208
|
+
|
1159
1209
|
bool running() {
|
1160
1210
|
return state != State::STOPPED;
|
1161
1211
|
}
|
@@ -1263,6 +1313,7 @@ class BaseCollector {
|
|
1263
1313
|
StackTable *stack_table;
|
1264
1314
|
VALUE stack_table_value;
|
1265
1315
|
|
1316
|
+
VALUE start_thread;
|
1266
1317
|
TimeStamp started_at;
|
1267
1318
|
|
1268
1319
|
BaseCollector(VALUE stack_table_value) : stack_table_value(stack_table_value), stack_table(get_stack_table(stack_table_value)) {
|
@@ -1274,6 +1325,7 @@ class BaseCollector {
|
|
1274
1325
|
return false;
|
1275
1326
|
}
|
1276
1327
|
|
1328
|
+
start_thread = rb_thread_current();
|
1277
1329
|
started_at = TimeStamp::Now();
|
1278
1330
|
|
1279
1331
|
running = true;
|
@@ -1289,17 +1341,19 @@ class BaseCollector {
|
|
1289
1341
|
return Qnil;
|
1290
1342
|
}
|
1291
1343
|
|
1292
|
-
void write_meta(VALUE result) {
|
1293
|
-
VALUE meta = rb_hash_new();
|
1294
|
-
rb_ivar_set(result, rb_intern("@meta"), meta);
|
1344
|
+
virtual void write_meta(VALUE meta, VALUE result) {
|
1295
1345
|
rb_hash_aset(meta, sym("started_at"), ULL2NUM(started_at.nanoseconds()));
|
1346
|
+
rb_hash_aset(meta, sym("interval"), Qnil);
|
1347
|
+
rb_hash_aset(meta, sym("allocation_sample_rate"), Qnil);
|
1296
1348
|
|
1297
1349
|
}
|
1298
1350
|
|
1299
1351
|
virtual VALUE build_collector_result() {
|
1300
1352
|
VALUE result = rb_obj_alloc(rb_cVernierResult);
|
1301
1353
|
|
1302
|
-
|
1354
|
+
VALUE meta = rb_hash_new();
|
1355
|
+
rb_ivar_set(result, rb_intern("@meta"), meta);
|
1356
|
+
write_meta(meta, result);
|
1303
1357
|
|
1304
1358
|
return result;
|
1305
1359
|
}
|
@@ -1616,6 +1670,13 @@ class TimeCollector : public BaseCollector {
|
|
1616
1670
|
|
1617
1671
|
}
|
1618
1672
|
|
1673
|
+
void write_meta(VALUE meta, VALUE result) {
|
1674
|
+
BaseCollector::write_meta(meta, result);
|
1675
|
+
rb_hash_aset(meta, sym("interval"), ULL2NUM(interval.microseconds()));
|
1676
|
+
rb_hash_aset(meta, sym("allocation_sample_rate"), ULL2NUM(allocation_sample_rate));
|
1677
|
+
|
1678
|
+
}
|
1679
|
+
|
1619
1680
|
private:
|
1620
1681
|
|
1621
1682
|
void record_sample(const RawSample &sample, TimeStamp time, Thread &thread, Category category) {
|
@@ -1883,6 +1944,7 @@ class TimeCollector : public BaseCollector {
|
|
1883
1944
|
rb_hash_aset(hash, sym("stopped_at"), ULL2NUM(thread->stopped_at.nanoseconds()));
|
1884
1945
|
}
|
1885
1946
|
rb_hash_aset(hash, sym("is_main"), thread->is_main() ? Qtrue : Qfalse);
|
1947
|
+
rb_hash_aset(hash, sym("is_start"), thread->is_start(BaseCollector::start_thread) ? Qtrue : Qfalse);
|
1886
1948
|
|
1887
1949
|
}
|
1888
1950
|
|
@@ -2060,6 +2122,7 @@ Init_vernier(void)
|
|
2060
2122
|
rb_undef_alloc_func(rb_cStackTable);
|
2061
2123
|
rb_define_singleton_method(rb_cStackTable, "new", stack_table_new, 0);
|
2062
2124
|
rb_define_method(rb_cStackTable, "current_stack", stack_table_current_stack, -1);
|
2125
|
+
rb_define_method(rb_cStackTable, "convert", StackTable::stack_table_convert, 2);
|
2063
2126
|
rb_define_method(rb_cStackTable, "stack_parent_idx", stack_table_stack_parent_idx, 1);
|
2064
2127
|
rb_define_method(rb_cStackTable, "stack_frame_idx", stack_table_stack_frame_idx, 1);
|
2065
2128
|
rb_define_method(rb_cStackTable, "frame_line_no", StackTable::stack_table_frame_line_no, 1);
|
data/lib/vernier/collector.rb
CHANGED
@@ -6,9 +6,8 @@ require_relative "thread_names"
|
|
6
6
|
module Vernier
|
7
7
|
class Collector
|
8
8
|
def initialize(mode, options = {})
|
9
|
-
|
10
|
-
|
11
|
-
end
|
9
|
+
@gc = options.fetch(:gc, true) && (mode == :retained)
|
10
|
+
GC.start if @gc
|
12
11
|
|
13
12
|
@mode = mode
|
14
13
|
@out = options[:out]
|
@@ -75,7 +74,11 @@ module Vernier
|
|
75
74
|
def stop
|
76
75
|
result = finish
|
77
76
|
|
78
|
-
result.
|
77
|
+
result.meta[:mode] = @mode
|
78
|
+
result.meta[:out] = @out
|
79
|
+
result.meta[:gc] = @gc
|
80
|
+
|
81
|
+
result.stack_table = stack_table
|
79
82
|
@thread_names.finish
|
80
83
|
|
81
84
|
@hooks.each do |hook|
|
data/lib/vernier/middleware.rb
CHANGED
@@ -12,8 +12,8 @@ module Vernier
|
|
12
12
|
permitted = @permit.call(request)
|
13
13
|
return @app.call(env) unless permitted
|
14
14
|
|
15
|
-
interval = request.GET.fetch(
|
16
|
-
allocation_sample_rate = request.GET.fetch(
|
15
|
+
interval = request.GET.fetch("vernier_interval", 200).to_i
|
16
|
+
allocation_sample_rate = request.GET.fetch("vernier_allocation_sample_rate", 200).to_i
|
17
17
|
|
18
18
|
result = Vernier.trace(interval:, allocation_sample_rate:, hooks: [:rails]) do
|
19
19
|
@app.call(env)
|
@@ -106,7 +106,7 @@ module Vernier
|
|
106
106
|
def data
|
107
107
|
markers_by_thread = profile.markers.group_by { |marker| marker[0] }
|
108
108
|
|
109
|
-
|
109
|
+
threads = profile.threads.map do |ruby_thread_id, thread_info|
|
110
110
|
markers = markers_by_thread[ruby_thread_id] || []
|
111
111
|
Thread.new(
|
112
112
|
ruby_thread_id,
|
@@ -114,7 +114,7 @@ module Vernier
|
|
114
114
|
@categorizer,
|
115
115
|
markers: markers,
|
116
116
|
**thread_info,
|
117
|
-
)
|
117
|
+
)
|
118
118
|
end
|
119
119
|
|
120
120
|
{
|
@@ -141,10 +141,12 @@ module Vernier
|
|
141
141
|
subcategories: category.subcategories.map(&:name)
|
142
142
|
}
|
143
143
|
end,
|
144
|
-
sourceCodeIsNotOnSearchfox: true
|
144
|
+
sourceCodeIsNotOnSearchfox: true,
|
145
|
+
initialVisibleThreads: threads.each_index.to_a,
|
146
|
+
initialSelectedThreads: Array(threads.find_index(&:is_start))
|
145
147
|
},
|
146
148
|
libs: [],
|
147
|
-
threads:
|
149
|
+
threads: threads.map(&:data)
|
148
150
|
}
|
149
151
|
end
|
150
152
|
|
@@ -204,9 +206,9 @@ module Vernier
|
|
204
206
|
end
|
205
207
|
|
206
208
|
class Thread
|
207
|
-
attr_reader :profile
|
209
|
+
attr_reader :profile, :is_start
|
208
210
|
|
209
|
-
def initialize(ruby_thread_id, profile, categorizer, name:, tid:, samples:, weights:, timestamps: nil, sample_categories: nil, markers:, started_at:, stopped_at: nil, allocations: nil, is_main: nil)
|
211
|
+
def initialize(ruby_thread_id, profile, categorizer, name:, tid:, samples:, weights:, timestamps: nil, sample_categories: nil, markers:, started_at:, stopped_at: nil, allocations: nil, is_main: nil, is_start: nil)
|
210
212
|
@ruby_thread_id = ruby_thread_id
|
211
213
|
@profile = profile
|
212
214
|
@categorizer = categorizer
|
@@ -218,18 +220,32 @@ module Vernier
|
|
218
220
|
@is_main = @ruby_thread_id == ::Thread.main.object_id
|
219
221
|
end
|
220
222
|
@is_main = true if profile.threads.size == 1
|
223
|
+
@is_start = is_start.nil? ? @is_main : is_start
|
224
|
+
|
225
|
+
@stack_table = Vernier::StackTable.new
|
226
|
+
samples = samples.map { |sample| @stack_table.convert(profile._stack_table, sample) }
|
227
|
+
|
228
|
+
@samples = samples
|
221
229
|
|
222
230
|
timestamps ||= [0] * samples.size
|
223
|
-
@
|
231
|
+
@weights, @timestamps = weights, timestamps
|
224
232
|
@sample_categories = sample_categories || ([0] * samples.size)
|
225
|
-
@markers = markers
|
233
|
+
@markers = markers.map do |marker|
|
234
|
+
if stack_idx = marker[5]&.dig(:cause, :stack)
|
235
|
+
marker = marker.dup
|
236
|
+
new_idx = @stack_table.convert(profile._stack_table, stack_idx)
|
237
|
+
marker[5] = marker[5].merge({ cause: { stack: new_idx }})
|
238
|
+
end
|
239
|
+
marker
|
240
|
+
end
|
226
241
|
|
227
242
|
@started_at, @stopped_at = started_at, stopped_at
|
228
243
|
|
229
|
-
|
230
|
-
|
244
|
+
@stack_table_hash = @stack_table.to_h
|
245
|
+
names = @stack_table_hash[:func_table].fetch(:name)
|
246
|
+
filenames = @stack_table_hash[:func_table].fetch(:filename)
|
231
247
|
|
232
|
-
stacks_size =
|
248
|
+
stacks_size = @stack_table.stack_count
|
233
249
|
@categorized_stacks = Hash.new do |h, k|
|
234
250
|
h[k] = h.size + stacks_size
|
235
251
|
end
|
@@ -243,8 +259,6 @@ module Vernier
|
|
243
259
|
@strings[filename]
|
244
260
|
end
|
245
261
|
|
246
|
-
lines = profile.frame_table.fetch(:line)
|
247
|
-
|
248
262
|
func_implementations = filenames.map do |filename|
|
249
263
|
# Must match strings in `src/profile-logic/profile-data.js`
|
250
264
|
# inside the firefox profiler. See `getFriendlyStackTypeName`
|
@@ -255,24 +269,27 @@ module Vernier
|
|
255
269
|
nil
|
256
270
|
end
|
257
271
|
end
|
258
|
-
@frame_implementations =
|
272
|
+
@frame_implementations = @stack_table_hash[:frame_table].fetch(:func).map do |func_idx|
|
259
273
|
func_implementations[func_idx]
|
260
274
|
end
|
261
275
|
|
262
276
|
cfunc_category = @categorizer.get_category("cfunc")
|
263
277
|
ruby_category = @categorizer.get_category("Ruby")
|
264
|
-
func_categories =
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
278
|
+
func_categories, func_subcategories = [], []
|
279
|
+
filenames.each do |filename|
|
280
|
+
if filename == "<cfunc>"
|
281
|
+
func_categories << cfunc_category
|
282
|
+
func_subcategories << 0
|
283
|
+
else
|
284
|
+
func_categories << ruby_category
|
285
|
+
subcategory = ruby_category.subcategories.detect {|c| c.matches?(filename) }&.idx || 0
|
286
|
+
func_subcategories << subcategory
|
287
|
+
end
|
271
288
|
end
|
272
|
-
@frame_categories =
|
289
|
+
@frame_categories = @stack_table_hash[:frame_table].fetch(:func).map do |func_idx|
|
273
290
|
func_categories[func_idx]
|
274
291
|
end
|
275
|
-
@frame_subcategories =
|
292
|
+
@frame_subcategories = @stack_table_hash[:frame_table].fetch(:func).map do |func_idx|
|
276
293
|
func_subcategories[func_idx]
|
277
294
|
end
|
278
295
|
end
|
@@ -420,15 +437,14 @@ module Vernier
|
|
420
437
|
stack: samples,
|
421
438
|
time: times,
|
422
439
|
weight: weights,
|
423
|
-
weightType: "samples",
|
424
|
-
#weightType: "bytes",
|
440
|
+
weightType: profile.meta[:mode] == :retained ? "bytes" : "samples",
|
425
441
|
length: samples.length
|
426
442
|
}
|
427
443
|
end
|
428
444
|
|
429
445
|
def stack_table
|
430
|
-
frames =
|
431
|
-
prefixes =
|
446
|
+
frames = @stack_table_hash[:stack_table].fetch(:frame).dup
|
447
|
+
prefixes = @stack_table_hash[:stack_table].fetch(:parent).dup
|
432
448
|
categories = frames.map{|idx| @frame_categories[idx].idx }
|
433
449
|
subcategories = frames.map{|idx| @frame_subcategories[idx] }
|
434
450
|
|
@@ -452,8 +468,8 @@ module Vernier
|
|
452
468
|
end
|
453
469
|
|
454
470
|
def frame_table
|
455
|
-
funcs =
|
456
|
-
lines =
|
471
|
+
funcs = @stack_table_hash[:frame_table].fetch(:func)
|
472
|
+
lines = @stack_table_hash[:frame_table].fetch(:line)
|
457
473
|
size = funcs.length
|
458
474
|
none = [nil] * size
|
459
475
|
categories = @frame_categories.map(&:idx)
|
@@ -480,7 +496,7 @@ module Vernier
|
|
480
496
|
|
481
497
|
cfunc_idx = @strings["<cfunc>"]
|
482
498
|
is_js = @filenames.map { |fn| fn != cfunc_idx }
|
483
|
-
line_numbers =
|
499
|
+
line_numbers = @stack_table_hash[:func_table].fetch(:first_line).map.with_index do |line, i|
|
484
500
|
if is_js[i] || line != 0
|
485
501
|
line
|
486
502
|
else
|
data/lib/vernier/result.rb
CHANGED
@@ -1,15 +1,11 @@
|
|
1
1
|
module Vernier
|
2
2
|
class Result
|
3
|
-
def stack_table
|
4
|
-
@stack_table
|
3
|
+
def stack_table=(stack_table)
|
4
|
+
@stack_table = stack_table
|
5
5
|
end
|
6
6
|
|
7
|
-
def
|
8
|
-
@stack_table
|
9
|
-
end
|
10
|
-
|
11
|
-
def func_table
|
12
|
-
@stack_table[:func_table]
|
7
|
+
def _stack_table
|
8
|
+
@stack_table
|
13
9
|
end
|
14
10
|
|
15
11
|
attr_reader :markers
|
@@ -19,6 +15,7 @@ module Vernier
|
|
19
15
|
attr_accessor :pid, :end_time
|
20
16
|
attr_accessor :threads
|
21
17
|
attr_accessor :meta
|
18
|
+
attr_accessor :mode
|
22
19
|
|
23
20
|
def main_thread
|
24
21
|
threads.values.detect {|x| x[:is_main] }
|
@@ -43,7 +40,7 @@ module Vernier
|
|
43
40
|
|
44
41
|
def write(out:)
|
45
42
|
gzip = out.end_with?(".gz")
|
46
|
-
File.
|
43
|
+
File.binwrite(out, to_gecko(gzip:))
|
47
44
|
end
|
48
45
|
|
49
46
|
def elapsed_seconds
|
@@ -81,12 +78,12 @@ module Vernier
|
|
81
78
|
|
82
79
|
class Func < BaseType
|
83
80
|
def label
|
84
|
-
result.
|
81
|
+
result._stack_table.func_name(idx)
|
85
82
|
end
|
86
83
|
alias name label
|
87
84
|
|
88
85
|
def filename
|
89
|
-
result.
|
86
|
+
result._stack_table.func_filename(idx)
|
90
87
|
end
|
91
88
|
|
92
89
|
def to_s
|
@@ -100,12 +97,12 @@ module Vernier
|
|
100
97
|
alias name label
|
101
98
|
|
102
99
|
def func
|
103
|
-
func_idx = result.
|
100
|
+
func_idx = result._stack_table.frame_func_idx(idx)
|
104
101
|
Func.new(result, func_idx)
|
105
102
|
end
|
106
103
|
|
107
104
|
def line
|
108
|
-
result.
|
105
|
+
result.frame_line_idx(idx)
|
109
106
|
end
|
110
107
|
|
111
108
|
def to_s
|
@@ -119,14 +116,14 @@ module Vernier
|
|
119
116
|
|
120
117
|
stack_idx = idx
|
121
118
|
while stack_idx
|
122
|
-
frame_idx = result.
|
119
|
+
frame_idx = result._stack_table.stack_frame_idx(stack_idx)
|
123
120
|
yield Frame.new(result, frame_idx)
|
124
|
-
stack_idx = result.
|
121
|
+
stack_idx = result._stack_table.stack_parent_idx(stack_idx)
|
125
122
|
end
|
126
123
|
end
|
127
124
|
|
128
125
|
def leaf_frame_idx
|
129
|
-
result.
|
126
|
+
result._stack_table.stack_frame_idx(idx)
|
130
127
|
end
|
131
128
|
|
132
129
|
def leaf_frame
|
data/lib/vernier/thread_names.rb
CHANGED
data/lib/vernier/version.rb
CHANGED
data/lib/vernier.rb
CHANGED
@@ -56,8 +56,8 @@ module Vernier
|
|
56
56
|
result
|
57
57
|
end
|
58
58
|
|
59
|
-
def self.trace_retained(
|
60
|
-
profile(mode: :retained,
|
59
|
+
def self.trace_retained(**profile_options, &block)
|
60
|
+
profile(**profile_options.merge(mode: :retained), &block)
|
61
61
|
end
|
62
62
|
|
63
63
|
class Collector
|
data/vernier.gemspec
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: vernier
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Hawthorn
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-07-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: gvltest
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: rack
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -103,7 +117,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
103
117
|
- !ruby/object:Gem::Version
|
104
118
|
version: '0'
|
105
119
|
requirements: []
|
106
|
-
rubygems_version: 3.
|
120
|
+
rubygems_version: 3.5.9
|
107
121
|
signing_key:
|
108
122
|
specification_version: 4
|
109
123
|
summary: A next generation CRuby profiler
|