vernier 1.2.1 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 +45 -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: 73ae72f3ced0043f71cc0fd1f8b5c829b57d6d4c7c39536a6564c8bcb654dfbd
|
4
|
+
data.tar.gz: c392fff41de6efe80ed825087ed92760493345aa04a18b288bae8d5afa166b88
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e8c95c63b4836d7a160003a7ad2be68c54d135ba34ad663246b563f4e2f5954351a0e8c1e47ee4392b27a980cba8d395b607b1093487305abbcb21dd5467d5f1
|
7
|
+
data.tar.gz: 3a516ab9a0f52dfeaeecf8332c92cd63e160c9e51478ac6e8f30b846e1319d16b5132a8e7a6007ca582c46f3d6bdd8d96a88b9c686fb208de97779646f070a38
|
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,19 @@ 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
|
+
.dcompact = collector_compact,
|
1978
2005
|
},
|
1979
2006
|
};
|
1980
2007
|
|
@@ -1989,7 +2016,7 @@ collector_start(VALUE self) {
|
|
1989
2016
|
auto *collector = get_collector(self);
|
1990
2017
|
|
1991
2018
|
if (!collector->start()) {
|
1992
|
-
rb_raise(rb_eRuntimeError, "already running");
|
2019
|
+
rb_raise(rb_eRuntimeError, "collector already running");
|
1993
2020
|
}
|
1994
2021
|
|
1995
2022
|
return Qtrue;
|
@@ -2042,14 +2069,17 @@ static VALUE collector_new(VALUE self, VALUE mode, VALUE options) {
|
|
2042
2069
|
interval = TimeStamp::from_microseconds(NUM2UINT(intervalv));
|
2043
2070
|
}
|
2044
2071
|
|
2045
|
-
VALUE
|
2046
|
-
|
2047
|
-
|
2048
|
-
|
2072
|
+
VALUE allocation_intervalv = rb_hash_aref(options, sym("allocation_interval"));
|
2073
|
+
if (NIL_P(allocation_intervalv))
|
2074
|
+
allocation_intervalv = rb_hash_aref(options, sym("allocation_sample_rate"));
|
2075
|
+
|
2076
|
+
unsigned int allocation_interval;
|
2077
|
+
if (NIL_P(allocation_intervalv)) {
|
2078
|
+
allocation_interval = 0;
|
2049
2079
|
} else {
|
2050
|
-
|
2080
|
+
allocation_interval = NUM2UINT(allocation_intervalv);
|
2051
2081
|
}
|
2052
|
-
collector = new TimeCollector(stack_table, interval,
|
2082
|
+
collector = new TimeCollector(stack_table, interval, allocation_interval);
|
2053
2083
|
} else {
|
2054
2084
|
rb_raise(rb_eArgError, "invalid mode");
|
2055
2085
|
}
|
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.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-
|
11
|
+
date: 2024-10-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|