vernier 1.2.1 → 1.3.1

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