vernier 0.1.0 → 0.2.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 +19 -5
- data/Rakefile +4 -0
- data/examples/threaded_http_requests.rb +38 -0
- data/ext/vernier/extconf.rb +1 -1
- data/ext/vernier/ruby_type_names.h +44 -0
- data/ext/vernier/stack.hh +106 -29
- data/ext/vernier/vernier.cc +1150 -114
- data/lib/vernier/collector.rb +52 -0
- data/lib/vernier/marker.rb +38 -0
- data/lib/vernier/output/firefox.rb +365 -0
- data/lib/vernier/output/top.rb +30 -0
- data/lib/vernier/version.rb +1 -1
- data/lib/vernier.rb +167 -5
- data/vernier.gemspec +1 -1
- metadata +10 -5
- data/Gemfile.lock +0 -24
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: da6d8fafb453c25b5f1127a33c53401150cb91bf604a37db971c952ec490d900
|
4
|
+
data.tar.gz: 97cc5b3435335fe97c531122d5692a3af3264c5abeb34eafb1a1808743b017fe
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 92529d7331c8a015c900227f1d28b093322dfb386ce99213c4a22dea168d433f9e0d9ae221c3171ea7e3c91fd67753a9da668140af97f62e0edec08341cb4f63
|
7
|
+
data.tar.gz: bdd0baaf24abdbed97f603da42e827fa10442f4f702f15a5ee2f01e2b25eb893465d8b313bcd3dec84a402d76554aa58d28547e2bad401db2c4dbb24797a2414
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Vernier
|
2
2
|
|
3
|
-
Experimental Ruby
|
3
|
+
Experimental next-generation Ruby profiler.
|
4
4
|
|
5
5
|
## Installation
|
6
6
|
|
@@ -10,16 +10,23 @@ gem 'vernier'
|
|
10
10
|
|
11
11
|
## Usage
|
12
12
|
|
13
|
-
|
13
|
+
### Retained memory
|
14
|
+
|
15
|
+
Record a flamegraph of all **retained** allocations from loading `irb`.
|
14
16
|
|
15
17
|
```
|
16
|
-
ruby -r vernier -e 'Vernier.trace_retained(out: "
|
18
|
+
ruby -r vernier -e 'Vernier.trace_retained(out: "irb_profile.json") { require "irb" }'
|
17
19
|
```
|
18
20
|
|
19
|
-
The output can then be viewed in
|
21
|
+
The output can then be viewed in the [Firefox Profiler (demo)](https://share.firefox.dev/3DhLsFa)
|
22
|
+
|
23
|
+
![Screenshot 2023-07-16 at 21-06-19 Ruby_Vernier – 1970-01-01 12 00 00 a m UTC – Firefox Profiler](https://github.com/jhawthorn/vernier/assets/131752/9ca0b593-70fb-4c8b-aed9-cb33e0e0bc06)
|
20
24
|
|
21
|
-
|
25
|
+
### Time
|
22
26
|
|
27
|
+
```
|
28
|
+
Vernier.trace(out: "time_profile.json") { some_slow_method }
|
29
|
+
```
|
23
30
|
|
24
31
|
## Development
|
25
32
|
|
@@ -31,6 +38,13 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
31
38
|
|
32
39
|
Bug reports and pull requests are welcome on GitHub at https://github.com/jhawthorn/vernier. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/jhawthorn/vernier/blob/main/CODE_OF_CONDUCT.md).
|
33
40
|
|
41
|
+
### Resources
|
42
|
+
|
43
|
+
* https://profiler.firefox.com/docs/#/
|
44
|
+
* https://github.com/firefox-devtools/profiler/tree/main/docs-developer
|
45
|
+
* https://github.com/tmm1/stackprof
|
46
|
+
* https://github.com/ruby/ruby/pull/5500
|
47
|
+
|
34
48
|
## License
|
35
49
|
|
36
50
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
CHANGED
@@ -0,0 +1,38 @@
|
|
1
|
+
require "vernier"
|
2
|
+
|
3
|
+
Vernier.trace(out: "http_requests.json") do
|
4
|
+
|
5
|
+
require "net/http"
|
6
|
+
require "uri"
|
7
|
+
require "openssl"
|
8
|
+
|
9
|
+
urls = Queue.new
|
10
|
+
received = Queue.new
|
11
|
+
|
12
|
+
threads = 2.times.map do
|
13
|
+
Thread.new do
|
14
|
+
while url = urls.pop
|
15
|
+
uri = URI.parse(url)
|
16
|
+
response = Net::HTTP.get_response(uri)
|
17
|
+
|
18
|
+
received << [url, response.code]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
Thread.new do
|
24
|
+
threads.each(&:join)
|
25
|
+
received.close
|
26
|
+
end
|
27
|
+
|
28
|
+
urls << "http://example.com"
|
29
|
+
urls << "https://www.johnhawthorn.com"
|
30
|
+
urls << "https://tenderlovemaking.com/"
|
31
|
+
urls.close
|
32
|
+
|
33
|
+
while x = received.pop
|
34
|
+
url, code = *x
|
35
|
+
puts "#{url} #{code}"
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
data/ext/vernier/extconf.rb
CHANGED
@@ -0,0 +1,44 @@
|
|
1
|
+
#pragma once
|
2
|
+
|
3
|
+
inline const char *
|
4
|
+
ruby_object_type_name(VALUE obj) {
|
5
|
+
enum ruby_value_type type = rb_type(obj);
|
6
|
+
|
7
|
+
#define TYPE_CASE(x) case (x): return (#x)
|
8
|
+
|
9
|
+
// Many of these are impossible, but it's easier to just include them
|
10
|
+
switch (type) {
|
11
|
+
TYPE_CASE(T_OBJECT);
|
12
|
+
TYPE_CASE(T_CLASS);
|
13
|
+
TYPE_CASE(T_MODULE);
|
14
|
+
TYPE_CASE(T_FLOAT);
|
15
|
+
TYPE_CASE(T_STRING);
|
16
|
+
TYPE_CASE(T_REGEXP);
|
17
|
+
TYPE_CASE(T_ARRAY);
|
18
|
+
TYPE_CASE(T_HASH);
|
19
|
+
TYPE_CASE(T_STRUCT);
|
20
|
+
TYPE_CASE(T_BIGNUM);
|
21
|
+
TYPE_CASE(T_FILE);
|
22
|
+
TYPE_CASE(T_DATA);
|
23
|
+
TYPE_CASE(T_MATCH);
|
24
|
+
TYPE_CASE(T_COMPLEX);
|
25
|
+
TYPE_CASE(T_RATIONAL);
|
26
|
+
|
27
|
+
TYPE_CASE(T_NIL);
|
28
|
+
TYPE_CASE(T_TRUE);
|
29
|
+
TYPE_CASE(T_FALSE);
|
30
|
+
TYPE_CASE(T_SYMBOL);
|
31
|
+
TYPE_CASE(T_FIXNUM);
|
32
|
+
TYPE_CASE(T_UNDEF);
|
33
|
+
|
34
|
+
TYPE_CASE(T_IMEMO);
|
35
|
+
TYPE_CASE(T_NODE);
|
36
|
+
TYPE_CASE(T_ICLASS);
|
37
|
+
TYPE_CASE(T_ZOMBIE);
|
38
|
+
TYPE_CASE(T_MOVED);
|
39
|
+
|
40
|
+
default:
|
41
|
+
return "unknown type";
|
42
|
+
}
|
43
|
+
#undef TYPE_CASE
|
44
|
+
}
|
data/ext/vernier/stack.hh
CHANGED
@@ -7,33 +7,87 @@
|
|
7
7
|
#include <memory>
|
8
8
|
#include <algorithm>
|
9
9
|
|
10
|
-
struct
|
11
|
-
VALUE frame
|
12
|
-
|
10
|
+
struct FrameInfo {
|
11
|
+
static const char *label_cstr(VALUE frame) {
|
12
|
+
VALUE label = rb_profile_frame_full_label(frame);
|
13
|
+
return StringValueCStr(label);
|
14
|
+
}
|
13
15
|
|
14
|
-
VALUE
|
15
|
-
|
16
|
+
static const char *file_cstr(VALUE frame) {
|
17
|
+
VALUE file = rb_profile_frame_absolute_path(frame);
|
18
|
+
if (NIL_P(file))
|
19
|
+
file = rb_profile_frame_path(frame);
|
20
|
+
if (NIL_P(file)) {
|
21
|
+
return "";
|
22
|
+
} else {
|
23
|
+
return StringValueCStr(file);
|
24
|
+
}
|
16
25
|
}
|
17
26
|
|
18
|
-
VALUE
|
19
|
-
|
27
|
+
static int first_lineno_int(VALUE frame) {
|
28
|
+
VALUE first_lineno = rb_profile_frame_first_lineno(frame);
|
29
|
+
return NIL_P(first_lineno) ? 0 : FIX2INT(first_lineno);
|
20
30
|
}
|
21
31
|
|
22
|
-
VALUE
|
23
|
-
|
32
|
+
FrameInfo(VALUE frame) :
|
33
|
+
label(label_cstr(frame)),
|
34
|
+
file(file_cstr(frame)),
|
35
|
+
first_lineno(first_lineno_int(frame)) { }
|
36
|
+
|
37
|
+
std::string label;
|
38
|
+
std::string file;
|
39
|
+
int first_lineno;
|
40
|
+
};
|
41
|
+
|
42
|
+
bool operator==(const FrameInfo& lhs, const FrameInfo& rhs) noexcept {
|
43
|
+
return
|
44
|
+
lhs.label == rhs.label &&
|
45
|
+
lhs.file == rhs.file &&
|
46
|
+
lhs.first_lineno == rhs.first_lineno;
|
47
|
+
}
|
48
|
+
|
49
|
+
template<>
|
50
|
+
struct std::hash<FrameInfo>
|
51
|
+
{
|
52
|
+
std::size_t operator()(FrameInfo const& f) const noexcept
|
53
|
+
{
|
54
|
+
return
|
55
|
+
std::hash<std::string>{}(f.label) ^
|
56
|
+
std::hash<std::string>{}(f.file) ^
|
57
|
+
f.first_lineno;
|
24
58
|
}
|
59
|
+
};
|
25
60
|
|
26
|
-
|
27
|
-
|
28
|
-
|
61
|
+
|
62
|
+
struct Frame {
|
63
|
+
VALUE frame;
|
64
|
+
int line;
|
65
|
+
|
66
|
+
FrameInfo info() const {
|
67
|
+
return FrameInfo(frame);
|
29
68
|
}
|
69
|
+
};
|
30
70
|
|
31
|
-
|
32
|
-
|
71
|
+
bool operator==(const Frame& lhs, const Frame& rhs) noexcept {
|
72
|
+
return lhs.frame == rhs.frame && lhs.line == rhs.line;
|
73
|
+
}
|
74
|
+
|
75
|
+
template<>
|
76
|
+
struct std::hash<Frame>
|
77
|
+
{
|
78
|
+
std::size_t operator()(Frame const& s) const noexcept
|
79
|
+
{
|
80
|
+
return s.frame ^ s.line;
|
33
81
|
}
|
34
82
|
};
|
35
83
|
|
36
|
-
struct
|
84
|
+
struct BaseStack {
|
85
|
+
virtual ~BaseStack() {};
|
86
|
+
|
87
|
+
virtual int size() const = 0;
|
88
|
+
};
|
89
|
+
|
90
|
+
struct Stack : public BaseStack {
|
37
91
|
std::unique_ptr<VALUE[]> frames;
|
38
92
|
std::unique_ptr<int[]> lines;
|
39
93
|
int _size = 0;
|
@@ -51,28 +105,51 @@ struct Stack {
|
|
51
105
|
std::copy_n(_lines, size, &lines[0]);
|
52
106
|
}
|
53
107
|
|
108
|
+
Stack(const Stack &s) :
|
109
|
+
_size(s.size()),
|
110
|
+
frames(std::make_unique<VALUE[]>(s.size())),
|
111
|
+
lines(std::make_unique<int[]>(s.size()))
|
112
|
+
{
|
113
|
+
std::copy_n(&s.frames[0], s.size(), &frames[0]);
|
114
|
+
std::copy_n(&s.lines[0], s.size(), &lines[0]);
|
115
|
+
}
|
116
|
+
|
54
117
|
Frame frame(int i) const {
|
55
118
|
if (i >= size()) throw std::out_of_range("nope");
|
56
119
|
return Frame{frames[i], lines[i]};
|
57
120
|
}
|
58
121
|
};
|
59
122
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
const char *file_cstr = NIL_P(file) ? "" : StringValueCStr(file);
|
65
|
-
os << file_cstr << ":" << frame.line << ":in `" << StringValueCStr(label) << "'";
|
66
|
-
return os;
|
123
|
+
bool operator==(const Stack& lhs, const Stack& rhs) noexcept {
|
124
|
+
return lhs.size() == rhs.size() &&
|
125
|
+
std::equal(&lhs.frames[0], &lhs.frames[lhs.size()], &rhs.frames[0]) &&
|
126
|
+
std::equal(&lhs.lines[0], &lhs.lines[lhs.size()], &rhs.lines[0]);
|
67
127
|
}
|
68
128
|
|
69
|
-
|
129
|
+
// https://xoshiro.di.unimi.it/splitmix64.c
|
130
|
+
// https://nullprogram.com/blog/2018/07/31/
|
131
|
+
uint64_t
|
132
|
+
hash64(uint64_t x)
|
70
133
|
{
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
return
|
134
|
+
x ^= x >> 16;
|
135
|
+
x *= 0x7feb352dU;
|
136
|
+
x ^= x >> 15;
|
137
|
+
x *= 0x846ca68bU;
|
138
|
+
x ^= x >> 16;
|
139
|
+
return x;
|
77
140
|
}
|
78
141
|
|
142
|
+
template<>
|
143
|
+
struct std::hash<Stack>
|
144
|
+
{
|
145
|
+
std::size_t operator()(Stack const& s) const noexcept
|
146
|
+
{
|
147
|
+
size_t hash = 0;
|
148
|
+
for (int i = 0; i < s.size(); i++) {
|
149
|
+
VALUE frame = s.frames[i];
|
150
|
+
hash ^= frame;
|
151
|
+
hash = hash64(hash);
|
152
|
+
}
|
153
|
+
return hash;
|
154
|
+
}
|
155
|
+
};
|