rblineprof 0.2.1 → 0.2.5
Sign up to get free protection for your applications and to get access to all the features.
- 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.
|