vernier 1.2.1 → 1.3.1

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: 89d2c876f873a04f937175301ba227c5dd3e883f0e2fefbaf6927f409c88d511
4
- data.tar.gz: 469d58efbd5ea8ef87288d0d7c07e74a4ede94aa60743afce80ed57904f4e40e
3
+ metadata.gz: e471649c05955e4b4411e87aed57ce900eca0d9c01750feca9df4c35a5de1541
4
+ data.tar.gz: e37b6d23c3178224d3de32d930bc27459dc49fb87172509222e03b1142ea524a
5
5
  SHA512:
6
- metadata.gz: 41bd857e2a36ddf757ffd5139d3b897202039ac8fb21020856c7f44b979b34488d154d2f88fa3a397124a131109be9c047a765d249d9b190a7f320d66c33c0c5
7
- data.tar.gz: fc6141244154307323bb0460ff7142c20cf7d57e7b6f03caf33b9febe9a975b82c1bdbb435174645523287edc4675ea551d37c00baf5a02137f1bc56ddd4a6ef
6
+ metadata.gz: 8fb4c59fe60b6b905c2491f6dc02e557d55c61c4bc233105ca6a6eb99e2c4264c9dbe3442185fdf52ff7d8d8f7d20324c05aeb390cbbfecbf612d3ee84ca6146
7
+ data.tar.gz: 9b57699f365e9f5460454da9dc45b36081baee0a1a94e51506c948cf9f76410c74a6c140ff01336117fc8337714217fb3a64f73ac6098f713283597894f0e070
data/README.md CHANGED
@@ -22,7 +22,7 @@ Rails benchmark - lobste.rs (time)
22
22
 
23
23
  ## Installation
24
24
 
25
- Vernier requires Ruby version 3.2.1 or greater
25
+ Vernier requires Ruby version 3.2.1 or greater.
26
26
 
27
27
  ```ruby
28
28
  gem "vernier", "~> 1.0"
@@ -30,24 +30,29 @@ gem "vernier", "~> 1.0"
30
30
 
31
31
  ## Usage
32
32
 
33
- The output can be viewed in the web app at https://vernier.prof or locally using the [`profile-viewer` gem](https://github.com/tenderlove/profiler/tree/ruby) (both are lightly customized versions of the firefox profiler frontend, which profiles are also compatible with).
33
+ The output can be viewed in the web app at https://vernier.prof, locally using the [`profile-viewer` gem](https://github.com/tenderlove/profiler/tree/ruby) (both lightly customized versions of the firefox profiler frontend, which profiles are also compatible with) or by using the `verrnier view` command in the CLI.
34
34
 
35
35
  - **Flame Graph**: Shows proportionally how much time is spent within particular stack frames. Frames are grouped together, which means that x-axis / left-to-right order is not meaningful.
36
36
  - **Stack Chart**: Shows the stack at each sample with the x-axis representing time and can be read left-to-right.
37
37
 
38
-
39
- ### Time
40
-
38
+ ### Time and Allocations
41
39
 
42
40
  #### Command line
43
41
 
44
- The easiest way to record a program or script is via the CLI
42
+ The easiest way to record a program or script is via the CLI:
45
43
 
46
44
  ```
47
45
  $ vernier run -- ruby -e 'sleep 1'
48
- starting profiler with interval 500
49
- #<Vernier::Result 1.001589 seconds, 1 threads, 2002 samples, 1 unique>
50
- written to /tmp/profile20240328-82441-gkzffc.vernier.json
46
+ starting profiler with interval 500 and allocation interval 0
47
+ #<Vernier::Result 1.001589 seconds, 1 threads, 1 samples, 1 unique>
48
+ written to /tmp/profile20240328-82441-gkzffc.vernier.json.gz
49
+ ```
50
+
51
+ ```
52
+ $ vernier run --interval 100 --allocation-interval 10 -- ruby -e '10.times { Object.new }'
53
+ starting profiler with interval 100 and allocation interval 10
54
+ #<Vernier::Result 0.00067 seconds, 1 threads, 1 samples, 1 unique>
55
+ written to /tmp/profile20241029-26525-dalmym.vernier.json.gz
51
56
  ```
52
57
 
53
58
  #### Block of code
@@ -58,12 +63,18 @@ Vernier.profile(out: "time_profile.json") do
58
63
  end
59
64
  ```
60
65
 
66
+ ``` ruby
67
+ Vernier.profile(out: "time_profile.json", interval: 100, allocation_interval: 10) do
68
+ some_slow_method
69
+ end
70
+ ```
71
+
61
72
  Alternatively you can use the aliases `Vernier.run` and `Vernier.trace`.
62
73
 
63
74
  #### Start and stop
64
75
 
