vernier 0.0.0 → 0.1.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/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
|