vernier 1.0.0 → 1.1.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 +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
|