ruby-prof 0.4.0-mswin32
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/CHANGES +17 -0
- data/LICENSE +23 -0
- data/README +220 -0
- data/Rakefile +141 -0
- data/bin/ruby-prof +154 -0
- data/doc/classes/RubyProf.html +563 -0
- data/doc/classes/RubyProf/CallInfo.html +274 -0
- data/doc/classes/RubyProf/FlatPrinter.html +207 -0
- data/doc/classes/RubyProf/GraphHtmlPrinter.html +538 -0
- data/doc/classes/RubyProf/GraphPrinter.html +240 -0
- data/doc/classes/RubyProf/MethodInfo.html +556 -0
- data/doc/classes/RubyProf/ProfileTask.html +395 -0
- data/doc/classes/RubyProf/Result.html +234 -0
- data/doc/created.rid +1 -0
- data/doc/files/LICENSE.html +142 -0
- data/doc/files/README.html +376 -0
- data/doc/files/bin/ruby-prof.html +143 -0
- data/doc/files/examples/flat_txt.html +187 -0
- data/doc/files/examples/graph_html.html +948 -0
- data/doc/files/examples/graph_txt.html +305 -0
- data/doc/files/ext/ruby_prof_c.html +101 -0
- data/doc/files/lib/ruby-prof/flat_printer_rb.html +101 -0
- data/doc/files/lib/ruby-prof/graph_html_printer_rb.html +108 -0
- data/doc/files/lib/ruby-prof/graph_printer_rb.html +101 -0
- data/doc/files/lib/ruby-prof/profiletask_rb.html +109 -0
- data/doc/files/lib/ruby-prof_rb.html +111 -0
- data/doc/files/lib/unprof_rb.html +108 -0
- data/doc/fr_class_index.html +34 -0
- data/doc/fr_file_index.html +39 -0
- data/doc/fr_method_index.html +67 -0
- data/doc/index.html +24 -0
- data/doc/rdoc-style.css +208 -0
- data/examples/flat.txt +57 -0
- data/examples/graph.html +827 -0
- data/examples/graph.txt +171 -0
- data/ext/extconf.rb +19 -0
- data/ext/ruby_prof.c +1433 -0
- data/lib/ruby-prof.rb +38 -0
- data/lib/ruby-prof/flat_printer.rb +76 -0
- data/lib/ruby-prof/graph_html_printer.rb +227 -0
- data/lib/ruby-prof/graph_printer.rb +142 -0
- data/lib/ruby-prof/profiletask.rb +150 -0
- data/lib/ruby_prof.so +0 -0
- data/lib/unprof.rb +8 -0
- data/test/basic_test.rb +141 -0
- data/test/clock_mode_test.rb +73 -0
- data/test/module_test.rb +45 -0
- data/test/prime.rb +58 -0
- data/test/prime_test.rb +24 -0
- data/test/printers_test.rb +28 -0
- data/test/recursive_test.rb +55 -0
- data/test/test.rb +3 -0
- data/test/test_helper.rb +45 -0
- data/test/test_suite.rb +9 -0
- data/test/thread_test.rb +32 -0
- data/test/timing_test.rb +90 -0
- metadata +122 -0
data/examples/graph.txt
ADDED
@@ -0,0 +1,171 @@
|
|
1
|
+
=Graph Profiles
|
2
|
+
|
3
|
+
Graph profiles show how long each method runs, which methods call it
|
4
|
+
and which methods it calls.
|
5
|
+
|
6
|
+
As an example, here is the output from running printers_test.rb:
|
7
|
+
|
8
|
+
|
9
|
+
Thread ID: 21277412
|
10
|
+
%total %self total self children calls Name
|
11
|
+
--------------------------------------------------------------------------------
|
12
|
+
100.00% 0.00% 8.77 0.00 8.77 1 #toplevel
|
13
|
+
8.77 0.00 8.77 1/1 Object#run_primes
|
14
|
+
--------------------------------------------------------------------------------
|
15
|
+
8.77 0.00 8.77 1/1 #toplevel
|
16
|
+
100.00% 0.00% 8.77 0.00 8.77 1 Object#run_primes
|
17
|
+
0.02 0.00 0.02 1/1 Object#make_random_array
|
18
|
+
2.09 0.00 2.09 1/1 Object#find_largest
|
19
|
+
6.66 0.00 6.66 1/1 Object#find_primes
|
20
|
+
--------------------------------------------------------------------------------
|
21
|
+
6.63 4.06 2.56 500/501 Object#is_prime
|
22
|
+
2.09 0.00 2.09 1/501 Object#find_largest
|
23
|
+
99.48% 46.34% 8.72 4.06 4.66 501 Integer#upto
|
24
|
+
0.00 0.00 0.00 61/61 Array#[]
|
25
|
+
0.00 0.00 0.00 61/61 Fixnum#>
|
26
|
+
2.09 2.09 0.00 61/61 Kernel.sleep
|
27
|
+
1.24 1.24 0.00 250862/250862 Fixnum#==
|
28
|
+
1.33 1.33 0.00 250862/250862 Fixnum#%
|
29
|
+
--------------------------------------------------------------------------------
|
30
|
+
6.66 0.01 6.64 1/1 Object#find_primes
|
31
|
+
75.93% 0.17% 6.66 0.01 6.64 1 Array#select
|
32
|
+
6.64 0.01 6.63 500/500 Object#is_prime
|
33
|
+
--------------------------------------------------------------------------------
|
34
|
+
6.66 0.00 6.66 1/1 Object#run_primes
|
35
|
+
75.93% 0.00% 6.66 0.00 6.66 1 Object#find_primes
|
36
|
+
6.66 0.01 6.64 1/1 Array#select
|
37
|
+
--------------------------------------------------------------------------------
|
38
|
+
6.64 0.01 6.63 500/500 Array#select
|
39
|
+
75.76% 0.17% 6.64 0.01 6.63 500 Object#is_prime
|
40
|
+
0.00 0.00 0.00 500/501 Fixnum#-
|
41
|
+
6.63 4.06 2.56 500/501 Integer#upto
|
42
|
+
--------------------------------------------------------------------------------
|
43
|
+
2.09 0.00 2.09 1/1 Object#run_primes
|
44
|
+
23.89% 0.00% 2.09 0.00 2.09 1 Object#find_largest
|
45
|
+
0.00 0.00 0.00 1/501 Fixnum#-
|
46
|
+
2.09 0.00 2.09 1/501 Integer#upto
|
47
|
+
0.00 0.00 0.00 1/1 Array#first
|
48
|
+
0.00 0.00 0.00 1/1 Array#length
|
49
|
+
--------------------------------------------------------------------------------
|
50
|
+
2.09 2.09 0.00 61/61 Integer#upto
|
51
|
+
23.89% 23.89% 2.09 2.09 0.00 61 Kernel.sleep
|
52
|
+
--------------------------------------------------------------------------------
|
53
|
+
1.33 1.33 0.00 250862/250862 Integer#upto
|
54
|
+
15.12% 15.12% 1.33 1.33 0.00 250862 Fixnum#%
|
55
|
+
--------------------------------------------------------------------------------
|
56
|
+
1.24 1.24 0.00 250862/250862 Integer#upto
|
57
|
+
14.13% 14.13% 1.24 1.24 0.00 250862 Fixnum#==
|
58
|
+
--------------------------------------------------------------------------------
|
59
|
+
0.02 0.00 0.02 1/1 Object#run_primes
|
60
|
+
0.18% 0.00% 0.02 0.00 0.02 1 Object#make_random_array
|
61
|
+
0.02 0.02 0.00 1/1 Array#each_index
|
62
|
+
0.00 0.00 0.00 1/1 Class#new
|
63
|
+
--------------------------------------------------------------------------------
|
64
|
+
0.02 0.02 0.00 1/1 Object#make_random_array
|
65
|
+
0.18% 0.18% 0.02 0.02 0.00 1 Array#each_index
|
66
|
+
0.00 0.00 0.00 500/500 Kernel.rand
|
67
|
+
0.00 0.00 0.00 500/500 Array#[]=
|
68
|
+
--------------------------------------------------------------------------------
|
69
|
+
0.00 0.00 0.00 500/501 Object#is_prime
|
70
|
+
0.00 0.00 0.00 1/501 Object#find_largest
|
71
|
+
0.00% 0.00% 0.00 0.00 0.00 501 Fixnum#-
|
72
|
+
--------------------------------------------------------------------------------
|
73
|
+
0.00 0.00 0.00 1/1 Kernel.rand
|
74
|
+
0.00% 0.00% 0.00 0.00 0.00 1 Integer#to_int
|
75
|
+
--------------------------------------------------------------------------------
|
76
|
+
0.00 0.00 0.00 1/1 Object#find_largest
|
77
|
+
0.00% 0.00% 0.00 0.00 0.00 1 Array#first
|
78
|
+
--------------------------------------------------------------------------------
|
79
|
+
0.00 0.00 0.00 1/1 Class#new
|
80
|
+
0.00% 0.00% 0.00 0.00 0.00 1 Array#initialize
|
81
|
+
--------------------------------------------------------------------------------
|
82
|
+
0.00 0.00 0.00 1/1 Object#find_largest
|
83
|
+
0.00% 0.00% 0.00 0.00 0.00 1 Array#length
|
84
|
+
--------------------------------------------------------------------------------
|
85
|
+
0.00 0.00 0.00 1/1 Object#make_random_array
|
86
|
+
0.00% 0.00% 0.00 0.00 0.00 1 Class#new
|
87
|
+
0.00 0.00 0.00 1/1 Array#initialize
|
88
|
+
--------------------------------------------------------------------------------
|
89
|
+
0.00 0.00 0.00 61/61 Integer#upto
|
90
|
+
0.00% 0.00% 0.00 0.00 0.00 61 Fixnum#>
|
91
|
+
--------------------------------------------------------------------------------
|
92
|
+
0.00 0.00 0.00 61/61 Integer#upto
|
93
|
+
0.00% 0.00% 0.00 0.00 0.00 61 Array#[]
|
94
|
+
--------------------------------------------------------------------------------
|
95
|
+
0.00 0.00 0.00 500/500 Array#each_index
|
96
|
+
0.00% 0.00% 0.00 0.00 0.00 500 Array#[]=
|
97
|
+
--------------------------------------------------------------------------------
|
98
|
+
0.00 0.00 0.00 500/500 Array#each_index
|
99
|
+
0.00% 0.00% 0.00 0.00 0.00 500 Kernel.rand
|
100
|
+
0.00 0.00 0.00 1/1 Integer#to_int
|
101
|
+
|
102
|
+
|
103
|
+
|
104
|
+
== Overview
|
105
|
+
Dashed lines divide the report into entries, with one entry
|
106
|
+
per method. Entries are sorted by total time which is the
|
107
|
+
time spent in the method plus its children.
|
108
|
+
|
109
|
+
Each entry has a primary line demarked by values in the
|
110
|
+
%total and %self columns. The primary line represents
|
111
|
+
the method being profiles. Lines above it are the methods
|
112
|
+
that called this method (parents) while the lines below it
|
113
|
+
are the methods it called (children).
|
114
|
+
|
115
|
+
All values are in seconds. For the primary line, the columns represent:
|
116
|
+
|
117
|
+
%total - The percentage of time spent in this method and its children
|
118
|
+
%self - The percentage of time spent in this method
|
119
|
+
total - The time spent in this method and its children.
|
120
|
+
self - The time spent in this method.
|
121
|
+
children - The time spent in this method's children.
|
122
|
+
calls - The number of times this method was called.
|
123
|
+
name - The name of the method.
|
124
|
+
|
125
|
+
The interpretation of method names is:
|
126
|
+
* #toplevel - The root method that calls all other methods
|
127
|
+
* Object#test - An instance method "test" on the class "Object"
|
128
|
+
* <Class:Object>#test - A class method "test" on the class "Object"
|
129
|
+
* <Object:Object>#test - A singleton method "test" on a singleton class inherited from "Object"
|
130
|
+
* Module.test - An instance method "test" on the module "Module"
|
131
|
+
|
132
|
+
For example, we see that 99.48% of the time was spent in Integer#upto and its children.
|
133
|
+
Of that time, 4.06 seconds was spent in Integer#upto itself and 4.66 in its children.
|
134
|
+
Overall, Integer#upto was called 501 times.
|
135
|
+
|
136
|
+
== Parents
|
137
|
+
In each entry, the lines above the primary line are the methods that
|
138
|
+
called the current method. If the current method is a root method then
|
139
|
+
no parents are shown.
|
140
|
+
|
141
|
+
|
142
|
+
For parent lines, the columns represent:
|
143
|
+
|
144
|
+
total - The time spent in the current method and it children on behalf of the parent method.
|
145
|
+
self - The time spent in this method on behalf of the parent method.
|
146
|
+
children - The time spent in this method's children on behalf of the parent.
|
147
|
+
calls - The number of times the parent method called this child
|
148
|
+
|
149
|
+
Looking at Integer#upto again, we see that it was called 500 times from Object#is_prime
|
150
|
+
and 1 time from find_largest. Of the 8.72 total seconds spent in Integer#upto, 6.63
|
151
|
+
were done for Object#is_prime and 2.09 for Object#find_largest.
|
152
|
+
|
153
|
+
== Children
|
154
|
+
In each entry, the lines below the primary line are the methods that
|
155
|
+
the current method called. If the current method is a leaf method then
|
156
|
+
no children are shown.
|
157
|
+
|
158
|
+
For children lines, the columns represent:
|
159
|
+
|
160
|
+
total - The time spent in the child, and its children, on behalf of the current method
|
161
|
+
self - The time spent in the child on behalf of the current method.
|
162
|
+
children - The time spent in the child's children (ie, granchildren) in behalf of the current method
|
163
|
+
calls - The number of times the child method was called by the current method.
|
164
|
+
|
165
|
+
Taking our example of Integer#upto, we see that it called five other methods - Array#[],
|
166
|
+
Fixnum#>, Kernel.sleep, Fixnum#= and Fixnum#%. Looking at Kernel.sleep, we see that
|
167
|
+
its spent 2.09 seconds working for Integer#upto and its children spent 0 time working for
|
168
|
+
Integer#upto. To see the overall time Kernel.sleep took we would have to look up its entry
|
169
|
+
in the graph table.
|
170
|
+
|
171
|
+
|
data/ext/extconf.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require "mkmf"
|
2
|
+
|
3
|
+
if RUBY_VERSION >= "1.9"
|
4
|
+
if RUBY_RELEASE_DATE < "2005-03-17"
|
5
|
+
STDERR.print("Ruby version is too old\n")
|
6
|
+
exit(1)
|
7
|
+
end
|
8
|
+
elsif RUBY_VERSION >= "1.8"
|
9
|
+
if RUBY_RELEASE_DATE < "2005-03-22"
|
10
|
+
STDERR.print("Ruby version is too old\n")
|
11
|
+
exit(1)
|
12
|
+
end
|
13
|
+
else
|
14
|
+
STDERR.print("Ruby version is too old\n")
|
15
|
+
exit(1)
|
16
|
+
end
|
17
|
+
|
18
|
+
have_header("sys/times.h")
|
19
|
+
create_makefile("ruby_prof")
|
data/ext/ruby_prof.c
ADDED
@@ -0,0 +1,1433 @@
|
|
1
|
+
/*
|
2
|
+
* $Id: prof.c 298 2005-05-11 08:33:37Z shugo $
|
3
|
+
* Copyright (C) 2005 Shugo Maeda <shugo@ruby-lang.org>
|
4
|
+
* All rights reserved.
|
5
|
+
*
|
6
|
+
* Redistribution and use in source and binary forms, with or without
|
7
|
+
* modification, are permitted provided that the following conditions
|
8
|
+
* are met:
|
9
|
+
* 1. Redistributions of source code must retain the above copyright
|
10
|
+
* notice, this list of conditions and the following disclaimer.
|
11
|
+
* 2. Redistributions in binary form must reproduce the above copyright
|
12
|
+
* notice, this list of conditions and the following disclaimer in the
|
13
|
+
* documentation and/or other materials provided with the distribution.
|
14
|
+
*
|
15
|
+
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
|
16
|
+
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
17
|
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
18
|
+
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
|
19
|
+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
20
|
+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
21
|
+
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
22
|
+
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
23
|
+
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
24
|
+
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
25
|
+
* SUCH DAMAGE.
|
26
|
+
*/
|
27
|
+
|
28
|
+
#include <stdio.h>
|
29
|
+
#include <time.h>
|
30
|
+
#ifdef HAVE_SYS_TIMES_H
|
31
|
+
#include <sys/times.h>
|
32
|
+
#endif
|
33
|
+
|
34
|
+
#include <ruby.h>
|
35
|
+
#include <node.h>
|
36
|
+
#include <st.h>
|
37
|
+
|
38
|
+
#define PROF_VERSION "0.4.0"
|
39
|
+
|
40
|
+
static VALUE mProf;
|
41
|
+
static VALUE cResult;
|
42
|
+
static VALUE cMethodInfo;
|
43
|
+
static VALUE cCallInfo;
|
44
|
+
|
45
|
+
#ifdef HAVE_LONG_LONG
|
46
|
+
typedef LONG_LONG prof_clock_t;
|
47
|
+
#else
|
48
|
+
typedef unsigned long prof_clock_t;
|
49
|
+
#endif
|
50
|
+
|
51
|
+
typedef struct {
|
52
|
+
VALUE klass;
|
53
|
+
ID mid;
|
54
|
+
int called;
|
55
|
+
prof_clock_t self_time;
|
56
|
+
prof_clock_t total_time;
|
57
|
+
} prof_call_info_t;
|
58
|
+
|
59
|
+
typedef struct {
|
60
|
+
/* Cache hash value for speed reasons. */
|
61
|
+
st_data_t key;
|
62
|
+
VALUE klass;
|
63
|
+
ID mid;
|
64
|
+
int thread_id;
|
65
|
+
int called;
|
66
|
+
prof_clock_t self_time;
|
67
|
+
prof_clock_t total_time;
|
68
|
+
st_table *parents;
|
69
|
+
st_table *children;
|
70
|
+
/* Hack - piggyback a field to keep track of the
|
71
|
+
of times the method appears in the current
|
72
|
+
stack. Used to detect recursive cycles. This
|
73
|
+
works because there is an instance of this struct
|
74
|
+
per method per thread. Could have a separate
|
75
|
+
hash table...would be cleaner but adds a bit of
|
76
|
+
code and 1 extra lookup per event.*/
|
77
|
+
int stack_count;
|
78
|
+
} prof_method_t;
|
79
|
+
|
80
|
+
typedef struct {
|
81
|
+
/* Cache prof_method_t values to significantly
|
82
|
+
increase speed. */
|
83
|
+
prof_method_t *method_info;
|
84
|
+
prof_clock_t start_time;
|
85
|
+
prof_clock_t child_cost;
|
86
|
+
} prof_data_t;
|
87
|
+
|
88
|
+
typedef struct {
|
89
|
+
prof_data_t *start;
|
90
|
+
prof_data_t *end;
|
91
|
+
prof_data_t *ptr;
|
92
|
+
} prof_stack_t;
|
93
|
+
|
94
|
+
typedef struct {
|
95
|
+
prof_stack_t* stack;
|
96
|
+
st_table* minfo_table;
|
97
|
+
int thread_id;
|
98
|
+
} thread_data_t;
|
99
|
+
|
100
|
+
typedef struct {
|
101
|
+
VALUE threads;
|
102
|
+
} prof_result_t;
|
103
|
+
|
104
|
+
static VALUE toplevel_id;
|
105
|
+
static st_data_t toplevel_key;
|
106
|
+
static int clock_mode;
|
107
|
+
static st_table *threads_tbl = NULL;
|
108
|
+
static VALUE class_tbl = Qnil;
|
109
|
+
|
110
|
+
#define CLOCK_MODE_PROCESS 0
|
111
|
+
#define CLOCK_MODE_WALL 1
|
112
|
+
#if defined(_WIN32) || (defined(__GNUC__) && (defined(__i386__) || defined(__powerpc__) || defined(__ppc__)))
|
113
|
+
#define CLOCK_MODE_CPU 2
|
114
|
+
static double cpu_frequency;
|
115
|
+
#endif
|
116
|
+
|
117
|
+
#define INITIAL_STACK_SIZE 8
|
118
|
+
|
119
|
+
static prof_clock_t
|
120
|
+
clock_get_clock()
|
121
|
+
{
|
122
|
+
return clock();
|
123
|
+
}
|
124
|
+
|
125
|
+
static double
|
126
|
+
clock_clock2sec(prof_clock_t c)
|
127
|
+
{
|
128
|
+
return (double) c / CLOCKS_PER_SEC;
|
129
|
+
}
|
130
|
+
|
131
|
+
static prof_clock_t
|
132
|
+
gettimeofday_get_clock()
|
133
|
+
{
|
134
|
+
struct timeval tv;
|
135
|
+
gettimeofday(&tv, NULL);
|
136
|
+
return tv.tv_sec * 1000000 + tv.tv_usec;
|
137
|
+
}
|
138
|
+
|
139
|
+
static double
|
140
|
+
gettimeofday_clock2sec(prof_clock_t c)
|
141
|
+
{
|
142
|
+
return (double) c / 1000000;
|
143
|
+
}
|
144
|
+
|
145
|
+
#ifdef CLOCK_MODE_CPU
|
146
|
+
|
147
|
+
|
148
|
+
#if defined(__GNUC__)
|
149
|
+
|
150
|
+
static prof_clock_t
|
151
|
+
cpu_get_clock()
|
152
|
+
{
|
153
|
+
#if defined(__i386__)
|
154
|
+
unsigned long long x;
|
155
|
+
__asm__ __volatile__ ("rdtsc" : "=A" (x));
|
156
|
+
return x;
|
157
|
+
#elif defined(__powerpc__) || defined(__ppc__)
|
158
|
+
unsigned long long x, y;
|
159
|
+
|
160
|
+
__asm__ __volatile__ ("\n\
|
161
|
+
1: mftbu %1\n\
|
162
|
+
mftb %L0\n\
|
163
|
+
mftbu %0\n\
|
164
|
+
cmpw %0,%1\n\
|
165
|
+
bne- 1b"
|
166
|
+
: "=r" (x), "=r" (y));
|
167
|
+
return x;
|
168
|
+
#endif
|
169
|
+
}
|
170
|
+
|
171
|
+
#elif defined(_WIN32)
|
172
|
+
|
173
|
+
static prof_clock_t
|
174
|
+
cpu_get_clock()
|
175
|
+
{
|
176
|
+
prof_clock_t cycles = 0;
|
177
|
+
|
178
|
+
__asm
|
179
|
+
{
|
180
|
+
rdtsc
|
181
|
+
mov DWORD PTR cycles, eax
|
182
|
+
mov DWORD PTR [cycles + 4], edx
|
183
|
+
}
|
184
|
+
return cycles;
|
185
|
+
}
|
186
|
+
|
187
|
+
#endif
|
188
|
+
|
189
|
+
|
190
|
+
/* The _WIN32 check is needed for msys (and maybe cygwin?) */
|
191
|
+
#if defined(__GNUC__) && !defined(_WIN32)
|
192
|
+
|
193
|
+
double get_cpu_frequency()
|
194
|
+
{
|
195
|
+
unsigned long long x, y;
|
196
|
+
|
197
|
+
struct timespec ts;
|
198
|
+
ts.tv_sec = 0;
|
199
|
+
ts.tv_nsec = 500000000;
|
200
|
+
x = cpu_get_clock();
|
201
|
+
nanosleep(&ts, NULL);
|
202
|
+
y = cpu_get_clock();
|
203
|
+
return (y - x) * 2;
|
204
|
+
}
|
205
|
+
|
206
|
+
#elif defined(_WIN32)
|
207
|
+
|
208
|
+
double get_cpu_frequency()
|
209
|
+
{
|
210
|
+
unsigned long long x, y;
|
211
|
+
double frequency;
|
212
|
+
x = cpu_get_clock();
|
213
|
+
|
214
|
+
/* Use the windows sleep function, not Ruby's */
|
215
|
+
Sleep(500);
|
216
|
+
y = cpu_get_clock();
|
217
|
+
frequency = 2*(y-x);
|
218
|
+
return frequency;
|
219
|
+
}
|
220
|
+
#endif
|
221
|
+
|
222
|
+
static double
|
223
|
+
cpu_clock2sec(prof_clock_t c)
|
224
|
+
{
|
225
|
+
return (double) c / cpu_frequency;
|
226
|
+
}
|
227
|
+
|
228
|
+
/* call-seq:
|
229
|
+
cpu_frequency -> int
|
230
|
+
|
231
|
+
Returns the cpu's frequency. This value is needed when using the
|
232
|
+
cpu RubyProf::clock_mode. */
|
233
|
+
static VALUE
|
234
|
+
prof_get_cpu_frequency(VALUE self)
|
235
|
+
{
|
236
|
+
return rb_float_new(cpu_frequency);
|
237
|
+
}
|
238
|
+
|
239
|
+
/* call-seq:
|
240
|
+
cpu_frequency=value -> void
|
241
|
+
|
242
|
+
Sets the cpu's frequency. This value is needed when using the
|
243
|
+
cpu RubyProf::clock_mode. */
|
244
|
+
static VALUE
|
245
|
+
prof_set_cpu_freqeuncy(VALUE self, VALUE val)
|
246
|
+
{
|
247
|
+
cpu_frequency = NUM2DBL(val);
|
248
|
+
return val;
|
249
|
+
}
|
250
|
+
|
251
|
+
#endif
|
252
|
+
|
253
|
+
static prof_clock_t (*get_clock)() = clock_get_clock;
|
254
|
+
static double (*clock2sec)(prof_clock_t) = clock_clock2sec;
|
255
|
+
|
256
|
+
|
257
|
+
/* Helper method to get the id of a Ruby thread. */
|
258
|
+
static inline int
|
259
|
+
get_thread_id(VALUE thread)
|
260
|
+
{
|
261
|
+
return NUM2INT(rb_obj_id(thread));
|
262
|
+
}
|
263
|
+
|
264
|
+
static VALUE
|
265
|
+
figure_singleton_name(VALUE klass)
|
266
|
+
{
|
267
|
+
VALUE result = Qnil;
|
268
|
+
|
269
|
+
/* We have come across a singleton object. First
|
270
|
+
figure out what it is attached to.*/
|
271
|
+
VALUE attached = rb_iv_get(klass, "__attached__");
|
272
|
+
|
273
|
+
|
274
|
+
/* Is this a singleton class acting as a metaclass? */
|
275
|
+
if (TYPE(attached) == T_CLASS)
|
276
|
+
{
|
277
|
+
result = rb_str_new2("<Class::");
|
278
|
+
rb_str_append(result, rb_inspect(attached));
|
279
|
+
rb_str_cat2(result, ">#");
|
280
|
+
}
|
281
|
+
|
282
|
+
/* Is this for singleton methods on a module? */
|
283
|
+
else if (TYPE(attached) == T_MODULE)
|
284
|
+
{
|
285
|
+
result = rb_str_new2("<Module::");
|
286
|
+
rb_str_append(result, rb_inspect(attached));
|
287
|
+
rb_str_cat2(result, ">#");
|
288
|
+
}
|
289
|
+
|
290
|
+
/* Is it a regular singleton class for an object? */
|
291
|
+
else if (TYPE(attached) == T_OBJECT)
|
292
|
+
{
|
293
|
+
/* Make sure to get the super class so that we don't
|
294
|
+
mistakenly grab a T_ICLASS which would lead to
|
295
|
+
unknown method errors. */
|
296
|
+
VALUE super = rb_class_real(RCLASS(klass)->super);
|
297
|
+
result = rb_str_new2("<Object::");
|
298
|
+
rb_str_append(result, rb_inspect(super));
|
299
|
+
rb_str_cat2(result, ">#");
|
300
|
+
}
|
301
|
+
else
|
302
|
+
{
|
303
|
+
/* Should never happen. */
|
304
|
+
result = rb_str_new2("<Unknown:");
|
305
|
+
rb_str_append(result, rb_inspect(klass));
|
306
|
+
rb_str_cat2(result, ">#");
|
307
|
+
rb_raise(rb_eRuntimeError, "Unknown singleton class: %i", result);
|
308
|
+
}
|
309
|
+
|
310
|
+
return result;
|
311
|
+
}
|
312
|
+
|
313
|
+
static VALUE
|
314
|
+
method_name(VALUE klass, ID mid)
|
315
|
+
{
|
316
|
+
VALUE result;
|
317
|
+
VALUE method_name;
|
318
|
+
|
319
|
+
if (mid == ID_ALLOCATOR)
|
320
|
+
method_name = rb_str_new2("allocate");
|
321
|
+
else
|
322
|
+
method_name = rb_String(ID2SYM(mid));
|
323
|
+
|
324
|
+
|
325
|
+
if (klass == Qnil)
|
326
|
+
result = rb_str_new2("#");
|
327
|
+
else if (TYPE(klass) == T_MODULE)
|
328
|
+
{
|
329
|
+
result = rb_inspect(klass);
|
330
|
+
rb_str_cat2(result, "#");
|
331
|
+
}
|
332
|
+
else if (TYPE(klass) == T_CLASS && FL_TEST(klass, FL_SINGLETON))
|
333
|
+
{
|
334
|
+
result = figure_singleton_name(klass);
|
335
|
+
}
|
336
|
+
else if (TYPE(klass) == T_CLASS)
|
337
|
+
{
|
338
|
+
result = rb_inspect(klass);
|
339
|
+
rb_str_cat2(result, "#");
|
340
|
+
}
|
341
|
+
else
|
342
|
+
{
|
343
|
+
/* Should never happen. */
|
344
|
+
result = rb_str_new2("Unknown#");
|
345
|
+
rb_str_append(result, rb_inspect(klass));
|
346
|
+
rb_str_cat2(result, ">#");
|
347
|
+
rb_raise(rb_eRuntimeError, "Unsupported type in method name: %i\n", result);
|
348
|
+
}
|
349
|
+
|
350
|
+
/* Last add in the method name */
|
351
|
+
rb_str_append(result, method_name);
|
352
|
+
|
353
|
+
return result;
|
354
|
+
}
|
355
|
+
|
356
|
+
static inline st_data_t
|
357
|
+
method_key(VALUE klass, ID mid)
|
358
|
+
{
|
359
|
+
return klass ^ mid;
|
360
|
+
}
|
361
|
+
|
362
|
+
|
363
|
+
/* -- Stack to track methods call sequence and times ---- */
|
364
|
+
static prof_stack_t *
|
365
|
+
stack_create()
|
366
|
+
{
|
367
|
+
prof_stack_t *stack;
|
368
|
+
|
369
|
+
stack = ALLOC(prof_stack_t);
|
370
|
+
stack->start = stack->ptr =
|
371
|
+
ALLOC_N(prof_data_t, INITIAL_STACK_SIZE);
|
372
|
+
stack->end = stack->start + INITIAL_STACK_SIZE;
|
373
|
+
return stack;
|
374
|
+
}
|
375
|
+
|
376
|
+
static void
|
377
|
+
stack_free(prof_stack_t *stack)
|
378
|
+
{
|
379
|
+
xfree(stack->start);
|
380
|
+
xfree(stack);
|
381
|
+
}
|
382
|
+
|
383
|
+
static inline prof_data_t *
|
384
|
+
stack_push(prof_stack_t *stack)
|
385
|
+
{
|
386
|
+
if (stack->ptr == stack->end) {
|
387
|
+
int len, new_capa;
|
388
|
+
len = stack->ptr - stack->start;
|
389
|
+
new_capa = (stack->end - stack->start) * 2;
|
390
|
+
REALLOC_N(stack->start, prof_data_t, new_capa);
|
391
|
+
stack->ptr = stack->start + len;
|
392
|
+
stack->end = stack->start + new_capa;
|
393
|
+
}
|
394
|
+
return stack->ptr++;
|
395
|
+
}
|
396
|
+
|
397
|
+
static inline prof_data_t *
|
398
|
+
stack_pop(prof_stack_t *stack)
|
399
|
+
{
|
400
|
+
if (stack->ptr == stack->start)
|
401
|
+
return NULL;
|
402
|
+
else
|
403
|
+
return --stack->ptr;
|
404
|
+
}
|
405
|
+
|
406
|
+
static inline prof_data_t *
|
407
|
+
stack_peek(prof_stack_t *stack)
|
408
|
+
{
|
409
|
+
if (stack->ptr == stack->start)
|
410
|
+
return NULL;
|
411
|
+
else
|
412
|
+
return stack->ptr - 1;
|
413
|
+
}
|
414
|
+
|
415
|
+
|
416
|
+
|
417
|
+
/* --- Keeps track of the methods the current method calls */
|
418
|
+
static st_table *
|
419
|
+
minfo_table_create()
|
420
|
+
{
|
421
|
+
return st_init_numtable();
|
422
|
+
}
|
423
|
+
|
424
|
+
static inline int
|
425
|
+
minfo_table_insert(st_table *table, st_data_t key, prof_method_t *val)
|
426
|
+
{
|
427
|
+
return st_insert(table, key, (st_data_t) val);
|
428
|
+
}
|
429
|
+
|
430
|
+
static inline prof_method_t *
|
431
|
+
minfo_table_lookup(st_table *table, st_data_t key)
|
432
|
+
{
|
433
|
+
st_data_t val;
|
434
|
+
if (st_lookup(table, key, &val)) {
|
435
|
+
return (prof_method_t *) val;
|
436
|
+
}
|
437
|
+
else {
|
438
|
+
return NULL;
|
439
|
+
}
|
440
|
+
}
|
441
|
+
|
442
|
+
static void
|
443
|
+
minfo_table_free(st_table *table)
|
444
|
+
{
|
445
|
+
st_free_table(table);
|
446
|
+
}
|
447
|
+
|
448
|
+
|
449
|
+
/* ---- Hash, keyed on class/method_id, that holds
|
450
|
+
child call_info objects ---- */
|
451
|
+
static st_table *
|
452
|
+
child_table_create()
|
453
|
+
{
|
454
|
+
return st_init_numtable();
|
455
|
+
}
|
456
|
+
|
457
|
+
static inline int
|
458
|
+
child_table_insert(st_table *table, st_data_t key, prof_call_info_t *val)
|
459
|
+
{
|
460
|
+
return st_insert(table, key, (st_data_t) val);
|
461
|
+
}
|
462
|
+
|
463
|
+
static inline prof_call_info_t *
|
464
|
+
child_table_lookup(st_table *table, st_data_t key)
|
465
|
+
{
|
466
|
+
st_data_t val;
|
467
|
+
if (st_lookup(table, key, &val)) {
|
468
|
+
return (prof_call_info_t *) val;
|
469
|
+
}
|
470
|
+
else {
|
471
|
+
return NULL;
|
472
|
+
}
|
473
|
+
}
|
474
|
+
|
475
|
+
static void
|
476
|
+
child_table_free(st_table *table)
|
477
|
+
{
|
478
|
+
st_free_table(table);
|
479
|
+
}
|
480
|
+
|
481
|
+
/* Document-class: RubyProf::CallInfo
|
482
|
+
RubyProf::CallInfo is a helper class used by RubyProf::MethodInfo
|
483
|
+
to keep track of which child methods were called and how long
|
484
|
+
they took to execute. */
|
485
|
+
|
486
|
+
/* :nodoc: */
|
487
|
+
static prof_call_info_t *
|
488
|
+
call_info_create(VALUE klass, ID mid)
|
489
|
+
{
|
490
|
+
prof_call_info_t *result;
|
491
|
+
|
492
|
+
result = ALLOC(prof_call_info_t);
|
493
|
+
result->klass = klass;
|
494
|
+
result->mid = mid;
|
495
|
+
result->called = 0;
|
496
|
+
result->total_time = 0;
|
497
|
+
result->self_time = 0;
|
498
|
+
return result;
|
499
|
+
}
|
500
|
+
|
501
|
+
static void
|
502
|
+
call_info_free(prof_call_info_t *call_info)
|
503
|
+
{
|
504
|
+
xfree(call_info);
|
505
|
+
}
|
506
|
+
|
507
|
+
static int
|
508
|
+
free_call_infos(st_data_t key, st_data_t value, st_data_t data)
|
509
|
+
{
|
510
|
+
prof_call_info_t* call_info = (prof_call_info_t*) value;
|
511
|
+
call_info_free(call_info);
|
512
|
+
return ST_CONTINUE;
|
513
|
+
}
|
514
|
+
|
515
|
+
static VALUE
|
516
|
+
call_info_new(prof_call_info_t *result)
|
517
|
+
{
|
518
|
+
/* We don't want Ruby freeing the underlying C structures, that
|
519
|
+
is when the prof_method_t is freed. */
|
520
|
+
return Data_Wrap_Struct(cCallInfo, NULL, NULL, result);
|
521
|
+
}
|
522
|
+
|
523
|
+
static prof_call_info_t *
|
524
|
+
get_call_info_result(VALUE obj)
|
525
|
+
{
|
526
|
+
if (TYPE(obj) != T_DATA)
|
527
|
+
{
|
528
|
+
/* Should never happen */
|
529
|
+
rb_raise(rb_eTypeError, "Not a call info object");
|
530
|
+
}
|
531
|
+
return (prof_call_info_t *) DATA_PTR(obj);
|
532
|
+
}
|
533
|
+
|
534
|
+
/* call-seq:
|
535
|
+
called -> int
|
536
|
+
|
537
|
+
Returns the total amount of time this method was called. */
|
538
|
+
static VALUE
|
539
|
+
call_info_called(VALUE self)
|
540
|
+
{
|
541
|
+
prof_call_info_t *result = get_call_info_result(self);
|
542
|
+
|
543
|
+
return INT2NUM(result->called);
|
544
|
+
}
|
545
|
+
|
546
|
+
/* call-seq:
|
547
|
+
total_time -> float
|
548
|
+
|
549
|
+
Returns the total amount of time spent in this method and its children. */
|
550
|
+
static VALUE
|
551
|
+
call_info_total_time(VALUE self)
|
552
|
+
{
|
553
|
+
prof_call_info_t *result = get_call_info_result(self);
|
554
|
+
|
555
|
+
return rb_float_new(clock2sec(result->total_time));
|
556
|
+
}
|
557
|
+
|
558
|
+
/* call-seq:
|
559
|
+
self_time -> float
|
560
|
+
|
561
|
+
Returns the total amount of time spent in this method. */
|
562
|
+
static VALUE
|
563
|
+
call_info_self_time(VALUE self)
|
564
|
+
{
|
565
|
+
prof_call_info_t *result = get_call_info_result(self);
|
566
|
+
|
567
|
+
return rb_float_new(clock2sec(result->self_time));
|
568
|
+
}
|
569
|
+
|
570
|
+
/* call-seq:
|
571
|
+
children_time -> float
|
572
|
+
|
573
|
+
Returns the total amount of time spent in this method's children. */
|
574
|
+
static VALUE
|
575
|
+
call_info_children_time(VALUE self)
|
576
|
+
{
|
577
|
+
prof_call_info_t *result = get_call_info_result(self);
|
578
|
+
prof_clock_t children_time = result->total_time - result->self_time;
|
579
|
+
return rb_float_new(clock2sec(children_time));
|
580
|
+
}
|
581
|
+
|
582
|
+
|
583
|
+
/* Document-class: RubyProf::MethodInfo
|
584
|
+
The RubyProf::MethodInfo class stores profiling data for a method.
|
585
|
+
One instance of the RubyProf::MethodInfo class is created per method
|
586
|
+
called per thread. Thus, if a method is called in two different
|
587
|
+
thread then there will be two RubyProf::MethodInfo objects
|
588
|
+
created. RubyProf::MethodInfo objects can be accessed via
|
589
|
+
the RubyProf::Result object.
|
590
|
+
*/
|
591
|
+
|
592
|
+
/* :nodoc: */
|
593
|
+
static prof_method_t *
|
594
|
+
prof_method_create(VALUE klass, ID mid, VALUE thread)
|
595
|
+
{
|
596
|
+
prof_method_t *result;
|
597
|
+
|
598
|
+
/* Store reference to klass so it is not garbage collected */
|
599
|
+
rb_hash_aset(class_tbl, klass, Qnil);
|
600
|
+
|
601
|
+
result = ALLOC(prof_method_t);
|
602
|
+
result->key = method_key(klass, mid);
|
603
|
+
result->called = 0;
|
604
|
+
result->total_time = 0;
|
605
|
+
result->self_time = 0;
|
606
|
+
result->klass = klass;
|
607
|
+
result->mid = mid;
|
608
|
+
result->thread_id = get_thread_id(thread);
|
609
|
+
result->parents = minfo_table_create();
|
610
|
+
result->children = child_table_create();
|
611
|
+
result->stack_count = 0;
|
612
|
+
return result;
|
613
|
+
}
|
614
|
+
|
615
|
+
static void
|
616
|
+
prof_method_mark(prof_method_t *data)
|
617
|
+
{
|
618
|
+
rb_gc_mark(data->klass);
|
619
|
+
}
|
620
|
+
|
621
|
+
static void
|
622
|
+
prof_method_free(prof_method_t *data)
|
623
|
+
{
|
624
|
+
st_foreach(data->children, free_call_infos, 0);
|
625
|
+
minfo_table_free(data->parents);
|
626
|
+
child_table_free(data->children);
|
627
|
+
xfree(data);
|
628
|
+
}
|
629
|
+
|
630
|
+
static VALUE
|
631
|
+
prof_method_new(prof_method_t *result)
|
632
|
+
{
|
633
|
+
return Data_Wrap_Struct(cMethodInfo, prof_method_mark, prof_method_free,
|
634
|
+
result);
|
635
|
+
}
|
636
|
+
|
637
|
+
static prof_method_t *
|
638
|
+
get_prof_method(VALUE obj)
|
639
|
+
{
|
640
|
+
if (TYPE(obj) != T_DATA ||
|
641
|
+
RDATA(obj)->dfree != (RUBY_DATA_FUNC) prof_method_free)
|
642
|
+
{
|
643
|
+
/* Should never happen */
|
644
|
+
rb_raise(rb_eTypeError, "wrong profile result");
|
645
|
+
}
|
646
|
+
return (prof_method_t *) DATA_PTR(obj);
|
647
|
+
}
|
648
|
+
|
649
|
+
/* call-seq:
|
650
|
+
called -> int
|
651
|
+
|
652
|
+
Returns the number of times this method was called. */
|
653
|
+
static VALUE
|
654
|
+
prof_method_called(VALUE self)
|
655
|
+
{
|
656
|
+
prof_method_t *result = get_prof_method(self);
|
657
|
+
|
658
|
+
return INT2NUM(result->called);
|
659
|
+
}
|
660
|
+
|
661
|
+
|
662
|
+
/* call-seq:
|
663
|
+
total_time -> float
|
664
|
+
|
665
|
+
Returns the total amount of time spent in this method and its children. */
|
666
|
+
static VALUE
|
667
|
+
prof_method_total_time(VALUE self)
|
668
|
+
{
|
669
|
+
prof_method_t *result = get_prof_method(self);
|
670
|
+
|
671
|
+
return rb_float_new(clock2sec(result->total_time));
|
672
|
+
}
|
673
|
+
|
674
|
+
/* call-seq:
|
675
|
+
self_time -> float
|
676
|
+
|
677
|
+
Returns the total amount of time spent in this method. */
|
678
|
+
static VALUE
|
679
|
+
prof_method_self_time(VALUE self)
|
680
|
+
{
|
681
|
+
prof_method_t *result = get_prof_method(self);
|
682
|
+
|
683
|
+
return rb_float_new(clock2sec(result->self_time));
|
684
|
+
}
|
685
|
+
|
686
|
+
/* call-seq:
|
687
|
+
children_time -> float
|
688
|
+
|
689
|
+
Returns the total amount of time spent in this method's children. */
|
690
|
+
static VALUE
|
691
|
+
prof_method_children_time(VALUE self)
|
692
|
+
{
|
693
|
+
prof_method_t *result = get_prof_method(self);
|
694
|
+
prof_clock_t children_time = result->total_time - result->self_time;
|
695
|
+
return rb_float_new(clock2sec(children_time));
|
696
|
+
}
|
697
|
+
|
698
|
+
/* call-seq:
|
699
|
+
thread_id -> id
|
700
|
+
|
701
|
+
Returns the id of the thread that executed this method.*/
|
702
|
+
static VALUE
|
703
|
+
prof_thread_id(VALUE self)
|
704
|
+
{
|
705
|
+
prof_method_t *result = get_prof_method(self);
|
706
|
+
|
707
|
+
return INT2FIX(result->thread_id);
|
708
|
+
}
|
709
|
+
|
710
|
+
/* call-seq:
|
711
|
+
method_class -> klass
|
712
|
+
|
713
|
+
Returns the Ruby klass that owns this method. */
|
714
|
+
static VALUE
|
715
|
+
prof_method_class(VALUE self)
|
716
|
+
{
|
717
|
+
prof_method_t *result = get_prof_method(self);
|
718
|
+
|
719
|
+
return result->klass;
|
720
|
+
}
|
721
|
+
|
722
|
+
/* call-seq:
|
723
|
+
method_id -> ID
|
724
|
+
|
725
|
+
Returns the id of this method. */
|
726
|
+
static VALUE
|
727
|
+
prof_method_id(VALUE self)
|
728
|
+
{
|
729
|
+
prof_method_t *result = get_prof_method(self);
|
730
|
+
|
731
|
+
return ID2SYM(result->mid);
|
732
|
+
}
|
733
|
+
|
734
|
+
/* call-seq:
|
735
|
+
method_name -> string
|
736
|
+
|
737
|
+
Returns the name of this object. The name may be in the form:
|
738
|
+
Object#method
|
739
|
+
Module.method
|
740
|
+
.method */
|
741
|
+
static VALUE
|
742
|
+
prof_method_name(VALUE self)
|
743
|
+
{
|
744
|
+
prof_method_t *method = get_prof_method(self);
|
745
|
+
return method_name(method->klass, method->mid);
|
746
|
+
}
|
747
|
+
|
748
|
+
static int
|
749
|
+
prof_method_collect_parents(st_data_t key, st_data_t value, st_data_t parents)
|
750
|
+
{
|
751
|
+
prof_method_t *parent = (prof_method_t *) value;
|
752
|
+
|
753
|
+
rb_ary_push(parents, INT2FIX((int) parent));
|
754
|
+
return ST_CONTINUE;
|
755
|
+
}
|
756
|
+
|
757
|
+
|
758
|
+
/* call-seq:
|
759
|
+
parents -> hash
|
760
|
+
|
761
|
+
Returns a hash table that lists all the methods that called this
|
762
|
+
method (ie, parents). The hash table is keyed on method name and contains references
|
763
|
+
to RubyProf::MethodInfo objects.*/
|
764
|
+
static VALUE
|
765
|
+
prof_method_parents(VALUE self)
|
766
|
+
{
|
767
|
+
VALUE result = rb_hash_new();
|
768
|
+
VALUE parents = rb_ary_new();
|
769
|
+
int len = 0;
|
770
|
+
int i = 0;
|
771
|
+
|
772
|
+
/* Get the list of parents */
|
773
|
+
prof_method_t *child = get_prof_method(self);
|
774
|
+
st_foreach(child->parents, prof_method_collect_parents, parents);
|
775
|
+
|
776
|
+
/* Iterate over each parent */
|
777
|
+
len = RARRAY(parents)->len;
|
778
|
+
for(i = 0; i<len; i++)
|
779
|
+
{
|
780
|
+
prof_call_info_t *call_info;
|
781
|
+
|
782
|
+
/* First get the parent */
|
783
|
+
VALUE item = rb_ary_entry(parents, i);
|
784
|
+
prof_method_t *parent = (prof_method_t *)(FIX2INT(item));
|
785
|
+
|
786
|
+
/* Now get the call info */
|
787
|
+
call_info = child_table_lookup(parent->children, child->key);
|
788
|
+
|
789
|
+
if (call_info == NULL)
|
790
|
+
{
|
791
|
+
/* Should never happen */
|
792
|
+
rb_raise(rb_eRuntimeError,
|
793
|
+
"Could not find parent call info object for %s",
|
794
|
+
method_name(child->klass, child->mid));
|
795
|
+
}
|
796
|
+
|
797
|
+
/* Create a new Ruby CallInfo object and store it into the hash
|
798
|
+
keyed on the parent's name. We use the parent's name because
|
799
|
+
we want to see that printed out for parent records in
|
800
|
+
a call graph. */
|
801
|
+
rb_hash_aset(result, method_name(parent->klass, parent->mid),
|
802
|
+
call_info_new(call_info));
|
803
|
+
}
|
804
|
+
|
805
|
+
return result;
|
806
|
+
}
|
807
|
+
|
808
|
+
|
809
|
+
static int
|
810
|
+
prof_method_collect_children(st_data_t key, st_data_t value, st_data_t result)
|
811
|
+
{
|
812
|
+
prof_call_info_t *call_info = (prof_call_info_t *) value;
|
813
|
+
VALUE name = method_name(call_info->klass, call_info->mid);
|
814
|
+
VALUE hash = (VALUE) result;
|
815
|
+
|
816
|
+
/* Create a new Ruby CallInfo object and store it into the hash
|
817
|
+
keyed on the parent's name. We use the parent's name because
|
818
|
+
we want to see that printed out for child records in
|
819
|
+
a call graph. */
|
820
|
+
rb_hash_aset(hash, name, call_info_new(call_info));
|
821
|
+
return ST_CONTINUE;
|
822
|
+
}
|
823
|
+
|
824
|
+
/* call-seq:
|
825
|
+
children -> hash
|
826
|
+
|
827
|
+
Returns a hash table that lists all the methods that this method
|
828
|
+
called (ie, children). The hash table is keyed on method name
|
829
|
+
and contains references to RubyProf::CallInfo objects.*/
|
830
|
+
static VALUE
|
831
|
+
prof_method_children(VALUE self)
|
832
|
+
{
|
833
|
+
/* Returns a hash table, keyed on method name, of call info
|
834
|
+
objects for all methods that this method calls (children). */
|
835
|
+
|
836
|
+
VALUE children = rb_hash_new();
|
837
|
+
prof_method_t *result = get_prof_method(self);
|
838
|
+
st_foreach(result->children, prof_method_collect_children, children);
|
839
|
+
return children;
|
840
|
+
}
|
841
|
+
|
842
|
+
/* :nodoc: */
|
843
|
+
static VALUE
|
844
|
+
prof_method_cmp(VALUE self, VALUE other)
|
845
|
+
{
|
846
|
+
/* For call graphs we want to sort methods by
|
847
|
+
their total time, not self time. */
|
848
|
+
prof_method_t *x = get_prof_method(self);
|
849
|
+
prof_method_t *y = get_prof_method(other);
|
850
|
+
|
851
|
+
/* Want toplevel to always be first */
|
852
|
+
if (x->klass == Qnil && x->mid == toplevel_id)
|
853
|
+
return INT2FIX(1);
|
854
|
+
else if (y->klass == Qnil && y->mid == toplevel_id)
|
855
|
+
return INT2FIX(-11);
|
856
|
+
else if (x->total_time < y->total_time)
|
857
|
+
return INT2FIX(-1);
|
858
|
+
else if (x->total_time == y->total_time)
|
859
|
+
return INT2FIX(0);
|
860
|
+
else
|
861
|
+
return INT2FIX(1);
|
862
|
+
}
|
863
|
+
|
864
|
+
static int
|
865
|
+
collect_methods(st_data_t key, st_data_t value, st_data_t result)
|
866
|
+
{
|
867
|
+
prof_method_t *method = (prof_method_t *) value;
|
868
|
+
VALUE name = method_name(method->klass, method->mid);
|
869
|
+
VALUE hash = (VALUE) result;
|
870
|
+
|
871
|
+
VALUE existing_value = rb_hash_aref(hash, name);
|
872
|
+
|
873
|
+
/* Sanity check. If we have generated the same method name for another prof_method
|
874
|
+
then we cannot put the current prof_method into the hash table. If we do, we
|
875
|
+
overwrite the reference to the other prof_method. That will mean that Ruby
|
876
|
+
will garbage collect it wreaking all sorts of havoc! Trust me - this one took
|
877
|
+
a long time to track down. */
|
878
|
+
if (existing_value != Qnil)
|
879
|
+
{
|
880
|
+
/* Definitely should never happen! */
|
881
|
+
rb_raise(rb_eRuntimeError,
|
882
|
+
"The name %s has already been assigned to another method. This is a bug - please report it.",
|
883
|
+
name);
|
884
|
+
}
|
885
|
+
rb_hash_aset(hash, name, prof_method_new(method));
|
886
|
+
|
887
|
+
return ST_CONTINUE;
|
888
|
+
}
|
889
|
+
|
890
|
+
|
891
|
+
/* ---- Keeps track of thread's stack and methods ---- */
|
892
|
+
static thread_data_t*
|
893
|
+
thread_data_create()
|
894
|
+
{
|
895
|
+
thread_data_t* result = ALLOC(thread_data_t);
|
896
|
+
result->stack = stack_create();
|
897
|
+
result->minfo_table = minfo_table_create();
|
898
|
+
return result;
|
899
|
+
}
|
900
|
+
|
901
|
+
static void
|
902
|
+
thread_data_free(thread_data_t* thread_data)
|
903
|
+
{
|
904
|
+
stack_free(thread_data->stack);
|
905
|
+
minfo_table_free(thread_data->minfo_table);
|
906
|
+
xfree(thread_data);
|
907
|
+
}
|
908
|
+
|
909
|
+
|
910
|
+
/* ---- Hash, keyed on thread, that stores thread's stack
|
911
|
+
and methods---- */
|
912
|
+
|
913
|
+
static st_table *
|
914
|
+
threads_table_create()
|
915
|
+
{
|
916
|
+
return st_init_numtable();
|
917
|
+
}
|
918
|
+
|
919
|
+
static inline int
|
920
|
+
threads_table_insert(st_table *table, VALUE thread, thread_data_t *thread_data)
|
921
|
+
{
|
922
|
+
/* Its too slow to key on the real thread id so just typecast thread instead. */
|
923
|
+
return st_insert(table, (st_data_t ) thread, (st_data_t) thread_data);
|
924
|
+
}
|
925
|
+
|
926
|
+
static inline thread_data_t *
|
927
|
+
threads_table_lookup(st_table *table, VALUE thread)
|
928
|
+
{
|
929
|
+
thread_data_t* result;
|
930
|
+
st_data_t val;
|
931
|
+
|
932
|
+
/* Its too slow to key on the real thread id so just typecast thread instead. */
|
933
|
+
if (st_lookup(table, (st_data_t) thread, &val))
|
934
|
+
{
|
935
|
+
result = (thread_data_t *) val;
|
936
|
+
}
|
937
|
+
else
|
938
|
+
{
|
939
|
+
prof_method_t *toplevel;
|
940
|
+
result = thread_data_create();
|
941
|
+
/* Store the real thread id here so it can be shown in the results. */
|
942
|
+
result->thread_id = get_thread_id(thread);
|
943
|
+
|
944
|
+
/* Add a toplevel method to the thread */
|
945
|
+
toplevel = prof_method_create(Qnil, toplevel_id, thread);
|
946
|
+
toplevel->called = 1;
|
947
|
+
toplevel->total_time = 0;
|
948
|
+
toplevel->self_time = 0;
|
949
|
+
minfo_table_insert(result->minfo_table, toplevel->key, toplevel);
|
950
|
+
|
951
|
+
/* Insert the table */
|
952
|
+
threads_table_insert(threads_tbl, thread, result);
|
953
|
+
}
|
954
|
+
return result;
|
955
|
+
}
|
956
|
+
|
957
|
+
static void
|
958
|
+
threads_table_free(st_table *table)
|
959
|
+
{
|
960
|
+
st_free_table(table);
|
961
|
+
}
|
962
|
+
|
963
|
+
static int
|
964
|
+
free_thread_data(st_data_t key, st_data_t value, st_data_t dummy)
|
965
|
+
{
|
966
|
+
thread_data_free((thread_data_t*)value);
|
967
|
+
return ST_CONTINUE;
|
968
|
+
}
|
969
|
+
|
970
|
+
static void
|
971
|
+
free_threads(st_table* thread_table)
|
972
|
+
{
|
973
|
+
st_foreach(thread_table, free_thread_data, 0);
|
974
|
+
}
|
975
|
+
|
976
|
+
static int
|
977
|
+
collect_threads(st_data_t key, st_data_t value, st_data_t result)
|
978
|
+
{
|
979
|
+
/* Although threads are keyed on an id, that is actually a
|
980
|
+
pointer to the VALUE object of the thread. So its bogus.
|
981
|
+
However, in thread_data is the real thread id stored
|
982
|
+
as an int. */
|
983
|
+
thread_data_t* thread_data = (thread_data_t*) value;
|
984
|
+
VALUE threads_hash = (VALUE) result;
|
985
|
+
VALUE minfo_hash = rb_hash_new();
|
986
|
+
st_foreach(thread_data->minfo_table, collect_methods, minfo_hash);
|
987
|
+
rb_hash_aset(threads_hash, INT2NUM(thread_data->thread_id), minfo_hash);
|
988
|
+
|
989
|
+
return ST_CONTINUE;
|
990
|
+
}
|
991
|
+
|
992
|
+
static void
|
993
|
+
update_result(prof_method_t * parent, prof_method_t *child,
|
994
|
+
prof_clock_t total_time, prof_clock_t self_time)
|
995
|
+
{
|
996
|
+
/* Update child information on parent (ie, the method that
|
997
|
+
called the current method) */
|
998
|
+
prof_call_info_t *parent_call_info = child_table_lookup(parent->children, child->key);
|
999
|
+
if (parent_call_info == NULL)
|
1000
|
+
{
|
1001
|
+
parent_call_info = call_info_create(child->klass, child->mid);
|
1002
|
+
child_table_insert(parent->children, child->key, parent_call_info);
|
1003
|
+
}
|
1004
|
+
|
1005
|
+
parent_call_info->called++;
|
1006
|
+
parent_call_info->total_time += total_time;
|
1007
|
+
parent_call_info->self_time += self_time;
|
1008
|
+
|
1009
|
+
/* Slight hack here - if the child is the top level method then we want
|
1010
|
+
to update its total time */
|
1011
|
+
if (parent->key == toplevel_key)
|
1012
|
+
parent->total_time += total_time;
|
1013
|
+
|
1014
|
+
/* Update information about the child (ie, the current method) */
|
1015
|
+
child->called++;
|
1016
|
+
child->total_time += total_time;
|
1017
|
+
child->self_time += self_time;
|
1018
|
+
|
1019
|
+
/* Store pointer to parent */
|
1020
|
+
if (minfo_table_lookup(child->parents, parent->key) == NULL)
|
1021
|
+
minfo_table_insert(child->parents, parent->key, parent);
|
1022
|
+
}
|
1023
|
+
|
1024
|
+
static void
|
1025
|
+
prof_event_hook(rb_event_t event, NODE *node, VALUE self, ID mid, VALUE klass)
|
1026
|
+
{
|
1027
|
+
static int profiling = 0;
|
1028
|
+
VALUE thread;
|
1029
|
+
thread_data_t* thread_data;
|
1030
|
+
prof_data_t *data;
|
1031
|
+
|
1032
|
+
if (profiling) return;
|
1033
|
+
|
1034
|
+
/* Special case - skip any methods from the mProf
|
1035
|
+
module, such as Prof.stop, since they clutter
|
1036
|
+
the results but are not important to the results. */
|
1037
|
+
if (self == mProf) return;
|
1038
|
+
|
1039
|
+
/* Set flag showing we have started profiling */
|
1040
|
+
profiling++;
|
1041
|
+
|
1042
|
+
/* Is this an include for a module? If so get the actual
|
1043
|
+
module class since we want to combine all profiling
|
1044
|
+
results for that module. */
|
1045
|
+
if (BUILTIN_TYPE(klass) == T_ICLASS)
|
1046
|
+
klass = RBASIC(klass)->klass;
|
1047
|
+
|
1048
|
+
/* // Debug Code
|
1049
|
+
{
|
1050
|
+
VALUE class_name = rb_String(klass);
|
1051
|
+
char* c_class_name = StringValuePtr(class_name);
|
1052
|
+
char* c_method_name = rb_id2name(mid);
|
1053
|
+
VALUE generated_name = method_name(klass, mid);
|
1054
|
+
char* c_generated_name = StringValuePtr(generated_name);
|
1055
|
+
printf("Event: %2d, Method: %s#%s\n", event, c_class_name, c_method_name);
|
1056
|
+
}*/
|
1057
|
+
|
1058
|
+
thread = rb_thread_current();
|
1059
|
+
thread_data = threads_table_lookup(threads_tbl, thread);
|
1060
|
+
|
1061
|
+
switch (event) {
|
1062
|
+
case RUBY_EVENT_CALL:
|
1063
|
+
case RUBY_EVENT_C_CALL:
|
1064
|
+
{
|
1065
|
+
st_data_t key = method_key(klass, mid);
|
1066
|
+
prof_method_t *child = minfo_table_lookup(thread_data->minfo_table, key);
|
1067
|
+
|
1068
|
+
if (child == NULL) {
|
1069
|
+
child = prof_method_create(klass, mid, thread);
|
1070
|
+
minfo_table_insert(thread_data->minfo_table, key, child);
|
1071
|
+
}
|
1072
|
+
|
1073
|
+
/* Increment count of number of times this child has been called on
|
1074
|
+
the current stack. */
|
1075
|
+
child->stack_count++;
|
1076
|
+
|
1077
|
+
/* Push the data for this method onto our stack */
|
1078
|
+
data = stack_push(thread_data->stack);
|
1079
|
+
data->method_info = child;
|
1080
|
+
data->start_time = get_clock();
|
1081
|
+
data->child_cost = 0;
|
1082
|
+
|
1083
|
+
break;
|
1084
|
+
}
|
1085
|
+
case RUBY_EVENT_RETURN:
|
1086
|
+
case RUBY_EVENT_C_RETURN:
|
1087
|
+
{
|
1088
|
+
prof_data_t* caller;
|
1089
|
+
prof_method_t *parent;
|
1090
|
+
prof_method_t *child;
|
1091
|
+
prof_clock_t now = get_clock();
|
1092
|
+
prof_clock_t total_time, self_time;
|
1093
|
+
|
1094
|
+
/* Pop data for this method off the stack. */
|
1095
|
+
data = stack_pop(thread_data->stack);
|
1096
|
+
|
1097
|
+
if (data == NULL)
|
1098
|
+
{
|
1099
|
+
/* Can happen on exceptions. The stack gets unwound without RubyProf.stop
|
1100
|
+
being called. */
|
1101
|
+
VALUE name = method_name(klass, mid);
|
1102
|
+
VALUE message = rb_str_new2("ruby-prof: An error occured when leaving the method %s.\n");
|
1103
|
+
rb_str_cat2(message, " Perhaps an exception occured in the code being profiled?\n" );
|
1104
|
+
|
1105
|
+
rb_warn(StringValuePtr(message), StringValuePtr(name));
|
1106
|
+
|
1107
|
+
return;
|
1108
|
+
}
|
1109
|
+
|
1110
|
+
/* Update timing information. */
|
1111
|
+
total_time = now - data->start_time;
|
1112
|
+
self_time = total_time - data->child_cost;
|
1113
|
+
|
1114
|
+
/* Okay, get the method that called this method (ie, parent) */
|
1115
|
+
caller = stack_peek(thread_data->stack);
|
1116
|
+
|
1117
|
+
if (caller == NULL)
|
1118
|
+
{
|
1119
|
+
/* We are at the top of the stack, so grab the toplevel method */
|
1120
|
+
parent = minfo_table_lookup(thread_data->minfo_table, toplevel_key);
|
1121
|
+
}
|
1122
|
+
else
|
1123
|
+
{
|
1124
|
+
caller->child_cost += total_time;
|
1125
|
+
parent = caller->method_info;
|
1126
|
+
}
|
1127
|
+
|
1128
|
+
/* Decrement count of number of times this child has been called on
|
1129
|
+
the current stack. */
|
1130
|
+
child = data->method_info;
|
1131
|
+
child->stack_count--;
|
1132
|
+
|
1133
|
+
/* If the stack count is greater than zero, then this
|
1134
|
+
method has been called recursively. In that case set the total
|
1135
|
+
time to zero because it will be correctly set when we unwind
|
1136
|
+
the stack up. If we don't do this, then the total time for the
|
1137
|
+
method will be double counted per recursive call. */
|
1138
|
+
if (child->stack_count != 0)
|
1139
|
+
total_time = 0;
|
1140
|
+
|
1141
|
+
update_result(parent, child, total_time, self_time);
|
1142
|
+
break;
|
1143
|
+
}
|
1144
|
+
}
|
1145
|
+
profiling--;
|
1146
|
+
}
|
1147
|
+
|
1148
|
+
|
1149
|
+
/* ======== ProfResult ============== */
|
1150
|
+
|
1151
|
+
/* Document-class: RubyProf::Result
|
1152
|
+
The RubyProf::Result class is used to store the results of a
|
1153
|
+
profiling run. And instace of the class is returned from
|
1154
|
+
the methods RubyProf#stop and RubyProf#profile.
|
1155
|
+
|
1156
|
+
RubyProf::Result has one field, called threads, which is a hash
|
1157
|
+
table keyed on thread ID. For each thread id, the hash table
|
1158
|
+
stores another hash table that contains profiling information
|
1159
|
+
for each method called during the threads execution. That
|
1160
|
+
hash table is keyed on method name and contains
|
1161
|
+
RubyProf::MethodInfo objects. */
|
1162
|
+
|
1163
|
+
|
1164
|
+
static void
|
1165
|
+
prof_result_mark(prof_result_t *prof_result)
|
1166
|
+
{
|
1167
|
+
VALUE threads = prof_result->threads;
|
1168
|
+
rb_gc_mark(threads);
|
1169
|
+
}
|
1170
|
+
|
1171
|
+
static void
|
1172
|
+
prof_result_free(prof_result_t *prof_result)
|
1173
|
+
{
|
1174
|
+
prof_result->threads = Qnil;
|
1175
|
+
xfree(prof_result);
|
1176
|
+
}
|
1177
|
+
|
1178
|
+
static VALUE
|
1179
|
+
prof_result_new()
|
1180
|
+
{
|
1181
|
+
prof_result_t *prof_result = ALLOC(prof_result_t);
|
1182
|
+
|
1183
|
+
/* Wrap threads in Ruby regular Ruby hash table. */
|
1184
|
+
prof_result->threads = rb_hash_new();
|
1185
|
+
st_foreach(threads_tbl, collect_threads, prof_result->threads);
|
1186
|
+
|
1187
|
+
return Data_Wrap_Struct(cResult, prof_result_mark, prof_result_free, prof_result);
|
1188
|
+
}
|
1189
|
+
|
1190
|
+
|
1191
|
+
static prof_result_t *
|
1192
|
+
get_prof_result(VALUE obj)
|
1193
|
+
{
|
1194
|
+
if (TYPE(obj) != T_DATA ||
|
1195
|
+
RDATA(obj)->dfree != (RUBY_DATA_FUNC) prof_result_free)
|
1196
|
+
{
|
1197
|
+
/* Should never happen */
|
1198
|
+
rb_raise(rb_eTypeError, "wrong result object");
|
1199
|
+
}
|
1200
|
+
return (prof_result_t *) DATA_PTR(obj);
|
1201
|
+
}
|
1202
|
+
|
1203
|
+
/* call-seq:
|
1204
|
+
threads -> Hash
|
1205
|
+
|
1206
|
+
Returns a hash table keyed on thread ID. For each thread id,
|
1207
|
+
the hash table stores another hash table that contains profiling
|
1208
|
+
information for each method called during the threads execution.
|
1209
|
+
That hash table is keyed on method name and contains
|
1210
|
+
RubyProf::MethodInfo objects. */
|
1211
|
+
static VALUE
|
1212
|
+
prof_result_threads(VALUE self)
|
1213
|
+
{
|
1214
|
+
prof_result_t *prof_result = get_prof_result(self);
|
1215
|
+
return prof_result->threads;
|
1216
|
+
}
|
1217
|
+
|
1218
|
+
|
1219
|
+
/* call-seq:
|
1220
|
+
thread_id = int
|
1221
|
+
toplevel(thread_id) -> RubyProf::MethodInfo
|
1222
|
+
|
1223
|
+
Returns the RubyProf::MethodInfo object that represents the root
|
1224
|
+
calling method for this thread. This method will always
|
1225
|
+
be named #toplevel and contains the total amount of time spent
|
1226
|
+
executing code in this thread. */
|
1227
|
+
static VALUE
|
1228
|
+
prof_result_toplevel(VALUE self, VALUE thread_id)
|
1229
|
+
{
|
1230
|
+
prof_result_t *prof_result = get_prof_result(self);
|
1231
|
+
VALUE methods = rb_hash_aref(prof_result->threads, thread_id);
|
1232
|
+
VALUE key = method_name(Qnil, toplevel_id);
|
1233
|
+
VALUE result = rb_hash_aref(methods, key);
|
1234
|
+
|
1235
|
+
if (result == Qnil)
|
1236
|
+
{
|
1237
|
+
/* Should never happen */
|
1238
|
+
rb_raise(rb_eRuntimeError, "Could not find toplevel method information");
|
1239
|
+
}
|
1240
|
+
return result;
|
1241
|
+
}
|
1242
|
+
|
1243
|
+
|
1244
|
+
/* call-seq:
|
1245
|
+
clock_mode -> clock_mode
|
1246
|
+
|
1247
|
+
Returns the current clock mode. Valid values include:
|
1248
|
+
*RubyProf::PROCESS_TIME - Measure process time. This is default. It is implemented using the clock function in the C Runtime library.
|
1249
|
+
*RubyProf::WALL_TIME - Measure wall time using gettimeofday on Linx and GetLocalTime on Windows
|
1250
|
+
*RubyProf::CPU_TIME - Measure time using the CPU clock counter. This mode is only supported on Pentium or PowerPC platforms. */
|
1251
|
+
static VALUE
|
1252
|
+
prof_get_clock_mode(VALUE self)
|
1253
|
+
{
|
1254
|
+
return INT2NUM(clock_mode);
|
1255
|
+
}
|
1256
|
+
|
1257
|
+
/* call-seq:
|
1258
|
+
clock_mode=value -> void
|
1259
|
+
|
1260
|
+
Specifies the method ruby-prof uses to measure time. Valid values include:
|
1261
|
+
*RubyProf::PROCESS_TIME - Measure process time. This is default. It is implemented using the clock function in the C Runtime library.
|
1262
|
+
*RubyProf::WALL_TIME - Measure wall time using gettimeofday on Linx and GetLocalTime on Windows
|
1263
|
+
*RubyProf::CPU_TIME - Measure time using the CPU clock counter. This mode is only supported on Pentium or PowerPC platforms. */
|
1264
|
+
static VALUE
|
1265
|
+
prof_set_clock_mode(VALUE self, VALUE val)
|
1266
|
+
{
|
1267
|
+
int mode = NUM2INT(val);
|
1268
|
+
|
1269
|
+
if (threads_tbl)
|
1270
|
+
{
|
1271
|
+
rb_raise(rb_eRuntimeError, "can't set clock_mode while profiling");
|
1272
|
+
}
|
1273
|
+
|
1274
|
+
switch (mode) {
|
1275
|
+
case CLOCK_MODE_PROCESS:
|
1276
|
+
get_clock = clock_get_clock;
|
1277
|
+
clock2sec = clock_clock2sec;
|
1278
|
+
break;
|
1279
|
+
case CLOCK_MODE_WALL:
|
1280
|
+
get_clock = gettimeofday_get_clock;
|
1281
|
+
clock2sec = gettimeofday_clock2sec;
|
1282
|
+
break;
|
1283
|
+
#ifdef CLOCK_MODE_CPU
|
1284
|
+
case CLOCK_MODE_CPU:
|
1285
|
+
if (cpu_frequency == 0)
|
1286
|
+
cpu_frequency = get_cpu_frequency();
|
1287
|
+
get_clock = cpu_get_clock;
|
1288
|
+
clock2sec = cpu_clock2sec;
|
1289
|
+
break;
|
1290
|
+
#endif
|
1291
|
+
default:
|
1292
|
+
rb_raise(rb_eArgError, "invalid mode: %d", mode);
|
1293
|
+
break;
|
1294
|
+
}
|
1295
|
+
clock_mode = mode;
|
1296
|
+
return val;
|
1297
|
+
}
|
1298
|
+
|
1299
|
+
/* ========= Profiling ============= */
|
1300
|
+
|
1301
|
+
|
1302
|
+
/* call-seq:
|
1303
|
+
start -> void
|
1304
|
+
|
1305
|
+
Starts recording profile data.*/
|
1306
|
+
static VALUE
|
1307
|
+
prof_start(VALUE self)
|
1308
|
+
{
|
1309
|
+
toplevel_id = rb_intern("toplevel");
|
1310
|
+
toplevel_key = method_key(Qnil, toplevel_id);
|
1311
|
+
|
1312
|
+
if (threads_tbl != NULL)
|
1313
|
+
{
|
1314
|
+
rb_raise(rb_eRuntimeError, "RubyProf.start was already called");
|
1315
|
+
}
|
1316
|
+
|
1317
|
+
/* Setup globals */
|
1318
|
+
class_tbl = rb_hash_new();
|
1319
|
+
threads_tbl = threads_table_create();
|
1320
|
+
|
1321
|
+
rb_add_event_hook(prof_event_hook,
|
1322
|
+
RUBY_EVENT_CALL | RUBY_EVENT_RETURN |
|
1323
|
+
RUBY_EVENT_C_CALL | RUBY_EVENT_C_RETURN);
|
1324
|
+
|
1325
|
+
return Qnil;
|
1326
|
+
}
|
1327
|
+
|
1328
|
+
|
1329
|
+
/* call-seq:
|
1330
|
+
stop -> RubyProf::Result
|
1331
|
+
|
1332
|
+
Stops collecting profile data and returns a RubyProf::Result object. */
|
1333
|
+
static VALUE
|
1334
|
+
prof_stop(VALUE self)
|
1335
|
+
{
|
1336
|
+
VALUE result = Qnil;
|
1337
|
+
|
1338
|
+
if (threads_tbl == NULL)
|
1339
|
+
{
|
1340
|
+
rb_raise(rb_eRuntimeError, "RubyProf.start is not called yet");
|
1341
|
+
}
|
1342
|
+
|
1343
|
+
/* Now unregister from event */
|
1344
|
+
rb_remove_event_hook(prof_event_hook);
|
1345
|
+
|
1346
|
+
/* Create the result */
|
1347
|
+
result = prof_result_new();
|
1348
|
+
|
1349
|
+
/* Free threads table */
|
1350
|
+
free_threads(threads_tbl);
|
1351
|
+
threads_table_free(threads_tbl);
|
1352
|
+
threads_tbl = NULL;
|
1353
|
+
|
1354
|
+
/* Free reference to class_tbl */
|
1355
|
+
class_tbl = Qnil;
|
1356
|
+
|
1357
|
+
return result;
|
1358
|
+
}
|
1359
|
+
|
1360
|
+
|
1361
|
+
/* call-seq:
|
1362
|
+
profile {block} -> RubyProf::Result
|
1363
|
+
|
1364
|
+
Profiles the specified block and returns a RubyProf::Result object. */
|
1365
|
+
static VALUE
|
1366
|
+
prof_profile(VALUE self)
|
1367
|
+
{
|
1368
|
+
if (!rb_block_given_p())
|
1369
|
+
{
|
1370
|
+
rb_raise(rb_eArgError, "A block must be provided to the profile method.");
|
1371
|
+
}
|
1372
|
+
|
1373
|
+
prof_start(self);
|
1374
|
+
rb_yield(Qnil);
|
1375
|
+
return prof_stop(self);
|
1376
|
+
}
|
1377
|
+
|
1378
|
+
|
1379
|
+
#if defined(_WIN32)
|
1380
|
+
__declspec(dllexport)
|
1381
|
+
#endif
|
1382
|
+
void
|
1383
|
+
|
1384
|
+
Init_ruby_prof()
|
1385
|
+
{
|
1386
|
+
mProf = rb_define_module("RubyProf");
|
1387
|
+
rb_define_const(mProf, "VERSION", rb_str_new2(PROF_VERSION));
|
1388
|
+
rb_define_module_function(mProf, "start", prof_start, 0);
|
1389
|
+
rb_define_module_function(mProf, "stop", prof_stop, 0);
|
1390
|
+
rb_define_module_function(mProf, "profile", prof_profile, 0);
|
1391
|
+
rb_define_singleton_method(mProf, "clock_mode", prof_get_clock_mode, 0);
|
1392
|
+
rb_define_singleton_method(mProf, "clock_mode=", prof_set_clock_mode, 1);
|
1393
|
+
rb_define_const(mProf, "PROCESS_TIME", INT2NUM(CLOCK_MODE_PROCESS));
|
1394
|
+
rb_define_const(mProf, "WALL_TIME", INT2NUM(CLOCK_MODE_WALL));
|
1395
|
+
#ifdef CLOCK_MODE_CPU
|
1396
|
+
rb_define_const(mProf, "CPU_TIME", INT2NUM(CLOCK_MODE_CPU));
|
1397
|
+
rb_define_singleton_method(mProf, "cpu_frequency",
|
1398
|
+
prof_get_cpu_frequency, 0);
|
1399
|
+
rb_define_singleton_method(mProf, "cpu_frequency=",
|
1400
|
+
prof_set_cpu_freqeuncy, 1);
|
1401
|
+
#endif
|
1402
|
+
|
1403
|
+
cResult = rb_define_class_under(mProf, "Result", rb_cObject);
|
1404
|
+
rb_undef_method(CLASS_OF(cMethodInfo), "new");
|
1405
|
+
rb_define_method(cResult, "threads", prof_result_threads, 0);
|
1406
|
+
rb_define_method(cResult, "toplevel", prof_result_toplevel, 1);
|
1407
|
+
|
1408
|
+
cMethodInfo = rb_define_class_under(mProf, "MethodInfo", rb_cObject);
|
1409
|
+
rb_include_module(cMethodInfo, rb_mComparable);
|
1410
|
+
rb_undef_method(CLASS_OF(cMethodInfo), "new");
|
1411
|
+
rb_define_method(cMethodInfo, "called", prof_method_called, 0);
|
1412
|
+
rb_define_method(cMethodInfo, "total_time", prof_method_total_time, 0);
|
1413
|
+
rb_define_method(cMethodInfo, "self_time", prof_method_self_time, 0);
|
1414
|
+
rb_define_method(cMethodInfo, "children_time", prof_method_children_time, 0);
|
1415
|
+
rb_define_method(cMethodInfo, "name", prof_method_name, 0);
|
1416
|
+
rb_define_method(cMethodInfo, "method_class", prof_method_class, 0);
|
1417
|
+
rb_define_method(cMethodInfo, "method_id", prof_method_id, 0);
|
1418
|
+
rb_define_method(cMethodInfo, "thread_id", prof_thread_id, 0);
|
1419
|
+
rb_define_method(cMethodInfo, "parents", prof_method_parents, 0);
|
1420
|
+
rb_define_method(cMethodInfo, "children", prof_method_children, 0);
|
1421
|
+
rb_define_method(cMethodInfo, "<=>", prof_method_cmp, 1);
|
1422
|
+
|
1423
|
+
cCallInfo = rb_define_class_under(mProf, "CallInfo", rb_cObject);
|
1424
|
+
rb_undef_method(CLASS_OF(cCallInfo), "new");
|
1425
|
+
rb_define_method(cCallInfo, "called", call_info_called, 0);
|
1426
|
+
rb_define_method(cCallInfo, "total_time", call_info_total_time, 0);
|
1427
|
+
rb_define_method(cCallInfo, "self_time", call_info_self_time, 0);
|
1428
|
+
rb_define_method(cCallInfo, "children_time", call_info_children_time, 0);
|
1429
|
+
|
1430
|
+
rb_global_variable(&class_tbl);
|
1431
|
+
}
|
1432
|
+
|
1433
|
+
/* vim: set filetype=c ts=8 sw=4 noexpandtab : */
|