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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 682bc2ce2b915b0fa21db46232322f1bafba1916fc4e1b58cbaeafe3631ab3f2
4
- data.tar.gz: af659f7fe18efa70c41fa178ff6ed52244292287f1b59e4950fcad6db0a1675e
3
+ metadata.gz: aa7e8ccc4ac1312ccffb820ec0c6690209725a26ff0f5a9945a508edca073975
4
+ data.tar.gz: a0f7b10b284dd8fd387a8b12e15a2c9e8535e07f9d19f37c0734b08c93693956
5
5
  SHA512:
6
- metadata.gz: a373326fd6ddb7043b57b5e0b8f8ddc96c666368ae38fbd8398df29e09fffea9141bc9a4166e6f0970817685c86e93c9cf3fc1312ca49fb9ae9ba1ba49dd60f8
7
- data.tar.gz: 59026602a1d4103e0b245e7fa63fd7168bfa9c57913d1d7a003349f4818c7b303da050b19c4c31523f853e81715ab56e425bccfa77eee8053486e4dc12ea864f
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, threded)
11
+ Sidekiq jobs from Mastodon (time, threaded)
12
12
  : https://share.firefox.dev/44jZRf3
13
13
 
14
- Puma web requests from Mastodon (time, threded)
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 'vernier'
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
- ### Block of code
53
+ #### Block of code
54
54
 
55
55
  ``` ruby
56
- Vernier.run(out: "time_profile.json") do
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.
@@ -1,34 +1,19 @@
1
1
  # Different (bad) ways to sleep
2
2
 
3
- File.write("#{__dir__}/my_sleep.c", <<~EOF)
4
- #include <time.h>
5
- #include <sys/errno.h>
3
+ require 'bundler/inline'
6
4
 
7
- void my_sleep() {
8
- struct timespec ts;
9
- ts.tv_sec = 1;
10
- ts.tv_nsec = 0;
5
+ gemfile do
6
+ source 'https://rubygems.org'
11
7
 
12
- int rc;
13
- do {
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
- Fiddle::Function.new(SLEEP_LIB['my_sleep'], [], Fiddle::TYPE_VOID, need_gvl: true).call
12
+ GVLTest.sleep_holding_gvl(1)
28
13
  end
29
14
 
30
15
  def cfunc_sleep_idle
31
- Fiddle::Function.new(SLEEP_LIB['my_sleep'], [], Fiddle::TYPE_VOID, need_gvl: true).call
16
+ GVLTest.sleep_without_gvl(1)
32
17
  end
33
18
 
34
19
  def ruby_sleep_gvl
@@ -14,9 +14,13 @@
14
14
 
15
15
  #include <sys/time.h>
16
16
  #include <signal.h>
17
- #ifdef __APPLE__
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
- #ifdef __APPLE__
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
- write_meta(result);
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);
@@ -6,9 +6,8 @@ require_relative "thread_names"
6
6
  module Vernier
7
7
  class Collector
8
8
  def initialize(mode, options = {})
9
- if options.fetch(:gc, true) && (mode == :retained)
10
- GC.start
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.instance_variable_set("@stack_table", stack_table.to_h)
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|
@@ -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(:vernier_interval, 200).to_i
16
- allocation_sample_rate = request.GET.fetch(:vernier_allocation_sample_rate, 200).to_i
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
- thread_data = profile.threads.map do |ruby_thread_id, thread_info|
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
- ).data
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: thread_data
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
- @samples, @weights, @timestamps = samples, weights, timestamps
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
- names = profile.func_table.fetch(:name)
230
- filenames = profile.func_table.fetch(:filename)
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 = profile.stack_table.fetch(:frame).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 = profile.frame_table.fetch(:func).map do |func_idx|
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 = filenames.map do |filename|
265
- filename == "<cfunc>" ? cfunc_category : ruby_category
266
- end
267
- func_subcategories = filenames.map do |filename|
268
- next 0 if filename == "<cfunc>"
269
-
270
- (ruby_category.subcategories.detect {|c| c.matches?(filename) } || ruby_category.subcategories.first).idx
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 = profile.frame_table.fetch(:func).map do |func_idx|
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 = profile.frame_table.fetch(:func).map do |func_idx|
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 = profile.stack_table.fetch(:frame).dup
431
- prefixes = profile.stack_table.fetch(:parent).dup
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 = profile.frame_table.fetch(:func)
456
- lines = profile.frame_table.fetch(:line)
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 = profile.func_table.fetch(:first_line).map.with_index do |line, i|
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
@@ -1,15 +1,11 @@
1
1
  module Vernier
2
2
  class Result
3
- def stack_table
4
- @stack_table[:stack_table]
3
+ def stack_table=(stack_table)
4
+ @stack_table = stack_table
5
5
  end
6
6
 
7
- def frame_table
8
- @stack_table[:frame_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.write(out, to_gecko(gzip:))
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.func_table[:name][idx]
81
+ result._stack_table.func_name(idx)
85
82
  end
86
83
  alias name label
87
84
 
88
85
  def filename
89
- result.func_table[:filename][idx]
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.frame_table[:func][idx]
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.frame_table[:line][idx]
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.stack_table[:frame][stack_idx]
119
+ frame_idx = result._stack_table.stack_frame_idx(stack_idx)
123
120
  yield Frame.new(result, frame_idx)
124
- stack_idx = result.stack_table[:parent][stack_idx]
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.stack_table[:frame][idx]
126
+ result._stack_table.stack_frame_idx(idx)
130
127
  end
131
128
 
132
129
  def leaf_frame
@@ -10,7 +10,7 @@ module Vernier
10
10
  end
11
11
 
12
12
  def [](object_id)
13
- @names[object_id] || "unknown thread #{object_id}"
13
+ @names[object_id] || "thread obj_id:#{object_id}"
14
14
  end
15
15
 
16
16
  def finish
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Vernier
4
- VERSION = "1.0.0"
4
+ VERSION = "1.1.0"
5
5
  end
data/lib/vernier.rb CHANGED
@@ -56,8 +56,8 @@ module Vernier
56
56
  result
57
57
  end
58
58
 
59
- def self.trace_retained(out: nil, gc: true, &block)
60
- profile(mode: :retained, out:, gc:, &block)
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
@@ -31,5 +31,6 @@ Gem::Specification.new do |spec|
31
31
  spec.extensions = ["ext/vernier/extconf.rb"]
32
32
 
33
33
  spec.add_development_dependency "activesupport"
34
+ spec.add_development_dependency "gvltest"
34
35
  spec.add_development_dependency "rack"
35
36
  end
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.0.0
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-05-03 00:00:00.000000000 Z
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.4.19
120
+ rubygems_version: 3.5.9
107
121
  signing_key:
108
122
  specification_version: 4
109
123
  summary: A next generation CRuby profiler