65
76
  ```ruby
66
- Vernier.start_profile(out: "time_profile.json")
77
+ Vernier.start_profile(out: "time_profile.json", interval: 10_000, allocation_interval: 100_000)
67
78
 
68
79
  some_slow_method
69
80
 
@@ -78,13 +89,14 @@ Vernier.stop_profile
78
89
 
79
90
  #### Block of code
80
91
 
81
- Record a flamegraph of all **retained** allocations from loading `irb`.
92
+ Record a flamegraph of all **retained** allocations from loading `irb`:
82
93
 
83
94
  ```
84
95
  ruby -r vernier -e 'Vernier.trace_retained(out: "irb_profile.json") { require "irb" }'
85
96
  ```
86
97
 
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.
98
+ > [!NOTE]
99
+ > 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.
88
100
 
89
101
  ### Options
90
102
 
@@ -93,7 +105,7 @@ Option | Description
93
105
  `mode` | The sampling mode to use. One of `:wall`, `:retained` or `:custom`. Default is `:wall`.
94
106
  `out` | The file to write the profile to.
95
107
  `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.
108
+ `allocation_interval` | The allocation sampling interval in number of allocations. Default is `0` (disabled). Only available in `:wall` mode.
97
109
  `gc` | Initiate a full and immediate garbage collection cycle before profiling. Default is `true`. Only available in `:retained` mode.
98
110
 
99
111
  ## Development
@@ -30,10 +30,10 @@ compare do
30
30
  end
31
31
  end
32
32
 
33
- compare(allocation_sample_rate: 1000) do
33
+ compare(allocation_interval: 1000) do
34
34
  Object.new
35
35
  end
36
36
 
37
- compare(allocation_sample_rate: 1) do
37
+ compare(allocation_interval: 1) do
38
38
  Object.new
39
39
  end
data/examples/rails.rb CHANGED
@@ -123,7 +123,7 @@ end
123
123
  # warm up
124
124
  make_request.call
125
125
 
126
- Vernier.trace(out: "rails.json", hooks: [:rails], allocation_sample_rate: 100) do |collector|
126
+ Vernier.trace(out: "rails.json", hooks: [:rails], allocation_interval: 100) do |collector|
127
127
  1000.times do
128
128
  make_request.call
129
129
  end
@@ -1,6 +1,6 @@
1
1
  require "vernier"
2
2
 
3
- Vernier.trace(out: "http_requests.json", allocation_sample_rate: 100) do
3
+ Vernier.trace(out: "http_requests.json", allocation_interval: 100) do
4
4
 
5
5
  require "net/http"
6
6
  require "uri"
data/exe/vernier CHANGED
@@ -21,8 +21,8 @@ FLAGS:
21
21
  o.on('--interval [MICROSECONDS]', Integer, "sampling interval (default 500)") do |i|
22
22
  options[:interval] = i
23
23
  end
24
- o.on('--allocation_sample_rate [ALLOCATIONS]', Integer, "allocation sampling interval (default 0 disabled)") do |i|
25
- options[:allocation_sample_rate] = i
24
+ o.on('--allocation-interval [ALLOCATIONS]', Integer, "allocation sampling interval (default 0 disabled)") do |i|
25
+ options[:allocation_interval] = i
26
26
  end
27
27
  o.on('--signal [NAME]', String, "specify a signal to start and stop the profiler") do |s|
28
28
  options[:signal] = s
@@ -30,7 +30,7 @@ FLAGS:
30
30
  o.on('--start-paused', "don't automatically start the profiler") do
31
31
  options[:start_paused] = true
32
32
  end
33
- o.on('--hooks [HOOKS]', String, "Enable instrumentation hooks. Currently supported: rails") do |s|
33
+ o.on('--hooks [HOOKS]', String, "enable instrumentation hooks, currently supported: rails") do |s|
34
34
  options[:hooks] = s
35
35
  end
36
36
  end
@@ -38,7 +38,7 @@ FLAGS:
38
38
 
39
39
  def self.view(options)
40
40
  banner = <<-END
41
- Usage: vernier view [FLAGS] FILENAME
41
+ Usage: vernier view [FLAGS] -- FILENAME
42
42
 
43
43
  FLAGS:
44
44
  END
