vernier 1.8.1 → 1.9.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/.ruby-version +1 -1
- data/Gemfile +1 -0
- data/README.md +65 -0
- data/examples/custom_hook.rb +37 -0
- data/ext/vernier/heap_tracker.cc +277 -0
- data/ext/vernier/memory.cc +1 -1
- data/ext/vernier/stack_table.cc +290 -0
- data/ext/vernier/stack_table.hh +314 -0
- data/ext/vernier/vernier.cc +67 -791
- data/ext/vernier/vernier.hh +7 -0
- data/lib/vernier/collector.rb +112 -2
- data/lib/vernier/heap_tracker.rb +47 -0
- data/lib/vernier/memory_leak_detector.rb +40 -0
- data/lib/vernier/result.rb +37 -22
- data/lib/vernier/stack_table_helpers.rb +24 -10
- data/lib/vernier/version.rb +1 -1
- data/lib/vernier.rb +2 -6
- data/vernier.gemspec +39 -0
- metadata +9 -2
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
#include "vernier.hh"
|
|
2
|
+
#include "stack_table.hh"
|
|
3
|
+
|
|
4
|
+
static VALUE rb_cStackTable;
|
|
5
|
+
|
|
6
|
+
static void
|
|
7
|
+
stack_table_mark(void *data) {
|
|
8
|
+
StackTable *stack_table = static_cast<StackTable *>(data);
|
|
9
|
+
stack_table->mark_frames();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
static void
|
|
13
|
+
stack_table_free(void *data) {
|
|
14
|
+
StackTable *stack_table = static_cast<StackTable *>(data);
|
|
15
|
+
delete stack_table;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
static const rb_data_type_t rb_stack_table_type = {
|
|
19
|
+
.wrap_struct_name = "vernier/stack_table",
|
|
20
|
+
.function = {
|
|
21
|
+
.dmark = stack_table_mark,
|
|
22
|
+
.dfree = stack_table_free,
|
|
23
|
+
.dsize = nullptr,
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
VALUE
|
|
28
|
+
StackTable::stack_table_new() {
|
|
29
|
+
StackTable *stack_table = new StackTable();
|
|
30
|
+
VALUE obj = TypedData_Wrap_Struct(rb_cStackTable, &rb_stack_table_type, stack_table);
|
|
31
|
+
return obj;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
StackTable *get_stack_table(VALUE obj) {
|
|
35
|
+
StackTable *stack_table;
|
|
36
|
+
TypedData_Get_Struct(obj, StackTable, &rb_stack_table_type, stack_table);
|
|
37
|
+
return stack_table;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
static VALUE
|
|
41
|
+
stack_table_current_stack(int argc, VALUE *argv, VALUE self) {
|
|
42
|
+
int offset;
|
|
43
|
+
VALUE offset_v;
|
|
44
|
+
|
|
45
|
+
rb_scan_args(argc, argv, "01", &offset_v);
|
|
46
|
+
if (argc > 0) {
|
|
47
|
+
offset = NUM2INT(offset_v) + 1;
|
|
48
|
+
} else {
|
|
49
|
+
offset = 1;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
StackTable *stack_table = get_stack_table(self);
|
|
53
|
+
RawSample stack;
|
|
54
|
+
stack.sample(offset);
|
|
55
|
+
int stack_index = stack_table->stack_index(stack);
|
|
56
|
+
return INT2NUM(stack_index);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
static VALUE
|
|
60
|
+
stack_table_stack_parent_idx(VALUE self, VALUE idxval) {
|
|
61
|
+
StackTable *stack_table = get_stack_table(self);
|
|
62
|
+
int idx = NUM2INT(idxval);
|
|
63
|
+
int parent_idx = stack_table->stack_parent(idx);
|
|
64
|
+
if (parent_idx < 0) {
|
|
65
|
+
return Qnil;
|
|
66
|
+
} else {
|
|
67
|
+
return INT2NUM(parent_idx);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
static VALUE
|
|
72
|
+
stack_table_stack_frame_idx(VALUE self, VALUE idxval) {
|
|
73
|
+
StackTable *stack_table = get_stack_table(self);
|
|
74
|
+
//stack_table->finalize();
|
|
75
|
+
int idx = NUM2INT(idxval);
|
|
76
|
+
int frame_idx = stack_table->stack_frame(idx);
|
|
77
|
+
return frame_idx < 0 ? Qnil : INT2NUM(frame_idx);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
VALUE
|
|
81
|
+
StackTable::stack_table_stack_count(VALUE self) {
|
|
82
|
+
StackTable *stack_table = get_stack_table(self);
|
|
83
|
+
int count;
|
|
84
|
+
{
|
|
85
|
+
const std::lock_guard<std::mutex> lock(stack_table->stack_mutex);
|
|
86
|
+
count = stack_table->stack_node_list.size();
|
|
87
|
+
}
|
|
88
|
+
return INT2NUM(count);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
VALUE
|
|
92
|
+
StackTable::stack_table_convert(VALUE self, VALUE original_tableval, VALUE original_idxval) {
|
|
93
|
+
StackTable *stack_table = get_stack_table(self);
|
|
94
|
+
StackTable *original_table = get_stack_table(original_tableval);
|
|
95
|
+
int original_idx = NUM2INT(original_idxval);
|
|
96
|
+
|
|
97
|
+
int original_size;
|
|
98
|
+
{
|
|
99
|
+
const std::lock_guard<std::mutex> lock(original_table->stack_mutex);
|
|
100
|
+
original_size = original_table->stack_node_list.size();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (original_idx >= original_size || original_idx < 0) {
|
|
104
|
+
rb_raise(rb_eRangeError, "index out of range");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
int result_idx;
|
|
108
|
+
{
|
|
109
|
+
const std::lock_guard<std::mutex> lock1(stack_table->stack_mutex);
|
|
110
|
+
const std::lock_guard<std::mutex> lock2(original_table->stack_mutex);
|
|
111
|
+
StackNode *node = stack_table->convert_stack(*original_table, original_idx);
|
|
112
|
+
result_idx = node->index;
|
|
113
|
+
}
|
|
114
|
+
return INT2NUM(result_idx);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
VALUE
|
|
118
|
+
StackTable::stack_table_frame_count(VALUE self) {
|
|
119
|
+
StackTable *stack_table = get_stack_table(self);
|
|
120
|
+
stack_table->finalize();
|
|
121
|
+
int count = stack_table->frame_map.size();
|
|
122
|
+
return INT2NUM(count);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
VALUE
|
|
126
|
+
StackTable::stack_table_func_count(VALUE self) {
|
|
127
|
+
StackTable *stack_table = get_stack_table(self);
|
|
128
|
+
stack_table->finalize();
|
|
129
|
+
int count = stack_table->func_map.size();
|
|
130
|
+
return INT2NUM(count);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
static VALUE
|
|
134
|
+
stack_table_finalize(VALUE self) {
|
|
135
|
+
StackTable *stack_table = get_stack_table(self);
|
|
136
|
+
stack_table->finalize();
|
|
137
|
+
return self;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
VALUE
|
|
141
|
+
StackTable::stack_table_frame_line_no(VALUE self, VALUE idxval) {
|
|
142
|
+
StackTable *stack_table = get_stack_table(self);
|
|
143
|
+
stack_table->finalize();
|
|
144
|
+
int idx = NUM2INT(idxval);
|
|
145
|
+
if (idx < 0 || idx >= stack_table->frame_map.size()) {
|
|
146
|
+
return Qnil;
|
|
147
|
+
} else {
|
|
148
|
+
const auto &frame = stack_table->frame_map[idx];
|
|
149
|
+
return INT2NUM(frame.line);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
VALUE
|
|
154
|
+
StackTable::stack_table_frame_func_idx(VALUE self, VALUE idxval) {
|
|
155
|
+
StackTable *stack_table = get_stack_table(self);
|
|
156
|
+
stack_table->finalize();
|
|
157
|
+
int idx = NUM2INT(idxval);
|
|
158
|
+
if (idx < 0 || idx >= stack_table->frame_map.size()) {
|
|
159
|
+
return Qnil;
|
|
160
|
+
} else {
|
|
161
|
+
const auto &frame = stack_table->frame_map[idx];
|
|
162
|
+
int func_idx = stack_table->func_map.index(frame.frame);
|
|
163
|
+
return INT2NUM(func_idx);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
VALUE
|
|
168
|
+
StackTable::stack_table_func_name(VALUE self, VALUE idxval) {
|
|
169
|
+
StackTable *stack_table = get_stack_table(self);
|
|
170
|
+
stack_table->finalize();
|
|
171
|
+
int idx = NUM2INT(idxval);
|
|
172
|
+
auto &table = stack_table->func_info_list;
|
|
173
|
+
if (idx < 0 || idx >= table.size()) {
|
|
174
|
+
return Qnil;
|
|
175
|
+
} else {
|
|
176
|
+
const auto &func_info = table[idx];
|
|
177
|
+
std::string label = func_info.full_label();
|
|
178
|
+
|
|
179
|
+
// Ruby constants are in an arbitrary (ASCII compatible) encoding and
|
|
180
|
+
// method names are in an arbitrary (ASCII compatible) encoding. These
|
|
181
|
+
// can be mixed in the same program.
|
|
182
|
+
//
|
|
183
|
+
// However, by this point we've lost the chain of what the correct
|
|
184
|
+
// encoding should be. Oops!
|
|
185
|
+
//
|
|
186
|
+
// Instead we'll just guess at UTF-8 which should satisfy most. It won't
|
|
187
|
+
// necessarily be valid but that can be scrubbed on the Ruby side.
|
|
188
|
+
//
|
|
189
|
+
// In the future we might keep class and method name separate for
|
|
190
|
+
// longer, preserve encodings, and defer formatting to the Ruby side.
|
|
191
|
+
return rb_enc_interned_str(label.c_str(), label.length(), rb_utf8_encoding());
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
VALUE
|
|
196
|
+
StackTable::stack_table_func_filename(VALUE self, VALUE idxval) {
|
|
197
|
+
StackTable *stack_table = get_stack_table(self);
|
|
198
|
+
stack_table->finalize();
|
|
199
|
+
int idx = NUM2INT(idxval);
|
|
200
|
+
auto &table = stack_table->func_info_list;
|
|
201
|
+
if (idx < 0 || idx >= table.size()) {
|
|
202
|
+
return Qnil;
|
|
203
|
+
} else {
|
|
204
|
+
const auto &func_info = table[idx];
|
|
205
|
+
std::string filename = func_info.absolute_path;
|
|
206
|
+
if (filename.empty()) filename = func_info.path;
|
|
207
|
+
|
|
208
|
+
// Technically filesystems are binary and then Ruby interprets that as
|
|
209
|
+
// default_external encoding. But to keep things simple for now we are
|
|
210
|
+
// going to assume UTF-8.
|
|
211
|
+
return rb_enc_interned_str(filename.c_str(), filename.length(), rb_utf8_encoding());
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
VALUE
|
|
216
|
+
StackTable::stack_table_func_path(VALUE self, VALUE idxval) {
|
|
217
|
+
StackTable *stack_table = get_stack_table(self);
|
|
218
|
+
stack_table->finalize();
|
|
219
|
+
int idx = NUM2INT(idxval);
|
|
220
|
+
auto &table = stack_table->func_info_list;
|
|
221
|
+
if (idx < 0 || idx >= table.size()) {
|
|
222
|
+
return Qnil;
|
|
223
|
+
} else {
|
|
224
|
+
const auto &func_info = table[idx];
|
|
225
|
+
std::string filename = func_info.path;
|
|
226
|
+
|
|
227
|
+
// Technically filesystems are binary and then Ruby interprets that as
|
|
228
|
+
// default_external encoding. But to keep things simple for now we are
|
|
229
|
+
// going to assume UTF-8.
|
|
230
|
+
return rb_enc_interned_str(filename.c_str(), filename.length(), rb_utf8_encoding());
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
VALUE
|
|
235
|
+
StackTable::stack_table_func_absolute_path(VALUE self, VALUE idxval) {
|
|
236
|
+
StackTable *stack_table = get_stack_table(self);
|
|
237
|
+
stack_table->finalize();
|
|
238
|
+
int idx = NUM2INT(idxval);
|
|
239
|
+
auto &table = stack_table->func_info_list;
|
|
240
|
+
if (idx < 0 || idx >= table.size()) {
|
|
241
|
+
return Qnil;
|
|
242
|
+
} else {
|
|
243
|
+
const auto &func_info = table[idx];
|
|
244
|
+
std::string filename = func_info.absolute_path;
|
|
245
|
+
|
|
246
|
+
// Technically filesystems are binary and then Ruby interprets that as
|
|
247
|
+
// default_external encoding. But to keep things simple for now we are
|
|
248
|
+
// going to assume UTF-8.
|
|
249
|
+
return rb_enc_interned_str(filename.c_str(), filename.length(), rb_utf8_encoding());
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
VALUE
|
|
254
|
+
StackTable::stack_table_func_first_lineno(VALUE self, VALUE idxval) {
|
|
255
|
+
StackTable *stack_table = get_stack_table(self);
|
|
256
|
+
stack_table->finalize();
|
|
257
|
+
int idx = NUM2INT(idxval);
|
|
258
|
+
auto &table = stack_table->func_info_list;
|
|
259
|
+
if (idx < 0 || idx >= table.size()) {
|
|
260
|
+
return Qnil;
|
|
261
|
+
} else {
|
|
262
|
+
const auto &func_info = table[idx];
|
|
263
|
+
return INT2NUM(func_info.first_lineno);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
VALUE stack_table_new(VALUE self) {
|
|
268
|
+
return StackTable::stack_table_new();
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
void Init_stack_table() {
|
|
272
|
+
rb_cStackTable = rb_define_class_under(rb_mVernier, "StackTable", rb_cObject);
|
|
273
|
+
rb_undef_alloc_func(rb_cStackTable);
|
|
274
|
+
rb_define_singleton_method(rb_cStackTable, "new", stack_table_new, 0);
|
|
275
|
+
rb_define_method(rb_cStackTable, "current_stack", stack_table_current_stack, -1);
|
|
276
|
+
rb_define_method(rb_cStackTable, "convert", StackTable::stack_table_convert, 2);
|
|
277
|
+
rb_define_method(rb_cStackTable, "stack_parent_idx", stack_table_stack_parent_idx, 1);
|
|
278
|
+
rb_define_method(rb_cStackTable, "stack_frame_idx", stack_table_stack_frame_idx, 1);
|
|
279
|
+
rb_define_method(rb_cStackTable, "frame_line_no", StackTable::stack_table_frame_line_no, 1);
|
|
280
|
+
rb_define_method(rb_cStackTable, "frame_func_idx", StackTable::stack_table_frame_func_idx, 1);
|
|
281
|
+
rb_define_method(rb_cStackTable, "func_name", StackTable::stack_table_func_name, 1);
|
|
282
|
+
rb_define_method(rb_cStackTable, "func_path", StackTable::stack_table_func_path, 1);
|
|
283
|
+
rb_define_method(rb_cStackTable, "func_absolute_path", StackTable::stack_table_func_absolute_path, 1);
|
|
284
|
+
rb_define_method(rb_cStackTable, "func_filename", StackTable::stack_table_func_filename, 1);
|
|
285
|
+
rb_define_method(rb_cStackTable, "func_first_lineno", StackTable::stack_table_func_first_lineno, 1);
|
|
286
|
+
rb_define_method(rb_cStackTable, "stack_count", StackTable::stack_table_stack_count, 0);
|
|
287
|
+
rb_define_method(rb_cStackTable, "frame_count", StackTable::stack_table_frame_count, 0);
|
|
288
|
+
rb_define_method(rb_cStackTable, "func_count", StackTable::stack_table_func_count, 0);
|
|
289
|
+
rb_define_method(rb_cStackTable, "finalize", stack_table_finalize, 0);
|
|
290
|
+
}
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
#ifndef STACK_TABLE_HH
|
|
2
|
+
#define STACK_TABLE_HH STACK_TABLE_HH
|
|
3
|
+
|
|
4
|
+
#include <mutex>
|
|
5
|
+
#include <stdexcept>
|
|
6
|
+
#include <string>
|
|
7
|
+
#include <unordered_map>
|
|
8
|
+
#include <vector>
|
|
9
|
+
|
|
10
|
+
#include "ruby/ruby.h"
|
|
11
|
+
#include "ruby/encoding.h"
|
|
12
|
+
#include "ruby/debug.h"
|
|
13
|
+
|
|
14
|
+
struct Frame {
|
|
15
|
+
VALUE frame;
|
|
16
|
+
int line;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
inline bool operator==(const Frame& lhs, const Frame& rhs) noexcept {
|
|
20
|
+
return lhs.frame == rhs.frame && lhs.line == rhs.line;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
inline bool operator!=(const Frame& lhs, const Frame& rhs) noexcept {
|
|
24
|
+
return !(lhs == rhs);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
namespace std {
|
|
28
|
+
template<>
|
|
29
|
+
struct hash<Frame>
|
|
30
|
+
{
|
|
31
|
+
std::size_t operator()(Frame const& s) const noexcept
|
|
32
|
+
{
|
|
33
|
+
return s.frame ^ s.line;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
class RawSample {
|
|
39
|
+
public:
|
|
40
|
+
|
|
41
|
+
constexpr static int MAX_LEN = 2048;
|
|
42
|
+
|
|
43
|
+
private:
|
|
44
|
+
|
|
45
|
+
VALUE frames[MAX_LEN];
|
|
46
|
+
int lines[MAX_LEN];
|
|
47
|
+
int len;
|
|
48
|
+
int offset;
|
|
49
|
+
bool gc;
|
|
50
|
+
|
|
51
|
+
public:
|
|
52
|
+
|
|
53
|
+
RawSample() : len(0), gc(false), offset(0) { }
|
|
54
|
+
|
|
55
|
+
int size() const {
|
|
56
|
+
return len - offset;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
Frame frame(int i) const {
|
|
60
|
+
int idx = len - i - 1;
|
|
61
|
+
if (idx < 0) throw std::out_of_range("VERNIER BUG: index out of range");
|
|
62
|
+
const Frame frame = {frames[idx], lines[idx]};
|
|
63
|
+
return frame;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
void sample(int offset = 0) {
|
|
67
|
+
clear();
|
|
68
|
+
|
|
69
|
+
if (!ruby_native_thread_p()) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (rb_during_gc()) {
|
|
74
|
+
gc = true;
|
|
75
|
+
} else {
|
|
76
|
+
len = rb_profile_frames(0, MAX_LEN, frames, lines);
|
|
77
|
+
this->offset = std::min(offset, len);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
void clear() {
|
|
82
|
+
len = 0;
|
|
83
|
+
offset = 0;
|
|
84
|
+
gc = false;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
bool empty() const {
|
|
88
|
+
return len <= offset;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
struct FuncInfo {
|
|
93
|
+
static int first_lineno_int(VALUE frame) {
|
|
94
|
+
VALUE first_lineno = rb_profile_frame_first_lineno(frame);
|
|
95
|
+
return NIL_P(first_lineno) ? 0 : FIX2INT(first_lineno);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
static std::string convert_rstring(VALUE rstring) {
|
|
99
|
+
if (NIL_P(rstring)) {
|
|
100
|
+
return "";
|
|
101
|
+
} else {
|
|
102
|
+
const char *cstring = StringValueCStr(rstring);
|
|
103
|
+
return cstring;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
std::string full_label() const {
|
|
108
|
+
std::string output;
|
|
109
|
+
if (!method_name.empty()) {
|
|
110
|
+
output.append(classpath);
|
|
111
|
+
output.append(is_singleton ? "." : "#");
|
|
112
|
+
output.append(method_name);
|
|
113
|
+
} else {
|
|
114
|
+
output.append(label);
|
|
115
|
+
}
|
|
116
|
+
return output;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
FuncInfo(VALUE frame) :
|
|
120
|
+
label(convert_rstring(rb_profile_frame_label(frame))),
|
|
121
|
+
base_label(convert_rstring(rb_profile_frame_base_label(frame))),
|
|
122
|
+
classpath(convert_rstring(rb_profile_frame_classpath(frame))),
|
|
123
|
+
absolute_path(convert_rstring(rb_profile_frame_absolute_path(frame))),
|
|
124
|
+
method_name(convert_rstring(rb_profile_frame_method_name(frame))),
|
|
125
|
+
path(convert_rstring(rb_profile_frame_path(frame))),
|
|
126
|
+
first_lineno(first_lineno_int(frame)),
|
|
127
|
+
is_singleton(RTEST(rb_profile_frame_singleton_method_p(frame)))
|
|
128
|
+
{ }
|
|
129
|
+
|
|
130
|
+
std::string label;
|
|
131
|
+
std::string base_label;
|
|
132
|
+
std::string classpath;
|
|
133
|
+
std::string path;
|
|
134
|
+
std::string absolute_path;
|
|
135
|
+
std::string method_name;
|
|
136
|
+
int first_lineno;
|
|
137
|
+
bool is_singleton;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
template <typename K>
|
|
141
|
+
class IndexMap {
|
|
142
|
+
public:
|
|
143
|
+
std::unordered_map<K, int> to_idx;
|
|
144
|
+
std::vector<K> list;
|
|
145
|
+
|
|
146
|
+
const K& operator[](int i) const noexcept {
|
|
147
|
+
return list[i];
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
size_t size() const noexcept {
|
|
151
|
+
return list.size();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
int index(const K key) {
|
|
155
|
+
auto it = to_idx.find(key);
|
|
156
|
+
if (it == to_idx.end()) {
|
|
157
|
+
int idx = list.size();
|
|
158
|
+
list.push_back(key);
|
|
159
|
+
|
|
160
|
+
auto result = to_idx.insert({key, idx});
|
|
161
|
+
it = result.first;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return it->second;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
void clear() {
|
|
168
|
+
list.clear();
|
|
169
|
+
to_idx.clear();
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
struct StackTable {
|
|
174
|
+
private:
|
|
175
|
+
|
|
176
|
+
IndexMap<Frame> frame_map;
|
|
177
|
+
|
|
178
|
+
IndexMap<VALUE> func_map;
|
|
179
|
+
std::vector<FuncInfo> func_info_list;
|
|
180
|
+
|
|
181
|
+
struct StackNode {
|
|
182
|
+
std::unordered_map<Frame, int> children;
|
|
183
|
+
Frame frame;
|
|
184
|
+
int parent;
|
|
185
|
+
int index;
|
|
186
|
+
|
|
187
|
+
StackNode(Frame frame, int index, int parent) : frame(frame), index(index), parent(parent) {}
|
|
188
|
+
|
|
189
|
+
// root
|
|
190
|
+
StackNode() : frame(Frame{0, 0}), index(-1), parent(-1) {}
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
// This mutex guards the StackNodes only. The rest of the maps and vectors
|
|
194
|
+
// should be guarded by the GVL
|
|
195
|
+
std::mutex stack_mutex;
|
|
196
|
+
|
|
197
|
+
StackNode root_stack_node;
|
|
198
|
+
std::vector<StackNode> stack_node_list;
|
|
199
|
+
int stack_node_list_finalized_idx = 0;
|
|
200
|
+
|
|
201
|
+
StackNode *next_stack_node(StackNode *node, Frame frame) {
|
|
202
|
+
auto search = node->children.find(frame);
|
|
203
|
+
if (search == node->children.end()) {
|
|
204
|
+
// insert a new node
|
|
205
|
+
int next_node_idx = stack_node_list.size();
|
|
206
|
+
node->children[frame] = next_node_idx;
|
|
207
|
+
stack_node_list.emplace_back(
|
|
208
|
+
frame,
|
|
209
|
+
next_node_idx,
|
|
210
|
+
node->index
|
|
211
|
+
);
|
|
212
|
+
return &stack_node_list[next_node_idx];
|
|
213
|
+
} else {
|
|
214
|
+
int node_idx = search->second;
|
|
215
|
+
return &stack_node_list[node_idx];
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
public:
|
|
220
|
+
|
|
221
|
+
int stack_index(const RawSample &stack) {
|
|
222
|
+
if (stack.empty()) {
|
|
223
|
+
throw std::runtime_error("VERNIER BUG: empty stack");
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const std::lock_guard<std::mutex> lock(stack_mutex);
|
|
227
|
+
|
|
228
|
+
StackNode *node = &root_stack_node;
|
|
229
|
+
for (int i = 0; i < stack.size(); i++) {
|
|
230
|
+
Frame frame = stack.frame(i);
|
|
231
|
+
node = next_stack_node(node, frame);
|
|
232
|
+
}
|
|
233
|
+
return node->index;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
int stack_parent(int stack_idx) {
|
|
237
|
+
const std::lock_guard<std::mutex> lock(stack_mutex);
|
|
238
|
+
if (stack_idx < 0 || stack_idx >= stack_node_list.size()) {
|
|
239
|
+
return -1;
|
|
240
|
+
} else {
|
|
241
|
+
return stack_node_list[stack_idx].parent;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
int stack_frame(int stack_idx) {
|
|
246
|
+
const std::lock_guard<std::mutex> lock(stack_mutex);
|
|
247
|
+
if (stack_idx < 0 || stack_idx >= stack_node_list.size()) {
|
|
248
|
+
return -1;
|
|
249
|
+
} else {
|
|
250
|
+
return frame_map.index(stack_node_list[stack_idx].frame);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Converts Frames from stacks other tables. "Symbolicates" the frames
|
|
255
|
+
// which allocates.
|
|
256
|
+
void finalize() {
|
|
257
|
+
{
|
|
258
|
+
const std::lock_guard<std::mutex> lock(stack_mutex);
|
|
259
|
+
for (int i = stack_node_list_finalized_idx; i < stack_node_list.size(); i++) {
|
|
260
|
+
const auto &stack_node = stack_node_list[i];
|
|
261
|
+
frame_map.index(stack_node.frame);
|
|
262
|
+
func_map.index(stack_node.frame.frame);
|
|
263
|
+
stack_node_list_finalized_idx = i;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
for (int i = func_info_list.size(); i < func_map.size(); i++) {
|
|
268
|
+
const auto &func = func_map[i];
|
|
269
|
+
// must not hold a mutex here
|
|
270
|
+
func_info_list.push_back(FuncInfo(func));
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
void mark_frames() {
|
|
275
|
+
const std::lock_guard<std::mutex> lock(stack_mutex);
|
|
276
|
+
|
|
277
|
+
for (auto stack_node: stack_node_list) {
|
|
278
|
+
rb_gc_mark(stack_node.frame.frame);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
StackNode *convert_stack(StackTable &other, int original_idx) {
|
|
283
|
+
if (original_idx < 0) {
|
|
284
|
+
return &root_stack_node;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
StackNode &original_node = other.stack_node_list[original_idx];
|
|
288
|
+
StackNode *parent_node = convert_stack(other, original_node.parent);
|
|
289
|
+
StackNode *node = next_stack_node(parent_node, original_node.frame);
|
|
290
|
+
|
|
291
|
+
return node;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
static VALUE stack_table_new();
|
|
295
|
+
static VALUE stack_table_convert(VALUE self, VALUE other, VALUE original_stack);
|
|
296
|
+
|
|
297
|
+
static VALUE stack_table_stack_count(VALUE self);
|
|
298
|
+
static VALUE stack_table_frame_count(VALUE self);
|
|
299
|
+
static VALUE stack_table_func_count(VALUE self);
|
|
300
|
+
|
|
301
|
+
static VALUE stack_table_frame_line_no(VALUE self, VALUE idxval);
|
|
302
|
+
static VALUE stack_table_frame_func_idx(VALUE self, VALUE idxval);
|
|
303
|
+
static VALUE stack_table_func_name(VALUE self, VALUE idxval);
|
|
304
|
+
static VALUE stack_table_func_filename(VALUE self, VALUE idxval);
|
|
305
|
+
static VALUE stack_table_func_path(VALUE self, VALUE idxval);
|
|
306
|
+
static VALUE stack_table_func_absolute_path(VALUE self, VALUE idxval);
|
|
307
|
+
static VALUE stack_table_func_first_lineno(VALUE self, VALUE idxval);
|
|
308
|
+
|
|
309
|
+
friend class SampleTranslator;
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
StackTable *get_stack_table(VALUE obj);
|
|
313
|
+
|
|
314
|
+
#endif
|