vernier 0.0.0 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +8 -11
- data/ext/vernier/extconf.rb +3 -0
- data/ext/vernier/stack.hh +78 -0
- data/ext/vernier/vernier.cc +188 -0
- data/ext/vernier/{vernier.h → vernier.hh} +0 -0
- data/lib/vernier/version.rb +1 -1
- data/lib/vernier.rb +11 -1
- metadata +5 -4
- data/ext/vernier/vernier.c +0 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 97eadc619870ae964e5bf61e925791d4399c0480775aadc5af31597535ae0374
|
4
|
+
data.tar.gz: 5327fd25cfac20752905f89126e8f6a4422e8cc90c134bab98a47b7132f7909d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f0fd5a598a03f41a0d5984d4652f3f85dece310ca83c015b554b85271cda220ca2535c198d4d62dad39532861ff38c858cb1048e46c29a43ce25c4fcb318ba7f
|
7
|
+
data.tar.gz: d7e0462723a56f1e7a1cef5c96f2a86958df9b129442defa7814c2b5dc8d2844a1507abf0ea486d7a0458257347e9cf243d6f369568258bbdde0fa374a9d7268
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,28 +1,25 @@
|
|
1
1
|
# Vernier
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
TODO: Delete this and the text above, and describe your gem
|
3
|
+
Experimental Ruby profiling tool
|
6
4
|
|
7
5
|
## Installation
|
8
6
|
|
9
|
-
Add this line to your application's Gemfile:
|
10
|
-
|
11
7
|
```ruby
|
12
8
|
gem 'vernier'
|
13
9
|
```
|
14
10
|
|
15
|
-
|
11
|
+
## Usage
|
16
12
|
|
17
|
-
|
13
|
+
Record a flamegraph of all **retained** allocations from requiring `irb`.
|
18
14
|
|
19
|
-
|
15
|
+
```
|
16
|
+
ruby -r vernier -e 'Vernier.trace_retained(out: "irb.stackcollapse") { require "irb" }'
|
17
|
+
```
|
20
18
|
|
21
|
-
|
19
|
+
The output can then be viewed in speedscope or other flamegraph tools
|
22
20
|
|
23
|
-
|
21
|
+
<img width="1082" alt="Screen Shot 2022-04-26 at 8 11 19 PM" src="https://user-images.githubusercontent.com/131752/165440422-3a11f5cc-3018-4455-8918-887c2afa6d6e.png">
|
24
22
|
|
25
|
-
TODO: Write usage instructions here
|
26
23
|
|
27
24
|
## Development
|
28
25
|
|
data/ext/vernier/extconf.rb
CHANGED
@@ -0,0 +1,78 @@
|
|
1
|
+
#pragma once
|
2
|
+
|
3
|
+
#include "ruby/debug.h"
|
4
|
+
|
5
|
+
#include <iostream>
|
6
|
+
#include <vector>
|
7
|
+
#include <memory>
|
8
|
+
#include <algorithm>
|
9
|
+
|
10
|
+
struct Frame {
|
11
|
+
VALUE frame;
|
12
|
+
int line;
|
13
|
+
|
14
|
+
VALUE full_label() const {
|
15
|
+
return rb_profile_frame_full_label(frame);
|
16
|
+
}
|
17
|
+
|
18
|
+
VALUE absolute_path() const {
|
19
|
+
return rb_profile_frame_absolute_path(frame);
|
20
|
+
}
|
21
|
+
|
22
|
+
VALUE path() const {
|
23
|
+
return rb_profile_frame_path(frame);
|
24
|
+
}
|
25
|
+
|
26
|
+
VALUE file() const {
|
27
|
+
VALUE file = absolute_path();
|
28
|
+
return NIL_P(file) ? path() : file;
|
29
|
+
}
|
30
|
+
|
31
|
+
VALUE first_lineno() const {
|
32
|
+
return rb_profile_frame_first_lineno(frame);
|
33
|
+
}
|
34
|
+
};
|
35
|
+
|
36
|
+
struct Stack {
|
37
|
+
std::unique_ptr<VALUE[]> frames;
|
38
|
+
std::unique_ptr<int[]> lines;
|
39
|
+
int _size = 0;
|
40
|
+
|
41
|
+
int size() const {
|
42
|
+
return _size;
|
43
|
+
}
|
44
|
+
|
45
|
+
Stack(const VALUE *_frames, const int *_lines, int size) :
|
46
|
+
_size(size),
|
47
|
+
frames(std::make_unique<VALUE[]>(size)),
|
48
|
+
lines(std::make_unique<int[]>(size))
|
49
|
+
{
|
50
|
+
std::copy_n(_frames, size, &frames[0]);
|
51
|
+
std::copy_n(_lines, size, &lines[0]);
|
52
|
+
}
|
53
|
+
|
54
|
+
Frame frame(int i) const {
|
55
|
+
if (i >= size()) throw std::out_of_range("nope");
|
56
|
+
return Frame{frames[i], lines[i]};
|
57
|
+
}
|
58
|
+
};
|
59
|
+
|
60
|
+
std::ostream& operator<<(std::ostream& os, const Frame& frame)
|
61
|
+
{
|
62
|
+
VALUE label = frame.full_label();
|
63
|
+
VALUE file = frame.absolute_path();
|
64
|
+
const char *file_cstr = NIL_P(file) ? "" : StringValueCStr(file);
|
65
|
+
os << file_cstr << ":" << frame.line << ":in `" << StringValueCStr(label) << "'";
|
66
|
+
return os;
|
67
|
+
}
|
68
|
+
|
69
|
+
std::ostream& operator<<(std::ostream& os, const Stack& stack)
|
70
|
+
{
|
71
|
+
for (int i = 0; i < stack.size(); i++) {
|
72
|
+
Frame frame = stack.frame(i);
|
73
|
+
os << frame << "\n";
|
74
|
+
}
|
75
|
+
|
76
|
+
return os;
|
77
|
+
}
|
78
|
+
|
@@ -0,0 +1,188 @@
|
|
1
|
+
#include <iostream>
|
2
|
+
#include <vector>
|
3
|
+
#include <memory>
|
4
|
+
#include <algorithm>
|
5
|
+
#include <sstream>
|
6
|
+
#include <unordered_map>
|
7
|
+
|
8
|
+
#include "vernier.hh"
|
9
|
+
#include "stack.hh"
|
10
|
+
#include "ruby/debug.h"
|
11
|
+
|
12
|
+
using namespace std;
|
13
|
+
|
14
|
+
#define numberof(array) ((int)(sizeof(array) / sizeof((array)[0])))
|
15
|
+
|
16
|
+
static VALUE rb_mVernier;
|
17
|
+
|
18
|
+
struct retained_collector {
|
19
|
+
int allocated_objects = 0;
|
20
|
+
int freed_objects = 0;
|
21
|
+
|
22
|
+
std::unordered_map<VALUE, std::unique_ptr<Stack>> object_frames;
|
23
|
+
};
|
24
|
+
|
25
|
+
struct TraceArg {
|
26
|
+
rb_trace_arg_t *tparg;
|
27
|
+
VALUE obj;
|
28
|
+
VALUE path;
|
29
|
+
VALUE line;
|
30
|
+
VALUE mid;
|
31
|
+
VALUE klass;
|
32
|
+
|
33
|
+
TraceArg(VALUE tpval) {
|
34
|
+
tparg = rb_tracearg_from_tracepoint(tpval);
|
35
|
+
obj = rb_tracearg_object(tparg);
|
36
|
+
path = rb_tracearg_path(tparg);
|
37
|
+
line = rb_tracearg_lineno(tparg);
|
38
|
+
mid = rb_tracearg_method_id(tparg);
|
39
|
+
klass = rb_tracearg_defined_class(tparg);
|
40
|
+
}
|
41
|
+
};
|
42
|
+
|
43
|
+
static retained_collector _collector;
|
44
|
+
|
45
|
+
static VALUE tp_newobj;
|
46
|
+
static VALUE tp_freeobj;
|
47
|
+
static void
|
48
|
+
newobj_i(VALUE tpval, void *data) {
|
49
|
+
retained_collector *collector = static_cast<retained_collector *>(data);
|
50
|
+
TraceArg tp(tpval);
|
51
|
+
collector->allocated_objects++;
|
52
|
+
|
53
|
+
VALUE frames_buffer[2048];
|
54
|
+
int lines_buffer[2048];
|
55
|
+
int n = rb_profile_frames(0, 2048, frames_buffer, lines_buffer);
|
56
|
+
|
57
|
+
collector->object_frames.emplace(
|
58
|
+
tp.obj,
|
59
|
+
make_unique<Stack>(frames_buffer, lines_buffer, n)
|
60
|
+
);
|
61
|
+
}
|
62
|
+
|
63
|
+
static void
|
64
|
+
freeobj_i(VALUE tpval, void *data) {
|
65
|
+
retained_collector *collector = static_cast<retained_collector *>(data);
|
66
|
+
TraceArg tp(tpval);
|
67
|
+
collector->freed_objects++;
|
68
|
+
|
69
|
+
collector->object_frames.erase(tp.obj);
|
70
|
+
}
|
71
|
+
|
72
|
+
|
73
|
+
static VALUE
|
74
|
+
trace_retained_start(VALUE self) {
|
75
|
+
retained_collector *collector = &_collector;
|
76
|
+
|
77
|
+
tp_newobj = rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_NEWOBJ, newobj_i, collector);
|
78
|
+
tp_freeobj = rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_FREEOBJ, freeobj_i, collector);
|
79
|
+
|
80
|
+
rb_tracepoint_enable(tp_newobj);
|
81
|
+
rb_tracepoint_enable(tp_freeobj);
|
82
|
+
|
83
|
+
return Qtrue;
|
84
|
+
}
|
85
|
+
|
86
|
+
#define sym(name) ID2SYM(rb_intern_const(name))
|
87
|
+
|
88
|
+
// HACK: This isn't public, but the objspace ext uses it
|
89
|
+
extern "C" size_t rb_obj_memsize_of(VALUE);
|
90
|
+
|
91
|
+
static const char *
|
92
|
+
ruby_object_type_name(VALUE obj) {
|
93
|
+
enum ruby_value_type type = rb_type(obj);
|
94
|
+
|
95
|
+
#define TYPE_CASE(x) case (x): return (#x)
|
96
|
+
|
97
|
+
// Many of these are impossible, but it's easier to just include them
|
98
|
+
switch (type) {
|
99
|
+
TYPE_CASE(T_OBJECT);
|
100
|
+
TYPE_CASE(T_CLASS);
|
101
|
+
TYPE_CASE(T_MODULE);
|
102
|
+
TYPE_CASE(T_FLOAT);
|
103
|
+
TYPE_CASE(T_STRING);
|
104
|
+
TYPE_CASE(T_REGEXP);
|
105
|
+
TYPE_CASE(T_ARRAY);
|
106
|
+
TYPE_CASE(T_HASH);
|
107
|
+
TYPE_CASE(T_STRUCT);
|
108
|
+
TYPE_CASE(T_BIGNUM);
|
109
|
+
TYPE_CASE(T_FILE);
|
110
|
+
TYPE_CASE(T_DATA);
|
111
|
+
TYPE_CASE(T_MATCH);
|
112
|
+
TYPE_CASE(T_COMPLEX);
|
113
|
+
TYPE_CASE(T_RATIONAL);
|
114
|
+
|
115
|
+
TYPE_CASE(T_NIL);
|
116
|
+
TYPE_CASE(T_TRUE);
|
117
|
+
TYPE_CASE(T_FALSE);
|
118
|
+
TYPE_CASE(T_SYMBOL);
|
119
|
+
TYPE_CASE(T_FIXNUM);
|
120
|
+
TYPE_CASE(T_UNDEF);
|
121
|
+
|
122
|
+
TYPE_CASE(T_IMEMO);
|
123
|
+
TYPE_CASE(T_NODE);
|
124
|
+
TYPE_CASE(T_ICLASS);
|
125
|
+
TYPE_CASE(T_ZOMBIE);
|
126
|
+
TYPE_CASE(T_MOVED);
|
127
|
+
|
128
|
+
default:
|
129
|
+
return "unknown type";
|
130
|
+
}
|
131
|
+
#undef TYPE_CASE
|
132
|
+
}
|
133
|
+
|
134
|
+
static VALUE
|
135
|
+
trace_retained_stop(VALUE self) {
|
136
|
+
rb_tracepoint_disable(tp_newobj);
|
137
|
+
rb_tracepoint_disable(tp_freeobj);
|
138
|
+
|
139
|
+
retained_collector *collector = &_collector;
|
140
|
+
|
141
|
+
std::stringstream ss;
|
142
|
+
|
143
|
+
for (auto& it: collector->object_frames) {
|
144
|
+
VALUE obj = it.first;
|
145
|
+
const Stack &stack = *it.second;
|
146
|
+
|
147
|
+
for (int i = stack.size() - 1; i >= 0; i--) {
|
148
|
+
const Frame &frame = stack.frame(i);
|
149
|
+
ss << frame;
|
150
|
+
if (i > 0) ss << ";";
|
151
|
+
}
|
152
|
+
ss << ";" << ruby_object_type_name(obj);
|
153
|
+
ss << " " << rb_obj_memsize_of(obj) << endl;
|
154
|
+
}
|
155
|
+
|
156
|
+
std::string s = ss.str();
|
157
|
+
VALUE str = rb_str_new(s.c_str(), s.size());
|
158
|
+
|
159
|
+
return str;
|
160
|
+
}
|
161
|
+
|
162
|
+
static void
|
163
|
+
retained_collector_mark(void *data) {
|
164
|
+
retained_collector *collector = static_cast<retained_collector *>(data);
|
165
|
+
|
166
|
+
// We don't mark the objects, but we MUST mark the frames, otherwise they
|
167
|
+
// can be garbage collected.
|
168
|
+
// This may lead to method entries being unnecessarily retained.
|
169
|
+
for (auto& it: collector->object_frames) {
|
170
|
+
const Stack &stack = *it.second;
|
171
|
+
|
172
|
+
for (int i = 0; i < stack.size(); i++) {
|
173
|
+
rb_gc_mark(stack.frames[i]);
|
174
|
+
}
|
175
|
+
}
|
176
|
+
}
|
177
|
+
|
178
|
+
extern "C" void
|
179
|
+
Init_vernier(void)
|
180
|
+
{
|
181
|
+
rb_mVernier = rb_define_module("Vernier");
|
182
|
+
|
183
|
+
rb_define_module_function(rb_mVernier, "trace_retained_start", trace_retained_start, 0);
|
184
|
+
rb_define_module_function(rb_mVernier, "trace_retained_stop", trace_retained_stop, 0);
|
185
|
+
|
186
|
+
static VALUE gc_hook = Data_Wrap_Struct(rb_cObject, retained_collector_mark, NULL, &_collector);
|
187
|
+
rb_global_variable(&gc_hook);
|
188
|
+
}
|
File without changes
|
data/lib/vernier/version.rb
CHANGED
data/lib/vernier.rb
CHANGED
@@ -5,5 +5,15 @@ require_relative "vernier/vernier"
|
|
5
5
|
|
6
6
|
module Vernier
|
7
7
|
class Error < StandardError; end
|
8
|
-
|
8
|
+
|
9
|
+
def self.trace_retained(out: nil, gc: true)
|
10
|
+
3.times { GC.start } if gc
|
11
|
+
Vernier.trace_retained_start
|
12
|
+
yield
|
13
|
+
3.times { GC.start } if gc
|
14
|
+
result = Vernier.trace_retained_stop
|
15
|
+
|
16
|
+
File.write(out, result) if out
|
17
|
+
result
|
18
|
+
end
|
9
19
|
end
|
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: 0.
|
4
|
+
version: 0.1.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: 2022-04-
|
11
|
+
date: 2022-04-27 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: An experimental profiler
|
14
14
|
email:
|
@@ -27,8 +27,9 @@ files:
|
|
27
27
|
- bin/console
|
28
28
|
- bin/setup
|
29
29
|
- ext/vernier/extconf.rb
|
30
|
-
- ext/vernier/
|
31
|
-
- ext/vernier/vernier.
|
30
|
+
- ext/vernier/stack.hh
|
31
|
+
- ext/vernier/vernier.cc
|
32
|
+
- ext/vernier/vernier.hh
|
32
33
|
- lib/vernier.rb
|
33
34
|
- lib/vernier/version.rb
|
34
35
|
- sig/vernier.rbs
|