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.
@@ -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