@@ -1337,7 +1337,7 @@ class BaseCollector {
1337
1337
  virtual void write_meta(VALUE meta, VALUE result) {
1338
1338
  rb_hash_aset(meta, sym("started_at"), ULL2NUM(started_at.nanoseconds()));
1339
1339
  rb_hash_aset(meta, sym("interval"), Qnil);
1340
- rb_hash_aset(meta, sym("allocation_sample_rate"), Qnil);
1340
+ rb_hash_aset(meta, sym("allocation_interval"), Qnil);
1341
1341
 
1342
1342
  }
1343
1343
 
@@ -1360,6 +1360,9 @@ class BaseCollector {
1360
1360
  rb_gc_mark(stack_table_value);
1361
1361
  };
1362
1362
 
1363
+ virtual void compact() {
1364
+ };
1365
+
1363
1366
  virtual VALUE get_markers() {
1364
1367
  return rb_ary_new();
1365
1368
  };
@@ -1547,6 +1550,23 @@ class RetainedCollector : public BaseCollector {
1547
1550
  rb_gc_mark(tp_newobj);
1548
1551
  rb_gc_mark(tp_freeobj);
1549
1552
  }
1553
+
1554
+ void compact() {
1555
+ RetainedCollector *collector = this;
1556
+ for (auto& obj: collector->object_list) {
1557
+ VALUE reloc_obj = rb_gc_location(obj);
1558
+
1559
+ const auto search = collector->object_frames.find(obj);
1560
+ if (search != collector->object_frames.end()) {
1561
+ int stack_index = search->second;
1562
+
1563
+ collector->object_frames.erase(search);
1564
+ collector->object_frames.emplace(reloc_obj, stack_index);
1565
+ }
1566
+
1567
+ obj = reloc_obj;
1568
+ }
1569
+ }
1550
1570
  };
1551
1571
 
