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 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