vernier 1.2.0 → 1.3.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 +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 +50 -16
- 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
|
@@ -1114,7 +1114,11 @@ class Thread {
|
|
|
1114
1114
|
sample.sample();
|
|
1115
1115
|
|
|
1116
1116
|
int stack_idx = translator.translate(frame_list, sample);
|
|
1117
|
-
|
|
1117
|
+
if (stack_idx >= 0) {
|
|
1118
|
+
allocation_samples.record_sample(stack_idx, TimeStamp::Now(), 1);
|
|
1119
|
+
} else {
|
|
1120
|
+
// TODO: should we log an empty frame?
|
|
1121
|
+
}
|
|
1118
1122
|
}
|
|
1119
1123
|
|
|
1120
1124
|
void set_state(State new_state) {
|
|
@@ -1333,7 +1337,7 @@ class BaseCollector {
|
|
|
1333
1337
|
virtual void write_meta(VALUE meta, VALUE result) {
|
|
1334
1338
|
rb_hash_aset(meta, sym("started_at"), ULL2NUM(started_at.nanoseconds()));
|
|
1335
1339
|
rb_hash_aset(meta, sym("interval"), Qnil);
|
|
1336
|
-
rb_hash_aset(meta, sym("
|
|
1340
|
+
rb_hash_aset(meta, sym("allocation_interval"), Qnil);
|
|
1337
1341
|
|
|
1338
1342
|
}
|
|
1339
1343
|
|
|
@@ -1356,6 +1360,9 @@ class BaseCollector {
|
|
|
1356
1360
|
rb_gc_mark(stack_table_value);
|
|
1357
1361
|
};
|
|
1358
1362
|
|
|
1363
|
+
virtual void compact() {
|
|
1364
|
+
};
|
|
1365
|
+
|
|
1359
1366
|
virtual VALUE get_markers() {
|
|
1360
1367
|
return rb_ary_new();
|
|
1361
1368
|
};
|
|
@@ -1543,6 +1550,23 @@ class RetainedCollector : public BaseCollector {
|
|
|
1543
1550
|
rb_gc_mark(tp_newobj);
|
|
1544
1551
|
rb_gc_mark(tp_freeobj);
|
|
1545
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
|
+
}
|
|
1546
1570
|
};
|
|
1547
1571
|
|
|
1548
1572
|
class GlobalSignalHandler {
|
|
@@ -1623,8 +1647,8 @@ class TimeCollector : public BaseCollector {
|
|
|
1623
1647
|
SignalSafeSemaphore thread_stopped;
|
|
1624
1648
|
|
|
1625
1649
|
TimeStamp interval;
|
|
1626
|
-
unsigned int
|
|
1627
|
-
unsigned int
|
|
1650
|
+
unsigned int allocation_interval;
|
|
1651
|
+
unsigned int allocation_tick = 0;
|
|
1628
1652
|
|
|
1629
1653
|
VALUE tp_newobj = Qnil;
|
|
1630
1654
|
|
|
@@ -1637,14 +1661,14 @@ class TimeCollector : public BaseCollector {
|
|
|
1637
1661
|
}
|
|
1638
1662
|
|
|
1639
1663
|
public:
|
|
1640
|
-
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)) {
|
|
1641
1665
|
}
|
|
1642
1666
|
|
|
1643
1667
|
void record_newobj(VALUE obj) {
|
|
1644
|
-
if (++
|
|
1668
|
+
if (++allocation_tick < allocation_interval) {
|
|
1645
1669
|
return;
|
|
1646
1670
|
}
|
|
1647
|
-
|
|
1671
|
+
allocation_tick = 0;
|
|
1648
1672
|
|
|
1649
1673
|
VALUE current_thread = rb_thread_current();
|
|
1650
1674
|
threads.mutex.lock();
|
|
@@ -1662,7 +1686,7 @@ class TimeCollector : public BaseCollector {
|
|
|
1662
1686
|
void write_meta(VALUE meta, VALUE result) {
|
|
1663
1687
|
BaseCollector::write_meta(meta, result);
|
|
1664
1688
|
rb_hash_aset(meta, sym("interval"), ULL2NUM(interval.microseconds()));
|
|
1665
|
-
rb_hash_aset(meta, sym("
|
|
1689
|
+
rb_hash_aset(meta, sym("allocation_interval"), ULL2NUM(allocation_interval));
|
|
1666
1690
|
|
|
1667
1691
|
}
|
|
1668
1692
|
|
|
@@ -1860,7 +1884,7 @@ class TimeCollector : public BaseCollector {
|
|
|
1860
1884
|
this->threads.initial(thread);
|
|
1861
1885
|
}
|
|
1862
1886
|
|
|
1863
|
-
if (
|
|
1887
|
+
if (allocation_interval > 0) {
|
|
1864
1888
|
tp_newobj = rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_NEWOBJ, newobj_i, this);
|
|
1865
1889
|
rb_tracepoint_enable(tp_newobj);
|
|
1866
1890
|
}
|
|
@@ -1965,12 +1989,19 @@ collector_free(void *data) {
|
|
|
1965
1989
|
delete collector;
|
|
1966
1990
|
}
|
|
1967
1991
|
|
|
1992
|
+
static void
|
|
1993
|
+
collector_compact(void *data) {
|
|
1994
|
+
BaseCollector *collector = static_cast<BaseCollector *>(data);
|
|
1995
|
+
collector->compact();
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1968
1998
|
static const rb_data_type_t rb_collector_type = {
|
|
1969
1999
|
.wrap_struct_name = "vernier/collector",
|
|
1970
2000
|
.function = {
|
|
1971
2001
|
//.dmemsize = rb_collector_memsize,
|
|
1972
2002
|
.dmark = collector_mark,
|
|
1973
2003
|
.dfree = collector_free,
|
|
2004
|
+
.dcompact = collector_compact,
|
|
1974
2005
|
},
|
|
1975
2006
|
};
|
|
1976
2007
|
|
|
@@ -1985,7 +2016,7 @@ collector_start(VALUE self) {
|
|
|
1985
2016
|
auto *collector = get_collector(self);
|
|
1986
2017
|
|
|
1987
2018
|
if (!collector->start()) {
|
|
1988
|
-
rb_raise(rb_eRuntimeError, "already running");
|
|
2019
|
+
rb_raise(rb_eRuntimeError, "collector already running");
|
|
1989
2020
|
}
|
|
1990
2021
|
|
|
1991
2022
|
return Qtrue;
|
|
@@ -2038,14 +2069,17 @@ static VALUE collector_new(VALUE self, VALUE mode, VALUE options) {
|
|
|
2038
2069
|
interval = TimeStamp::from_microseconds(NUM2UINT(intervalv));
|
|
2039
2070
|
}
|
|
2040
2071
|
|
|
2041
|
-
VALUE
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
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;
|
|
2045
2079
|
} else {
|
|
2046
|
-
|
|
2080
|
+
allocation_interval = NUM2UINT(allocation_intervalv);
|
|
2047
2081
|
}
|
|
2048
|
-
collector = new TimeCollector(stack_table, interval,
|
|
2082
|
+
collector = new TimeCollector(stack_table, interval, allocation_interval);
|
|
2049
2083
|
} else {
|
|
2050
2084
|
rb_raise(rb_eArgError, "invalid mode");
|
|
2051
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
|