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 +4 -4
- data/README.md +25 -13
- data/examples/measure_overhead.rb +2 -2
- data/examples/rails.rb +1 -1
- data/examples/threaded_http_requests.rb +1 -1
- data/exe/vernier +4 -4
- data/ext/vernier/vernier.cc +46 -15
- data/lib/vernier/autorun.rb +3 -3
- data/lib/vernier/collector.rb +1 -1
- data/lib/vernier/middleware.rb +2 -2
- data/lib/vernier/version.rb +1 -1
- data/lib/vernier.rb +2 -2
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e471649c05955e4b4411e87aed57ce900eca0d9c01750feca9df4c35a5de1541
|
4
|
+
data.tar.gz: e37b6d23c3178224d3de32d930bc27459dc49fb87172509222e03b1142ea524a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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,
|
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
|
-
|
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
|
-
`
|
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
|
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],
|
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
|
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('--
|
25
|
-
options[:
|
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, "
|
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
|
data/ext/vernier/vernier.cc
CHANGED
@@ -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("
|
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
|
1631
|
-
unsigned int
|
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
|
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 (++
|
1668
|
+
if (++allocation_tick < allocation_interval) {
|
1649
1669
|
return;
|
1650
1670
|
}
|
1651
|
-
|
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("
|
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 (
|
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
|
2046
|
-
|
2047
|
-
|
2048
|
-
|
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
|
-
|
2081
|
+
allocation_interval = NUM2UINT(allocation_intervalv);
|
2051
2082
|
}
|
2052
|
-
collector = new TimeCollector(stack_table, interval,
|
2083
|
+
collector = new TimeCollector(stack_table, interval, allocation_interval);
|
2053
2084
|
} else {
|
2054
2085
|
rb_raise(rb_eArgError, "invalid mode");
|
2055
2086
|
}
|
data/lib/vernier/autorun.rb
CHANGED
@@ -17,12 +17,12 @@ module Vernier
|
|
17
17
|
|
18
18
|
def self.start
|
19
19
|
interval = options.fetch(:interval, 500).to_i
|
20
|
-
|
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:,
|
25
|
+
@collector = Vernier::Collector.new(:wall, interval:, allocation_interval:, hooks:)
|
26
26
|
@collector.start
|
27
27
|
end
|
28
28
|
|
data/lib/vernier/collector.rb
CHANGED
data/lib/vernier/middleware.rb
CHANGED
@@ -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
|
-
|
16
|
+
allocation_interval = request.GET.fetch("vernier_allocation_interval", 200).to_i
|
17
17
|
|
18
|
-
result = Vernier.trace(interval:,
|
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)
|
data/lib/vernier/version.rb
CHANGED
data/lib/vernier.rb
CHANGED
@@ -40,7 +40,7 @@ module Vernier
|
|
40
40
|
@collector.stop
|
41
41
|
@collector = nil
|
42
42
|
|
43
|
-
raise "
|
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 "
|
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.
|
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-
|
11
|
+
date: 2024-11-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|