rblineprof 0.2.1 → 0.2.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. data/README.md +3 -0
  2. data/ext/rblineprof.c +191 -96
  3. data/rblineprof.gemspec +1 -1
  4. data/test.rb +34 -13
  5. 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
- typedef struct {
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
- uint64_t last_time;
20
- long last_line;
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
- char *last_file;
26
- long last_line;
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
- .last_file = NULL,
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 uint64_t
85
+ static prof_time_t
48
86
  timeofday_usec()
49
87
  {
50
88
  struct timeval tv;
51
89
  gettimeofday(&tv, NULL);
52
- return (uint64_t)tv.tv_sec*1e6 +
53
- (uint64_t)tv.tv_usec;
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
- sourcefile_record(sourcefile_t *sourcefile, uint64_t now)
95
+ stackframe_record(stackframe_t *frame, prof_time_t now)
58
96
  {
59
- if (sourcefile->last_time && sourcefile->last_line) {
60
- /* allocate space for per-line data the first time */
61
- if (sourcefile->lines == NULL) {
62
- sourcefile->nlines = sourcefile->last_line + 100;
63
- sourcefile->lines = ALLOC_N(uint64_t, sourcefile->nlines);
64
- MEMZERO(sourcefile->lines, uint64_t, sourcefile->nlines);
65
- }
66
-
67
- /* grow the per-line array if necessary */
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
- REALLOC_N(sourcefile->lines, uint64_t, sourcefile->nlines);
73
- MEMZERO(sourcefile->lines + prev_nlines, uint64_t, sourcefile->nlines - prev_nlines);
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
- /* record the sample */
77
- sourcefile->lines[sourcefile->last_line] += (now - sourcefile->last_time);
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 void
83
- profiler_hook(rb_event_t event, NODE *node, VALUE self, ID mid, VALUE klass)
125
+ static inline sourcefile_t*
126
+ sourcefile_lookup(char *filename)
84
127
  {
85
- sourcefile_t *sourcefile = NULL;
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 == file) {
103
- sourcefile = &rblineprof.file;
104
- sourcefile->filename = file;
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)file, (st_data_t *)&sourcefile);
139
+ st_lookup(rblineprof.files, (st_data_t)filename, (st_data_t *)&srcfile);
111
140
 
112
- if ((VALUE)sourcefile == Qnil) // known negative match, skip
113
- return;
141
+ if ((VALUE)srcfile == Qnil) // known negative match, skip
142
+ return NULL;
114
143
 
115
- if (!sourcefile) { // unknown file, check against regex
116
- if (rb_reg_search(rblineprof.source_regex, rb_str_new2(file), 0, 0) >= 0) {
117
- sourcefile = ALLOC_N(sourcefile_t, 1);
118
- MEMZERO(sourcefile, sourcefile_t, 1);
119
- sourcefile->filename = strdup(file);
120
- st_insert(rblineprof.files, (st_data_t)sourcefile->filename, (st_data_t)sourcefile);
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(file), (st_data_t)Qnil);
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
- if (sourcefile) {
129
- uint64_t now = timeofday_usec();
157
+ return srcfile;
158
+ }
130
159
 
131
- /* increment if the line in the current file changed */
132
- if (sourcefile->last_line != line) {
133
- sourcefile_record(sourcefile, now);
134
- }
135
- sourcefile->last_line = line;
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
- if (!sourcefile->last_time)
138
- sourcefile->last_time = now;
174
+ file = node->nd_file;
175
+ line = nd_line(node);
176
+ if (!file) return;
177
+ if (line <= 0) return;
139
178
 
140
- /* if we came from another file, increment there and reset */
141
- if (rblineprof.last_sourcefile && rblineprof.last_sourcefile != sourcefile) {
142
- sourcefile_record(rblineprof.last_sourcefile, now);
143
- rblineprof.last_sourcefile->last_line = 0;
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
- rblineprof.last_sourcefile = sourcefile;
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 *sourcefile = (sourcefile_t*)record;
169
- if (!sourcefile || (VALUE)sourcefile == Qnil) return ST_CONTINUE;
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
- for (i=0; i<sourcefile->nlines; i++)
176
- rb_ary_store(ary, i, ULL2NUM(sourcefile->lines[i]));
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(sourcefile->filename), ary);
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.last_file = NULL;
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, RUBY_EVENT_ALL);
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
- long i;
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
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'rblineprof'
3
- s.version = '0.2.1'
3
+ s.version = '0.2.5'
4
4
  s.homepage = 'http://github.com/tmm1/rblineprof'
5
5
 
6
6
  s.authors = 'Aman Gupta'
data/test.rb CHANGED
@@ -1,27 +1,48 @@
1
1
  $:.unshift 'ext'
2
2
  require 'rblineprof'
3
3
 
4
- profile = lineprof(/./) do
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
- 100.times do
22
+ o = Obj.new
23
+ o.inner_block
24
+ o.another
25
+ end
8
26
 
9
- sleep 0.001
10
- 1*2*3
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
- if (sample = profile[__FILE__][num+1]) > 0
21
- # printf "% 7d | %s", sample, line
22
- printf "% 8.1fms | %s", sample/1000.0, line
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
- # printf " | %s", line
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: 21
4
+ hash: 29
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 2
9
- - 1
10
- version: 0.2.1
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: 2012-09-24 00:00:00 Z
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.