rblineprof 0.2.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.
- data/README.md +34 -0
- data/ext/.gitignore +3 -0
- data/ext/extconf.rb +2 -0
- data/ext/rblineprof.c +228 -0
- data/rblineprof.gemspec +14 -0
- data/test.rb +22 -0
- metadata +70 -0
data/README.md
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# rblineprof
|
2
|
+
|
3
|
+
```
|
4
|
+
% ruby -C ext extconf.rb
|
5
|
+
% make -C ext
|
6
|
+
% ruby test.rb
|
7
|
+
| $:.unshift 'ext'
|
8
|
+
| require 'rblineprof'
|
9
|
+
|
|
10
|
+
| profile = lineprof(/./) do
|
11
|
+
1 | 1000.times do
|
12
|
+
|
|
13
|
+
410 | 1*2*3
|
14
|
+
441 | 4*5*6
|
15
|
+
1243 | 7*8*9*10*11*12*13*14*15
|
16
|
+
380 | 2**32
|
17
|
+
1115 | 2**128
|
18
|
+
|
|
19
|
+
| end
|
20
|
+
| end
|
21
|
+
|
|
22
|
+
| File.readlines(__FILE__).each_with_index do |line, num|
|
23
|
+
| if (sample = profile[__FILE__][num+1]) > 0
|
24
|
+
| printf "% 6d | %s", sample, line
|
25
|
+
| else
|
26
|
+
| printf " | %s", line
|
27
|
+
| end
|
28
|
+
| end
|
29
|
+
```
|
30
|
+
|
31
|
+
## Other profilers
|
32
|
+
|
33
|
+
* [PLine](https://github.com/soba1104/PLine) line-profiler for ruby 1.9
|
34
|
+
* pure-ruby [LineProfiler](http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/18997?help-en) for ruby 1.6
|
data/ext/.gitignore
ADDED
data/ext/extconf.rb
ADDED
data/ext/rblineprof.c
ADDED
@@ -0,0 +1,228 @@
|
|
1
|
+
#include <stdio.h>
|
2
|
+
#include <stdlib.h>
|
3
|
+
#include <stdbool.h>
|
4
|
+
#include <string.h>
|
5
|
+
|
6
|
+
#include <ruby.h>
|
7
|
+
#include <node.h>
|
8
|
+
#include <intern.h>
|
9
|
+
#include <st.h>
|
10
|
+
#include <re.h>
|
11
|
+
|
12
|
+
static VALUE gc_hook;
|
13
|
+
|
14
|
+
typedef struct {
|
15
|
+
char *filename;
|
16
|
+
uint64_t *lines;
|
17
|
+
long nlines;
|
18
|
+
|
19
|
+
uint64_t last_time;
|
20
|
+
long last_line;
|
21
|
+
} sourcefile_t;
|
22
|
+
|
23
|
+
static struct {
|
24
|
+
bool enabled;
|
25
|
+
|
26
|
+
// single file mode, store filename and line data directly
|
27
|
+
char *source_filename;
|
28
|
+
sourcefile_t file;
|
29
|
+
|
30
|
+
// regex mode, store file data in hash table
|
31
|
+
VALUE source_regex;
|
32
|
+
st_table *files;
|
33
|
+
sourcefile_t *last_file;
|
34
|
+
}
|
35
|
+
rblineprof = {
|
36
|
+
.enabled = false,
|
37
|
+
.source_filename = NULL,
|
38
|
+
.source_regex = Qfalse,
|
39
|
+
.files = NULL,
|
40
|
+
.last_file = NULL
|
41
|
+
};
|
42
|
+
|
43
|
+
static uint64_t
|
44
|
+
timeofday_usec()
|
45
|
+
{
|
46
|
+
struct timeval tv;
|
47
|
+
gettimeofday(&tv, NULL);
|
48
|
+
return (uint64_t)tv.tv_sec*1e6 +
|
49
|
+
(uint64_t)tv.tv_usec;
|
50
|
+
}
|
51
|
+
|
52
|
+
static void
|
53
|
+
profiler_hook(rb_event_t event, NODE *node, VALUE self, ID mid, VALUE klass)
|
54
|
+
{
|
55
|
+
sourcefile_t *sourcefile = NULL;
|
56
|
+
|
57
|
+
char *file = node->nd_file;
|
58
|
+
long line = nd_line(node);
|
59
|
+
|
60
|
+
if (!file) return;
|
61
|
+
if (line <= 0) return;
|
62
|
+
|
63
|
+
if (rblineprof.source_filename) { // single file mode
|
64
|
+
if (rblineprof.source_filename == file) {
|
65
|
+
sourcefile = &rblineprof.file;
|
66
|
+
sourcefile->filename = file;
|
67
|
+
} else {
|
68
|
+
return;
|
69
|
+
}
|
70
|
+
|
71
|
+
} else { // regex mode
|
72
|
+
st_lookup(rblineprof.files, (st_data_t)file, (st_data_t *)&sourcefile);
|
73
|
+
|
74
|
+
if ((VALUE)sourcefile == Qnil) // known negative match, skip
|
75
|
+
return;
|
76
|
+
|
77
|
+
if (!sourcefile) { // unknown file, check against regex
|
78
|
+
if (rb_reg_search(rblineprof.source_regex, rb_str_new2(file), 0, 0) >= 0) {
|
79
|
+
sourcefile = ALLOC_N(sourcefile_t, 1);
|
80
|
+
MEMZERO(sourcefile, sourcefile_t, 1);
|
81
|
+
sourcefile->filename = strdup(file);
|
82
|
+
st_insert(rblineprof.files, (st_data_t)sourcefile->filename, (st_data_t)sourcefile);
|
83
|
+
} else { // no match, insert Qnil to prevent regex next time
|
84
|
+
st_insert(rblineprof.files, (st_data_t)strdup(file), (st_data_t)Qnil);
|
85
|
+
return;
|
86
|
+
}
|
87
|
+
}
|
88
|
+
}
|
89
|
+
|
90
|
+
if (sourcefile) {
|
91
|
+
uint64_t now = timeofday_usec();
|
92
|
+
|
93
|
+
if (sourcefile->last_time) {
|
94
|
+
/* allocate space for per-line data the first time */
|
95
|
+
if (sourcefile->lines == NULL) {
|
96
|
+
sourcefile->nlines = sourcefile->last_line + 100;
|
97
|
+
sourcefile->lines = ALLOC_N(uint64_t, sourcefile->nlines);
|
98
|
+
MEMZERO(sourcefile->lines, uint64_t, sourcefile->nlines);
|
99
|
+
}
|
100
|
+
|
101
|
+
/* grow the per-line array if necessary */
|
102
|
+
if (sourcefile->last_line >= sourcefile->nlines) {
|
103
|
+
long prev_nlines = sourcefile->nlines;
|
104
|
+
sourcefile->nlines = sourcefile->last_line + 100;
|
105
|
+
|
106
|
+
REALLOC_N(sourcefile->lines, uint64_t, sourcefile->nlines);
|
107
|
+
MEMZERO(sourcefile->lines + prev_nlines, uint64_t, sourcefile->nlines - prev_nlines);
|
108
|
+
}
|
109
|
+
|
110
|
+
/* record the sample */
|
111
|
+
sourcefile->lines[sourcefile->last_line] += (now - sourcefile->last_time);
|
112
|
+
}
|
113
|
+
|
114
|
+
sourcefile->last_time = now;
|
115
|
+
sourcefile->last_line = line;
|
116
|
+
|
117
|
+
if (rblineprof.last_file && rblineprof.last_file != sourcefile)
|
118
|
+
rblineprof.last_file->last_time = 0;
|
119
|
+
|
120
|
+
rblineprof.last_file = sourcefile;
|
121
|
+
}
|
122
|
+
}
|
123
|
+
|
124
|
+
static int
|
125
|
+
cleanup_files(st_data_t key, st_data_t record, st_data_t arg)
|
126
|
+
{
|
127
|
+
xfree((char *)key);
|
128
|
+
|
129
|
+
sourcefile_t *sourcefile = (sourcefile_t*)record;
|
130
|
+
if (!sourcefile || (VALUE)sourcefile == Qnil) return ST_DELETE;
|
131
|
+
|
132
|
+
if (sourcefile->lines)
|
133
|
+
xfree(sourcefile->lines);
|
134
|
+
xfree(sourcefile);
|
135
|
+
|
136
|
+
return ST_DELETE;
|
137
|
+
}
|
138
|
+
|
139
|
+
static int
|
140
|
+
summarize_files(st_data_t key, st_data_t record, st_data_t arg)
|
141
|
+
{
|
142
|
+
sourcefile_t *sourcefile = (sourcefile_t*)record;
|
143
|
+
if (!sourcefile || (VALUE)sourcefile == Qnil) return ST_CONTINUE;
|
144
|
+
|
145
|
+
VALUE ret = (VALUE)arg;
|
146
|
+
VALUE ary = rb_ary_new();
|
147
|
+
long i;
|
148
|
+
|
149
|
+
for (i=0; i<sourcefile->nlines; i++)
|
150
|
+
rb_ary_store(ary, i, ULL2NUM(sourcefile->lines[i]));
|
151
|
+
|
152
|
+
rb_hash_aset(ret, rb_str_new2(sourcefile->filename), ary);
|
153
|
+
|
154
|
+
return ST_CONTINUE;
|
155
|
+
}
|
156
|
+
|
157
|
+
static VALUE
|
158
|
+
lineprof_ensure(VALUE self)
|
159
|
+
{
|
160
|
+
rb_remove_event_hook(profiler_hook);
|
161
|
+
rblineprof.enabled = false;
|
162
|
+
}
|
163
|
+
|
164
|
+
VALUE
|
165
|
+
lineprof(VALUE self, VALUE filename)
|
166
|
+
{
|
167
|
+
if (!rb_block_given_p())
|
168
|
+
rb_raise(rb_eArgError, "block required");
|
169
|
+
|
170
|
+
if (rblineprof.enabled)
|
171
|
+
rb_raise(rb_eArgError, "profiler is already enabled");
|
172
|
+
|
173
|
+
VALUE filename_class = rb_obj_class(filename);
|
174
|
+
|
175
|
+
if (filename_class == rb_cString) {
|
176
|
+
rblineprof.source_filename = rb_source_filename(StringValuePtr(filename));
|
177
|
+
} else if (filename_class == rb_cRegexp) {
|
178
|
+
rblineprof.source_regex = filename;
|
179
|
+
rblineprof.source_filename = NULL;
|
180
|
+
} else {
|
181
|
+
rb_raise(rb_eArgError, "argument must be String or Regexp");
|
182
|
+
}
|
183
|
+
|
184
|
+
// cleanup
|
185
|
+
rblineprof.last_file = NULL;
|
186
|
+
st_foreach(rblineprof.files, cleanup_files, 0);
|
187
|
+
if (rblineprof.file.lines) {
|
188
|
+
xfree(rblineprof.file.lines);
|
189
|
+
rblineprof.file.lines = NULL;
|
190
|
+
rblineprof.file.nlines = 0;
|
191
|
+
}
|
192
|
+
|
193
|
+
rblineprof.enabled = true;
|
194
|
+
rb_add_event_hook(profiler_hook, RUBY_EVENT_LINE);
|
195
|
+
rb_ensure(rb_yield, Qnil, lineprof_ensure, self);
|
196
|
+
|
197
|
+
VALUE ret = rb_hash_new();
|
198
|
+
VALUE ary = Qnil;
|
199
|
+
|
200
|
+
if (rblineprof.source_filename) {
|
201
|
+
long i;
|
202
|
+
ary = rb_ary_new();
|
203
|
+
for (i=0; i<rblineprof.file.nlines; i++)
|
204
|
+
rb_ary_store(ary, i, ULL2NUM(rblineprof.file.lines[i]));
|
205
|
+
rb_hash_aset(ret, rb_str_new2(rblineprof.source_filename), ary);
|
206
|
+
} else {
|
207
|
+
st_foreach(rblineprof.files, summarize_files, ret);
|
208
|
+
}
|
209
|
+
|
210
|
+
return ret;
|
211
|
+
}
|
212
|
+
|
213
|
+
static void
|
214
|
+
rblineprof_gc_mark()
|
215
|
+
{
|
216
|
+
if (rblineprof.enabled)
|
217
|
+
rb_gc_mark_maybe(rblineprof.source_regex);
|
218
|
+
}
|
219
|
+
|
220
|
+
void
|
221
|
+
Init_rblineprof()
|
222
|
+
{
|
223
|
+
gc_hook = Data_Wrap_Struct(rb_cObject, rblineprof_gc_mark, NULL, NULL);
|
224
|
+
rb_global_variable(&gc_hook);
|
225
|
+
|
226
|
+
rblineprof.files = st_init_strtable();
|
227
|
+
rb_define_global_function("lineprof", lineprof, 1);
|
228
|
+
}
|
data/rblineprof.gemspec
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'rblineprof'
|
3
|
+
s.version = '0.2.0'
|
4
|
+
s.homepage = 'http://github.com/tmm1/rblineprof'
|
5
|
+
|
6
|
+
s.authors = 'Aman Gupta'
|
7
|
+
s.email = 'aman@tmm1.net'
|
8
|
+
|
9
|
+
s.files = `git ls-files`.split("\n")
|
10
|
+
s.extensions = 'ext/extconf.rb'
|
11
|
+
|
12
|
+
s.summary = 'line-profiler for ruby 1.8'
|
13
|
+
s.description = 'rblineprof shows you lines of code that are slow.'
|
14
|
+
end
|
data/test.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
$:.unshift 'ext'
|
2
|
+
require 'rblineprof'
|
3
|
+
|
4
|
+
profile = lineprof(/./) do
|
5
|
+
1000.times do
|
6
|
+
|
7
|
+
1*2*3
|
8
|
+
4*5*6
|
9
|
+
7*8*9*10*11*12*13*14*15
|
10
|
+
2**32
|
11
|
+
2**128
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
File.readlines(__FILE__).each_with_index do |line, num|
|
17
|
+
if (sample = profile[__FILE__][num+1]) > 0
|
18
|
+
printf "% 6d | %s", sample, line
|
19
|
+
else
|
20
|
+
printf " | %s", line
|
21
|
+
end
|
22
|
+
end
|
metadata
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rblineprof
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 23
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 2
|
9
|
+
- 0
|
10
|
+
version: 0.2.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Aman Gupta
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-09-18 00:00:00 Z
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description: rblineprof shows you lines of code that are slow.
|
22
|
+
email: aman@tmm1.net
|
23
|
+
executables: []
|
24
|
+
|
25
|
+
extensions:
|
26
|
+
- ext/extconf.rb
|
27
|
+
extra_rdoc_files: []
|
28
|
+
|
29
|
+
files:
|
30
|
+
- README.md
|
31
|
+
- ext/.gitignore
|
32
|
+
- ext/extconf.rb
|
33
|
+
- ext/rblineprof.c
|
34
|
+
- rblineprof.gemspec
|
35
|
+
- test.rb
|
36
|
+
homepage: http://github.com/tmm1/rblineprof
|
37
|
+
licenses: []
|
38
|
+
|
39
|
+
post_install_message:
|
40
|
+
rdoc_options: []
|
41
|
+
|
42
|
+
require_paths:
|
43
|
+
- lib
|
44
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
45
|
+
none: false
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
hash: 3
|
50
|
+
segments:
|
51
|
+
- 0
|
52
|
+
version: "0"
|
53
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
hash: 3
|
59
|
+
segments:
|
60
|
+
- 0
|
61
|
+
version: "0"
|
62
|
+
requirements: []
|
63
|
+
|
64
|
+
rubyforge_project:
|
65
|
+
rubygems_version: 1.8.24
|
66
|
+
signing_key:
|
67
|
+
specification_version: 3
|
68
|
+
summary: line-profiler for ruby 1.8
|
69
|
+
test_files: []
|
70
|
+
|