1552
1572
  class GlobalSignalHandler {
@@ -1627,8 +1647,8 @@ class TimeCollector : public BaseCollector {
1627
1647
  SignalSafeSemaphore thread_stopped;
1628
1648
 
1629
1649
  TimeStamp interval;
1630
- unsigned int allocation_sample_rate;
1631
- unsigned int allocation_sample_tick = 0;
1650
+ unsigned int allocation_interval;
1651
+ unsigned int allocation_tick = 0;
1632
1652
 
1633
1653
  VALUE tp_newobj = Qnil;
1634
1654
 
@@ -1641,14 +1661,14 @@ class TimeCollector : public BaseCollector {
1641
1661
  }
1642
1662
 
1643
1663
  public:
1644
- TimeCollector(VALUE stack_table, TimeStamp interval, unsigned int allocation_sample_rate) : BaseCollector(stack_table), interval(interval), allocation_sample_rate(allocation_sample_rate), threads(*get_stack_table(stack_table)) {
1664
+ TimeCollector(VALUE stack_table, TimeStamp interval, unsigned int allocation_interval) : BaseCollector(stack_table), interval(interval), allocation_interval(allocation_interval), threads(*get_stack_table(stack_table)) {
1645
1665
  }
1646
1666
 
1647
1667
  void record_newobj(VALUE obj) {
1648
- if (++allocation_sample_tick < allocation_sample_rate) {
1668
+ if (++allocation_tick < allocation_interval) {
1649
1669
  return;
1650
1670
  }
1651
- allocation_sample_tick = 0;
1671
+ allocation_tick = 0;
1652
1672
 
1653
1673
  VALUE current_thread = rb_thread_current();
1654
1674
  threads.mutex.lock();
@@ -1666,7 +1686,7 @@ class TimeCollector : public BaseCollector {
1666
1686
  void write_meta(VALUE meta, VALUE result) {
1667
1687
  BaseCollector::write_meta(meta, result);
1668
1688
  rb_hash_aset(meta, sym("interval"), ULL2NUM(interval.microseconds()));
1669
- rb_hash_aset(meta, sym("allocation_sample_rate"), ULL2NUM(allocation_sample_rate));
1689
+ rb_hash_aset(meta, sym("allocation_interval"), ULL2NUM(allocation_interval));
1670
1690
 
1671
1691
  }
1672
1692
 
@@ -1864,7 +1884,7 @@ class TimeCollector : public BaseCollector {
1864
1884
  this->threads.initial(thread);
1865
1885
  }
1866
1886
 
1867
- if (allocation_sample_rate > 0) {
1887
+ if (allocation_interval > 0) {
1868
1888
  tp_newobj = rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_NEWOBJ, newobj_i, this);
1869
1889
  rb_tracepoint_enable(tp_newobj);
1870
1890
  }
@@ -1969,12 +1989,20 @@ collector_free(void *data) {
1969
1989
  delete collector;
1970
1990
  }
1971
1991
 
1992
+ static void
1993
+ collector_compact(void *data) {
1994
+ BaseCollector *collector = static_cast<BaseCollector *>(data);
1995
+ collector->compact();
1996
+ }
1997
+
1972
1998
  static const rb_data_type_t rb_collector_type = {
1973
1999
  .wrap_struct_name = "vernier/collector",
1974
2000
  .function = {
1975
2001
  //.dmemsize = rb_collector_memsize,
1976
2002
  .dmark = collector_mark,
1977
2003
  .dfree = collector_free,
2004
+ .dsize = NULL,
2005
+ .dcompact = collector_compact,
1978
2006
  },
1979
2007
  };
1980
2008
 
@@ -1989,7 +2017,7 @@ collector_start(VALUE self) {
1989
2017
  auto *collector = get_collector(self);
1990
2018
 
1991
2019
  if (!collector->start()) {
1992
- rb_raise(rb_eRuntimeError, "already running");
2020
+ rb_raise(rb_eRuntimeError, "collector already running");
1993
2021
  }
1994
2022
 
1995
2023
  return Qtrue;
@@ -2042,14 +2070,17 @@ static VALUE collector_new(VALUE self, VALUE mode, VALUE options) {
2042
2070
  interval = TimeStamp::from_microseconds(NUM2UINT(intervalv));
2043
2071
  }
2044
2072
 
2045
- VALUE allocation_sample_ratev = rb_hash_aref(options, sym("allocation_sample_rate"));
2046
- unsigned int allocation_sample_rate;
2047
- if (NIL_P(allocation_sample_ratev)) {
2048
- allocation_sample_rate = 0;
2073
+ VALUE allocation_intervalv = rb_hash_aref(options, sym("allocation_interval"));
2074
+ if (NIL_P(allocation_intervalv))
2075
+ allocation_intervalv = rb_hash_aref(options, sym("allocation_sample_rate"));
2076
+
2077
+ unsigned int allocation_interval;
2078
+ if (NIL_P(allocation_intervalv)) {
2079
+ allocation_interval = 0;
2049
2080
  } else {
2050
- allocation_sample_rate = NUM2UINT(allocation_sample_ratev);
2081
+ allocation_interval = NUM2UINT(allocation_intervalv);
2051
2082
  }
2052
- collector = new TimeCollector(stack_table, interval, allocation_sample_rate);
2083
+ collector = new TimeCollector(stack_table, interval, allocation_interval);
2053
2084
  } else {
2054
2085
  rb_raise(rb_eArgError, "invalid mode");
2055
2086
  }
@@ -17,12 +17,12 @@ module Vernier
17
17
 
18
18
  def self.start
19
19
  interval = options.fetch(:interval, 500).to_i
20
- allocation_sample_rate = options.fetch(:allocation_sample_rate, 0).to_i
20
+ allocation_interval = options.fetch(:allocation_interval, 0).to_i
21
21
  hooks = options.fetch(:hooks, "").split(",")
22
22
 
23
- STDERR.puts("starting profiler with interval #{interval}")
23
+ STDERR.puts("starting profiler with interval #{interval} and allocation interval #{allocation_interval}")
24
24
 
25
- @collector = Vernier::Collector.new(:wall, interval:, allocation_sample_rate:, hooks:)
25
+ @collector = Vernier::Collector.new(:wall, interval:, allocation_interval:, hooks:)
26
26
  @collector.start
27
27
  end
28
28
 
@@ -32,7 +32,7 @@ module Vernier
32
32
  when :rails, :activesupport
33
33
  @hooks << Vernier::Hooks::ActiveSupport.new(self)
34
34
  else
35
- warn "Unknown hook: #{hook.inspect}"
35
+ warn "unknown hook: #{hook.inspect}"
36
36
  end
37
37
  end
38
38
 
@@ -13,9 +13,9 @@ module Vernier
13
13
  return @app.call(env) unless permitted
14
14
 
15
15
  interval = request.GET.fetch("vernier_interval", 200).to_i
16
- allocation_sample_rate = request.GET.fetch("vernier_allocation_sample_rate", 200).to_i
16
+ allocation_interval = request.GET.fetch("vernier_allocation_interval", 200).to_i
17
17
 
18
- result = Vernier.trace(interval:, allocation_sample_rate:, hooks: [:rails]) do
18
+ result = Vernier.trace(interval:, allocation_interval:, hooks: [:rails]) do
19
19
  @app.call(env)
20
20
  end
21
21
  body = result.to_gecko(gzip: true)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Vernier
4
- VERSION = "1.2.1"
4
+ VERSION = "1.3.1"
5
5
  end
data/lib/vernier.rb CHANGED
@@ -40,7 +40,7 @@ module Vernier
40
40
  @collector.stop
41
41
  @collector = nil
42
42
 
43
- raise "Profile already started, stopping..."
43
+ raise "profile already started, stopping..."
44
44
  end
45
45
 
46
46
  @collector = Vernier::Collector.new(mode, collector_options)
@@ -48,7 +48,7 @@ module Vernier
48
48
  end
49
49
 
50
50
  def self.stop_profile
51
- raise "No profile started" unless @collector
51
+ raise "profile not started" unless @collector
52
52
 
53
53
  result = @collector.stop
54
54
  @collector = nil
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.2.1
4
+ version: 1.3.1
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-09-11 00:00:00.000000000 Z
11
+ date: 2024-11-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport