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