rblineprof 0.2.1 → 0.2.5
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.
- data/README.md +3 -0
- data/ext/rblineprof.c +191 -96
- data/rblineprof.gemspec +1 -1
- data/test.rb +34 -13
- metadata +4 -4
data/README.md
CHANGED
@@ -35,3 +35,6 @@
|
|
35
35
|
|
36
36
|
* [PLine](https://github.com/soba1104/PLine) line-profiler for ruby 1.9
|
37
37
|
* pure-ruby [LineProfiler](http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/18997?help-en) for ruby 1.6
|
38
|
+
* [method_profiler](https://github.com/change/method_profiler)
|
39
|
+
* [ruby-prof](https://github.com/rdp/ruby-prof)
|
40
|
+
* [perftools.rb](https://github.com/tmm1/perftools.rb)
|
data/ext/rblineprof.c
CHANGED
@@ -5,25 +5,69 @@
|
|
5
5
|
|
6
6
|
#include <ruby.h>
|
7
7
|
#include <node.h>
|
8
|
+
#include <env.h>
|
8
9
|
#include <intern.h>
|
9
10
|
#include <st.h>
|
10
11
|
#include <re.h>
|
11
12
|
|
13
|
+
typedef uint64_t prof_time_t;
|
14
|
+
|
12
15
|
static VALUE gc_hook;
|
13
16
|
|
14
|
-
|
17
|
+
/*
|
18
|
+
* Struct representing an individual line of
|
19
|
+
* Ruby source code
|
20
|
+
*/
|
21
|
+
typedef struct sourceline {
|
22
|
+
uint64_t calls; // total number of calls
|
23
|
+
prof_time_t total_time;
|
24
|
+
prof_time_t max_time;
|
25
|
+
} sourceline_t;
|
26
|
+
|
27
|
+
/*
|
28
|
+
* Struct representing a single Ruby file.
|
29
|
+
*/
|
30
|
+
typedef struct sourcefile {
|
15
31
|
char *filename;
|
16
|
-
uint64_t *lines;
|
17
32
|
long nlines;
|
33
|
+
sourceline_t *lines;
|
18
34
|
|
19
|
-
|
20
|
-
|
35
|
+
prof_time_t exclusive_start;
|
36
|
+
prof_time_t exclusive_time;
|
21
37
|
} sourcefile_t;
|
22
38
|
|
39
|
+
/*
|
40
|
+
* An individual stack frame used to track
|
41
|
+
* calls and returns from Ruby methods
|
42
|
+
*/
|
43
|
+
typedef struct stackframe {
|
44
|
+
// data emitted from Ruby to our profiler hook
|
45
|
+
rb_event_t event;
|
46
|
+
NODE *node;
|
47
|
+
VALUE self;
|
48
|
+
ID mid;
|
49
|
+
VALUE klass;
|
50
|
+
|
51
|
+
char *filename;
|
52
|
+
long line;
|
53
|
+
|
54
|
+
prof_time_t start;
|
55
|
+
sourcefile_t *srcfile;
|
56
|
+
} stackframe_t;
|
57
|
+
|
58
|
+
/*
|
59
|
+
* Static properties and rbineprof configuration
|
60
|
+
*/
|
23
61
|
static struct {
|
24
62
|
bool enabled;
|
25
|
-
|
26
|
-
|
63
|
+
|
64
|
+
// stack
|
65
|
+
#define MAX_STACK_DEPTH 32768
|
66
|
+
stackframe_t stack[MAX_STACK_DEPTH];
|
67
|
+
uint64_t stack_depth;
|
68
|
+
|
69
|
+
// current file
|
70
|
+
sourcefile_t *curr_srcfile;
|
27
71
|
|
28
72
|
// single file mode, store filename and line data directly
|
29
73
|
char *source_filename;
|
@@ -32,118 +76,168 @@ static struct {
|
|
32
76
|
// regex mode, store file data in hash table
|
33
77
|
VALUE source_regex;
|
34
78
|
st_table *files;
|
35
|
-
sourcefile_t *last_sourcefile;
|
36
79
|
}
|
37
80
|
rblineprof = {
|
38
81
|
.enabled = false,
|
39
|
-
.
|
40
|
-
.last_line = 0,
|
41
|
-
.source_filename = NULL,
|
42
|
-
.source_regex = Qfalse,
|
43
|
-
.files = NULL,
|
44
|
-
.last_sourcefile = NULL
|
82
|
+
.source_regex = Qfalse
|
45
83
|
};
|
46
84
|
|
47
|
-
static
|
85
|
+
static prof_time_t
|
48
86
|
timeofday_usec()
|
49
87
|
{
|
50
88
|
struct timeval tv;
|
51
89
|
gettimeofday(&tv, NULL);
|
52
|
-
return (
|
53
|
-
(
|
90
|
+
return (prof_time_t)tv.tv_sec*1e6 +
|
91
|
+
(prof_time_t)tv.tv_usec;
|
54
92
|
}
|
55
93
|
|
56
94
|
static inline void
|
57
|
-
|
95
|
+
stackframe_record(stackframe_t *frame, prof_time_t now)
|
58
96
|
{
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
if (sourcefile->last_line >= sourcefile->nlines) {
|
69
|
-
long prev_nlines = sourcefile->nlines;
|
70
|
-
sourcefile->nlines = sourcefile->last_line + 100;
|
97
|
+
sourcefile_t *srcfile = frame->srcfile;
|
98
|
+
long line = frame->line;
|
99
|
+
|
100
|
+
/* allocate space for per-line data the first time */
|
101
|
+
if (srcfile->lines == NULL) {
|
102
|
+
srcfile->nlines = line + 100;
|
103
|
+
srcfile->lines = ALLOC_N(sourceline_t, srcfile->nlines);
|
104
|
+
MEMZERO(srcfile->lines, sourceline_t, srcfile->nlines);
|
105
|
+
}
|
71
106
|
|
72
|
-
|
73
|
-
|
74
|
-
|
107
|
+
/* grow the per-line array if necessary */
|
108
|
+
if (line >= srcfile->nlines) {
|
109
|
+
long prev_nlines = srcfile->nlines;
|
110
|
+
srcfile->nlines = line + 100;
|
75
111
|
|
76
|
-
|
77
|
-
|
78
|
-
sourcefile->last_time = now;
|
112
|
+
REALLOC_N(srcfile->lines, sourceline_t, srcfile->nlines);
|
113
|
+
MEMZERO(srcfile->lines + prev_nlines, sourceline_t, srcfile->nlines - prev_nlines);
|
79
114
|
}
|
115
|
+
|
116
|
+
/* record the sample */
|
117
|
+
prof_time_t diff = now - frame->start;
|
118
|
+
sourceline_t *srcline = &(srcfile->lines[line]);
|
119
|
+
srcline->calls++;
|
120
|
+
srcline->total_time += diff;
|
121
|
+
if (diff > srcline->max_time)
|
122
|
+
srcline->max_time = diff;
|
80
123
|
}
|
81
124
|
|
82
|
-
static
|
83
|
-
|
125
|
+
static inline sourcefile_t*
|
126
|
+
sourcefile_lookup(char *filename)
|
84
127
|
{
|
85
|
-
sourcefile_t *
|
86
|
-
|
87
|
-
if (!node) return;
|
88
|
-
|
89
|
-
char *file = node->nd_file;
|
90
|
-
long line = nd_line(node);
|
91
|
-
|
92
|
-
if (!file) return;
|
93
|
-
if (line <= 0) return;
|
94
|
-
|
95
|
-
// skip duplicate events fast
|
96
|
-
if (file == rblineprof.last_file && line == rblineprof.last_line)
|
97
|
-
return;
|
98
|
-
rblineprof.last_file = file;
|
99
|
-
rblineprof.last_line = line;
|
128
|
+
sourcefile_t *srcfile = NULL;
|
100
129
|
|
101
130
|
if (rblineprof.source_filename) { // single file mode
|
102
|
-
if (rblineprof.source_filename ==
|
103
|
-
|
104
|
-
|
131
|
+
if (rblineprof.source_filename == filename) { // compare char*, not contents
|
132
|
+
srcfile = &rblineprof.file;
|
133
|
+
srcfile->filename = filename;
|
105
134
|
} else {
|
106
|
-
return;
|
135
|
+
return NULL;
|
107
136
|
}
|
108
137
|
|
109
138
|
} else { // regex mode
|
110
|
-
st_lookup(rblineprof.files, (st_data_t)
|
139
|
+
st_lookup(rblineprof.files, (st_data_t)filename, (st_data_t *)&srcfile);
|
111
140
|
|
112
|
-
if ((VALUE)
|
113
|
-
return;
|
141
|
+
if ((VALUE)srcfile == Qnil) // known negative match, skip
|
142
|
+
return NULL;
|
114
143
|
|
115
|
-
if (!
|
116
|
-
if (rb_reg_search(rblineprof.source_regex, rb_str_new2(
|
117
|
-
|
118
|
-
MEMZERO(
|
119
|
-
|
120
|
-
st_insert(rblineprof.files, (st_data_t)
|
144
|
+
if (!srcfile) { // unknown file, check against regex
|
145
|
+
if (rb_reg_search(rblineprof.source_regex, rb_str_new2(filename), 0, 0) >= 0) {
|
146
|
+
srcfile = ALLOC_N(sourcefile_t, 1);
|
147
|
+
MEMZERO(srcfile, sourcefile_t, 1);
|
148
|
+
srcfile->filename = strdup(filename);
|
149
|
+
st_insert(rblineprof.files, (st_data_t)srcfile->filename, (st_data_t)srcfile);
|
121
150
|
} else { // no match, insert Qnil to prevent regex next time
|
122
|
-
st_insert(rblineprof.files, (st_data_t)strdup(
|
123
|
-
return;
|
151
|
+
st_insert(rblineprof.files, (st_data_t)strdup(filename), (st_data_t)Qnil);
|
152
|
+
return NULL;
|
124
153
|
}
|
125
154
|
}
|
126
155
|
}
|
127
156
|
|
128
|
-
|
129
|
-
|
157
|
+
return srcfile;
|
158
|
+
}
|
130
159
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
160
|
+
static void
|
161
|
+
profiler_hook(rb_event_t event, NODE *node, VALUE self, ID mid, VALUE klass)
|
162
|
+
{
|
163
|
+
char *file;
|
164
|
+
long line;
|
165
|
+
stackframe_t *frame = NULL;
|
166
|
+
sourcefile_t *srcfile, *curr_srcfile;
|
167
|
+
prof_time_t now = timeofday_usec();
|
168
|
+
|
169
|
+
/* file profiler: when invoking a method in a new file, account elapsed
|
170
|
+
* time to the current file and start a new timer.
|
171
|
+
*/
|
172
|
+
if (!node) return;
|
136
173
|
|
137
|
-
|
138
|
-
|
174
|
+
file = node->nd_file;
|
175
|
+
line = nd_line(node);
|
176
|
+
if (!file) return;
|
177
|
+
if (line <= 0) return;
|
139
178
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
179
|
+
srcfile = sourcefile_lookup(file);
|
180
|
+
curr_srcfile = rblineprof.curr_srcfile;
|
181
|
+
|
182
|
+
if (curr_srcfile != srcfile) {
|
183
|
+
if (curr_srcfile)
|
184
|
+
curr_srcfile->exclusive_time += now - curr_srcfile->exclusive_start;
|
145
185
|
|
146
|
-
|
186
|
+
if (srcfile)
|
187
|
+
srcfile->exclusive_start = now;
|
188
|
+
|
189
|
+
rblineprof.curr_srcfile = srcfile;
|
190
|
+
}
|
191
|
+
|
192
|
+
/* line profiler: maintain a stack of CALL events with timestamps. for
|
193
|
+
* each corresponding RETURN, account elapsed time to the calling line.
|
194
|
+
*
|
195
|
+
* we use ruby_current_node here to get the caller's file/line info,
|
196
|
+
* (as opposed to node, which points to the callee method being invoked)
|
197
|
+
*/
|
198
|
+
NODE *caller_node = ruby_frame->node;
|
199
|
+
if (!caller_node) return;
|
200
|
+
|
201
|
+
file = caller_node->nd_file;
|
202
|
+
line = nd_line(caller_node);
|
203
|
+
if (!file) return;
|
204
|
+
if (line <= 0) return;
|
205
|
+
|
206
|
+
if (caller_node->nd_file != node->nd_file)
|
207
|
+
srcfile = sourcefile_lookup(file);
|
208
|
+
if (!srcfile) return; /* skip line profiling for this file */
|
209
|
+
|
210
|
+
switch (event) {
|
211
|
+
case RUBY_EVENT_CALL:
|
212
|
+
case RUBY_EVENT_C_CALL:
|
213
|
+
rblineprof.stack_depth++;
|
214
|
+
if (rblineprof.stack_depth > 0 && rblineprof.stack_depth < MAX_STACK_DEPTH) {
|
215
|
+
frame = &rblineprof.stack[rblineprof.stack_depth-1];
|
216
|
+
frame->event = event;
|
217
|
+
frame->node = node;
|
218
|
+
frame->self = self;
|
219
|
+
frame->mid = mid;
|
220
|
+
frame->klass = klass;
|
221
|
+
frame->line = line;
|
222
|
+
frame->start = now;
|
223
|
+
frame->srcfile = srcfile;
|
224
|
+
}
|
225
|
+
break;
|
226
|
+
|
227
|
+
case RUBY_EVENT_RETURN:
|
228
|
+
case RUBY_EVENT_C_RETURN:
|
229
|
+
do {
|
230
|
+
if (rblineprof.stack_depth > 0 && rblineprof.stack_depth < MAX_STACK_DEPTH)
|
231
|
+
frame = &rblineprof.stack[rblineprof.stack_depth-1];
|
232
|
+
else
|
233
|
+
frame = NULL;
|
234
|
+
rblineprof.stack_depth--;
|
235
|
+
} while (frame && frame->self != self && frame->mid != mid && frame->klass != klass);
|
236
|
+
|
237
|
+
if (frame)
|
238
|
+
stackframe_record(frame, now);
|
239
|
+
|
240
|
+
break;
|
147
241
|
}
|
148
242
|
}
|
149
243
|
|
@@ -165,17 +259,18 @@ cleanup_files(st_data_t key, st_data_t record, st_data_t arg)
|
|
165
259
|
static int
|
166
260
|
summarize_files(st_data_t key, st_data_t record, st_data_t arg)
|
167
261
|
{
|
168
|
-
sourcefile_t *
|
169
|
-
if (!
|
262
|
+
sourcefile_t *srcfile = (sourcefile_t*)record;
|
263
|
+
if (!srcfile || (VALUE)srcfile == Qnil) return ST_CONTINUE;
|
170
264
|
|
171
265
|
VALUE ret = (VALUE)arg;
|
172
266
|
VALUE ary = rb_ary_new();
|
173
267
|
long i;
|
174
268
|
|
175
|
-
|
176
|
-
|
269
|
+
rb_ary_store(ary, 0, ULL2NUM(srcfile->exclusive_time));
|
270
|
+
for (i=1; i<srcfile->nlines; i++)
|
271
|
+
rb_ary_store(ary, i, rb_ary_new3(2, ULL2NUM(srcfile->lines[i].total_time), ULL2NUM(srcfile->lines[i].calls)));
|
177
272
|
|
178
|
-
rb_hash_aset(ret, rb_str_new2(
|
273
|
+
rb_hash_aset(ret, rb_str_new2(srcfile->filename), ary);
|
179
274
|
|
180
275
|
return ST_CONTINUE;
|
181
276
|
}
|
@@ -208,9 +303,7 @@ lineprof(VALUE self, VALUE filename)
|
|
208
303
|
}
|
209
304
|
|
210
305
|
// reset state
|
211
|
-
rblineprof.
|
212
|
-
rblineprof.last_line = 0;
|
213
|
-
rblineprof.last_sourcefile = NULL;
|
306
|
+
rblineprof.curr_srcfile = NULL;
|
214
307
|
st_foreach(rblineprof.files, cleanup_files, 0);
|
215
308
|
if (rblineprof.file.lines) {
|
216
309
|
xfree(rblineprof.file.lines);
|
@@ -219,18 +312,18 @@ lineprof(VALUE self, VALUE filename)
|
|
219
312
|
}
|
220
313
|
|
221
314
|
rblineprof.enabled = true;
|
222
|
-
rb_add_event_hook(profiler_hook,
|
315
|
+
rb_add_event_hook(profiler_hook, RUBY_EVENT_CALL|RUBY_EVENT_RETURN|RUBY_EVENT_C_CALL|RUBY_EVENT_C_RETURN);
|
223
316
|
rb_ensure(rb_yield, Qnil, lineprof_ensure, self);
|
224
317
|
|
318
|
+
sourcefile_t *curr_srcfile = rblineprof.curr_srcfile;
|
319
|
+
if (curr_srcfile)
|
320
|
+
curr_srcfile->exclusive_time += timeofday_usec() - curr_srcfile->exclusive_start;
|
321
|
+
|
225
322
|
VALUE ret = rb_hash_new();
|
226
323
|
VALUE ary = Qnil;
|
227
324
|
|
228
325
|
if (rblineprof.source_filename) {
|
229
|
-
|
230
|
-
ary = rb_ary_new();
|
231
|
-
for (i=0; i<rblineprof.file.nlines; i++)
|
232
|
-
rb_ary_store(ary, i, ULL2NUM(rblineprof.file.lines[i]));
|
233
|
-
rb_hash_aset(ret, rb_str_new2(rblineprof.source_filename), ary);
|
326
|
+
summarize_files(Qnil, (st_data_t)&rblineprof.file, ret);
|
234
327
|
} else {
|
235
328
|
st_foreach(rblineprof.files, summarize_files, ret);
|
236
329
|
}
|
@@ -254,3 +347,5 @@ Init_rblineprof()
|
|
254
347
|
rblineprof.files = st_init_strtable();
|
255
348
|
rb_define_global_function("lineprof", lineprof, 1);
|
256
349
|
}
|
350
|
+
|
351
|
+
/* vim: ts=2,sw=2,expandtab */
|
data/rblineprof.gemspec
CHANGED
data/test.rb
CHANGED
@@ -1,27 +1,48 @@
|
|
1
1
|
$:.unshift 'ext'
|
2
2
|
require 'rblineprof'
|
3
3
|
|
4
|
-
|
4
|
+
class Obj
|
5
|
+
define_method(:inner_block) do
|
6
|
+
sleep 0.001
|
7
|
+
end
|
8
|
+
|
9
|
+
def another(options={})
|
10
|
+
sleep 0.001
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def inner
|
5
15
|
sleep 0.001
|
16
|
+
1*2*3
|
17
|
+
4*5*6
|
18
|
+
7*8*9*10*11*12*13*14*15
|
19
|
+
2**32
|
20
|
+
2**128
|
6
21
|
|
7
|
-
|
22
|
+
o = Obj.new
|
23
|
+
o.inner_block
|
24
|
+
o.another
|
25
|
+
end
|
8
26
|
|
9
|
-
|
10
|
-
|
11
|
-
4*5*6
|
12
|
-
7*8*9*10*11*12*13*14*15
|
13
|
-
2**32
|
14
|
-
2**128
|
27
|
+
def outer
|
28
|
+
sleep 0.001
|
15
29
|
|
30
|
+
100.times do
|
31
|
+
inner
|
16
32
|
end
|
33
|
+
|
34
|
+
inner
|
35
|
+
end
|
36
|
+
|
37
|
+
profile = lineprof(/./) do
|
38
|
+
outer
|
17
39
|
end
|
18
40
|
|
19
41
|
File.readlines(__FILE__).each_with_index do |line, num|
|
20
|
-
|
21
|
-
|
22
|
-
printf "% 8.1fms |
|
42
|
+
time, calls = profile[__FILE__][num+1]
|
43
|
+
if calls && calls > 0
|
44
|
+
printf "% 8.1fms (% 5d) | %s", time/1000.0, calls, line
|
23
45
|
else
|
24
|
-
|
25
|
-
printf " | %s", line
|
46
|
+
printf " | %s", line
|
26
47
|
end
|
27
48
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rblineprof
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 29
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 2
|
9
|
-
-
|
10
|
-
version: 0.2.
|
9
|
+
- 5
|
10
|
+
version: 0.2.5
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Aman Gupta
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date:
|
18
|
+
date: 2013-01-18 00:00:00 Z
|
19
19
|
dependencies: []
|
20
20
|
|
21
21
|
description: rblineprof shows you lines of code that are slow.
|