ruby-prof 0.18.0 → 1.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.
- checksums.yaml +4 -4
- data/CHANGES +44 -1
- data/LICENSE +2 -2
- data/README.rdoc +1 -483
- data/Rakefile +3 -6
- data/bin/ruby-prof +111 -128
- data/ext/ruby_prof/extconf.rb +6 -38
- data/ext/ruby_prof/rp_aggregate_call_tree.c +41 -0
- data/ext/ruby_prof/rp_aggregate_call_tree.h +13 -0
- data/ext/ruby_prof/rp_allocation.c +259 -0
- data/ext/ruby_prof/rp_allocation.h +31 -0
- data/ext/ruby_prof/rp_call_tree.c +353 -0
- data/ext/ruby_prof/rp_call_tree.h +43 -0
- data/ext/ruby_prof/rp_call_trees.c +266 -0
- data/ext/ruby_prof/rp_call_trees.h +29 -0
- data/ext/ruby_prof/rp_measure_allocations.c +25 -51
- data/ext/ruby_prof/rp_measure_memory.c +21 -56
- data/ext/ruby_prof/rp_measure_process_time.c +37 -43
- data/ext/ruby_prof/rp_measure_wall_time.c +40 -21
- data/ext/ruby_prof/rp_measurement.c +221 -0
- data/ext/ruby_prof/rp_measurement.h +50 -0
- data/ext/ruby_prof/rp_method.c +279 -439
- data/ext/ruby_prof/rp_method.h +33 -45
- data/ext/ruby_prof/rp_profile.c +902 -0
- data/ext/ruby_prof/rp_profile.h +36 -0
- data/ext/ruby_prof/rp_stack.c +163 -132
- data/ext/ruby_prof/rp_stack.h +18 -28
- data/ext/ruby_prof/rp_thread.c +192 -124
- data/ext/ruby_prof/rp_thread.h +18 -8
- data/ext/ruby_prof/ruby_prof.c +36 -778
- data/ext/ruby_prof/ruby_prof.h +11 -45
- data/ext/ruby_prof/vc/ruby_prof.vcxproj +18 -12
- data/lib/ruby-prof.rb +4 -21
- data/lib/ruby-prof/assets/call_stack_printer.html.erb +710 -0
- data/lib/ruby-prof/assets/call_stack_printer.png +0 -0
- data/lib/ruby-prof/assets/graph_printer.html.erb +355 -0
- data/lib/ruby-prof/call_tree.rb +57 -0
- data/lib/ruby-prof/call_tree_visitor.rb +36 -0
- data/lib/ruby-prof/compatibility.rb +37 -107
- data/lib/ruby-prof/exclude_common_methods.rb +198 -0
- data/lib/ruby-prof/measurement.rb +17 -0
- data/lib/ruby-prof/method_info.rb +47 -90
- data/lib/ruby-prof/printers/abstract_printer.rb +73 -50
- data/lib/ruby-prof/printers/call_info_printer.rb +24 -12
- data/lib/ruby-prof/printers/call_stack_printer.rb +66 -152
- data/lib/ruby-prof/printers/call_tree_printer.rb +20 -12
- data/lib/ruby-prof/printers/dot_printer.rb +5 -5
- data/lib/ruby-prof/printers/flat_printer.rb +6 -24
- data/lib/ruby-prof/printers/graph_html_printer.rb +6 -192
- data/lib/ruby-prof/printers/graph_printer.rb +11 -14
- data/lib/ruby-prof/printers/multi_printer.rb +66 -23
- data/lib/ruby-prof/profile.rb +10 -3
- data/lib/ruby-prof/thread.rb +5 -20
- data/lib/ruby-prof/version.rb +1 -1
- data/ruby-prof.gemspec +9 -2
- data/test/abstract_printer_test.rb +0 -27
- data/test/alias_test.rb +126 -0
- data/test/basic_test.rb +1 -86
- data/test/call_tree_visitor_test.rb +32 -0
- data/test/call_trees_test.rb +66 -0
- data/test/dynamic_method_test.rb +0 -2
- data/test/exclude_methods_test.rb +17 -12
- data/test/fiber_test.rb +214 -23
- data/test/gc_test.rb +105 -0
- data/test/inverse_call_tree_test.rb +175 -0
- data/test/line_number_test.rb +118 -40
- data/test/marshal_test.rb +115 -0
- data/test/measure_allocations.rb +30 -0
- data/test/measure_allocations_test.rb +361 -12
- data/test/measure_allocations_trace_test.rb +375 -0
- data/test/measure_memory_trace_test.rb +1101 -0
- data/test/measure_process_time_test.rb +757 -33
- data/test/measure_times.rb +56 -0
- data/test/measure_wall_time_test.rb +329 -149
- data/test/multi_printer_test.rb +1 -34
- data/test/pause_resume_test.rb +24 -15
- data/test/prime.rb +1 -1
- data/test/prime_script.rb +6 -0
- data/test/printer_call_stack_test.rb +28 -0
- data/test/printer_call_tree_test.rb +31 -0
- data/test/printer_flat_test.rb +68 -0
- data/test/printer_graph_html_test.rb +60 -0
- data/test/printer_graph_test.rb +41 -0
- data/test/printers_test.rb +32 -166
- data/test/printing_recursive_graph_test.rb +26 -72
- data/test/recursive_test.rb +68 -77
- data/test/stack_printer_test.rb +2 -15
- data/test/start_stop_test.rb +22 -25
- data/test/test_helper.rb +6 -261
- data/test/thread_test.rb +11 -54
- data/test/unique_call_path_test.rb +25 -107
- data/test/yarv_test.rb +1 -0
- metadata +43 -41
- data/examples/flat.txt +0 -50
- data/examples/graph.dot +0 -84
- data/examples/graph.html +0 -823
- data/examples/graph.txt +0 -139
- data/examples/multi.flat.txt +0 -23
- data/examples/multi.graph.html +0 -760
- data/examples/multi.grind.dat +0 -114
- data/examples/multi.stack.html +0 -547
- data/examples/stack.html +0 -547
- data/ext/ruby_prof/rp_call_info.c +0 -425
- data/ext/ruby_prof/rp_call_info.h +0 -53
- data/ext/ruby_prof/rp_measure.c +0 -40
- data/ext/ruby_prof/rp_measure.h +0 -45
- data/ext/ruby_prof/rp_measure_cpu_time.c +0 -136
- data/ext/ruby_prof/rp_measure_gc_runs.c +0 -73
- data/ext/ruby_prof/rp_measure_gc_time.c +0 -60
- data/lib/ruby-prof/aggregate_call_info.rb +0 -76
- data/lib/ruby-prof/assets/call_stack_printer.css.html +0 -117
- data/lib/ruby-prof/assets/call_stack_printer.js.html +0 -385
- data/lib/ruby-prof/call_info.rb +0 -115
- data/lib/ruby-prof/call_info_visitor.rb +0 -40
- data/lib/ruby-prof/printers/flat_printer_with_line_numbers.rb +0 -83
- data/lib/ruby-prof/profile/exclude_common_methods.rb +0 -207
- data/lib/ruby-prof/profile/legacy_method_elimination.rb +0 -50
- data/test/aggregate_test.rb +0 -136
- data/test/block_test.rb +0 -74
- data/test/call_info_test.rb +0 -78
- data/test/call_info_visitor_test.rb +0 -31
- data/test/issue137_test.rb +0 -63
- data/test/measure_cpu_time_test.rb +0 -212
- data/test/measure_gc_runs_test.rb +0 -32
- data/test/measure_gc_time_test.rb +0 -36
- data/test/measure_memory_test.rb +0 -33
- data/test/method_elimination_test.rb +0 -84
- data/test/module_test.rb +0 -45
- data/test/stack_test.rb +0 -138
@@ -0,0 +1,36 @@
|
|
1
|
+
/* Copyright (C) 2005-2019 Shugo Maeda <shugo@ruby-lang.org> and Charlie Savage <cfis@savagexi.com>
|
2
|
+
Please see the LICENSE file for copyright and distribution information */
|
3
|
+
|
4
|
+
#ifndef __RP_PROFILE_H__
|
5
|
+
#define __RP_PROFILE_H__
|
6
|
+
|
7
|
+
#include "ruby_prof.h"
|
8
|
+
#include "rp_measurement.h"
|
9
|
+
#include "rp_thread.h"
|
10
|
+
|
11
|
+
extern VALUE cProfile;
|
12
|
+
|
13
|
+
typedef struct
|
14
|
+
{
|
15
|
+
VALUE running;
|
16
|
+
VALUE paused;
|
17
|
+
|
18
|
+
prof_measurer_t* measurer;
|
19
|
+
|
20
|
+
VALUE tracepoints;
|
21
|
+
|
22
|
+
st_table* threads_tbl;
|
23
|
+
st_table* exclude_threads_tbl;
|
24
|
+
st_table* include_threads_tbl;
|
25
|
+
st_table* exclude_methods_tbl;
|
26
|
+
thread_data_t* last_thread_data;
|
27
|
+
double measurement_at_pause_resume;
|
28
|
+
bool allow_exceptions;
|
29
|
+
bool merge_fibers;
|
30
|
+
} prof_profile_t;
|
31
|
+
|
32
|
+
void rp_init_profile(void);
|
33
|
+
prof_profile_t* prof_get_profile(VALUE self);
|
34
|
+
|
35
|
+
|
36
|
+
#endif //__RP_PROFILE_H__
|
data/ext/ruby_prof/rp_stack.c
CHANGED
@@ -3,171 +3,202 @@
|
|
3
3
|
|
4
4
|
#include "rp_stack.h"
|
5
5
|
|
6
|
-
#define INITIAL_STACK_SIZE
|
6
|
+
#define INITIAL_STACK_SIZE 16
|
7
7
|
|
8
|
-
|
9
|
-
|
8
|
+
// Creates a stack of prof_frame_t to keep track of timings for active methods.
|
9
|
+
prof_stack_t* prof_stack_create()
|
10
10
|
{
|
11
|
-
|
12
|
-
|
11
|
+
prof_stack_t* stack = ALLOC(prof_stack_t);
|
12
|
+
stack->start = ZALLOC_N(prof_frame_t, INITIAL_STACK_SIZE);
|
13
|
+
stack->ptr = stack->start;
|
14
|
+
stack->end = stack->start + INITIAL_STACK_SIZE;
|
15
|
+
|
16
|
+
return stack;
|
13
17
|
}
|
14
18
|
|
15
|
-
void
|
16
|
-
prof_frame_unpause(prof_frame_t *frame, double current_measurement)
|
19
|
+
void prof_stack_free(prof_stack_t* stack)
|
17
20
|
{
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
+
xfree(stack->start);
|
22
|
+
xfree(stack);
|
23
|
+
}
|
24
|
+
|
25
|
+
prof_frame_t* prof_stack_last(prof_stack_t* stack)
|
26
|
+
{
|
27
|
+
if (stack->ptr == stack->start)
|
28
|
+
return NULL;
|
29
|
+
else
|
30
|
+
return stack->ptr - 1;
|
31
|
+
}
|
32
|
+
|
33
|
+
void prof_stack_verify_size(prof_stack_t* stack)
|
34
|
+
{
|
35
|
+
// Is there space on the stack? If not, double its size.
|
36
|
+
if (stack->ptr == stack->end)
|
37
|
+
{
|
38
|
+
size_t len = stack->ptr - stack->start;
|
39
|
+
size_t new_capacity = (stack->end - stack->start) * 2;
|
40
|
+
REALLOC_N(stack->start, prof_frame_t, new_capacity);
|
41
|
+
|
42
|
+
/* Memory just got moved, reset pointers */
|
43
|
+
stack->ptr = stack->start + len;
|
44
|
+
stack->end = stack->start + new_capacity;
|
21
45
|
}
|
22
46
|
}
|
23
47
|
|
48
|
+
prof_frame_t* prof_stack_push(prof_stack_t* stack)
|
49
|
+
{
|
50
|
+
prof_stack_verify_size(stack);
|
51
|
+
|
52
|
+
prof_frame_t* result = stack->ptr;
|
53
|
+
stack->ptr++;
|
54
|
+
return result;
|
55
|
+
}
|
24
56
|
|
25
|
-
|
26
|
-
of timings for active methods. */
|
27
|
-
prof_stack_t *
|
28
|
-
prof_stack_create()
|
57
|
+
prof_frame_t* prof_stack_pop(prof_stack_t* stack)
|
29
58
|
{
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
stack->end = stack->start + INITIAL_STACK_SIZE;
|
59
|
+
prof_frame_t* result = prof_stack_last(stack);
|
60
|
+
if (result)
|
61
|
+
stack->ptr--;
|
34
62
|
|
35
|
-
return
|
63
|
+
return result;
|
36
64
|
}
|
37
65
|
|
38
|
-
|
39
|
-
|
66
|
+
// ---------------- Frame Methods ----------------------------
|
67
|
+
void prof_frame_pause(prof_frame_t* frame, double current_measurement)
|
40
68
|
{
|
41
|
-
|
42
|
-
|
69
|
+
if (frame && prof_frame_is_unpaused(frame))
|
70
|
+
frame->pause_time = current_measurement;
|
43
71
|
}
|
44
72
|
|
45
|
-
prof_frame_t
|
46
|
-
prof_stack_push(prof_stack_t *stack, prof_call_info_t *call_info, double measurement, int paused)
|
73
|
+
void prof_frame_unpause(prof_frame_t* frame, double current_measurement)
|
47
74
|
{
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
its size. */
|
54
|
-
if (stack->ptr == stack->end)
|
55
|
-
{
|
56
|
-
size_t len = stack->ptr - stack->start;
|
57
|
-
size_t new_capacity = (stack->end - stack->start) * 2;
|
58
|
-
REALLOC_N(stack->start, prof_frame_t, new_capacity);
|
59
|
-
/* Memory just got moved, reset pointers */
|
60
|
-
stack->ptr = stack->start + len;
|
61
|
-
stack->end = stack->start + new_capacity;
|
62
|
-
}
|
63
|
-
|
64
|
-
parent_frame = prof_stack_peek(stack);
|
65
|
-
|
66
|
-
// Reserve the next available frame pointer.
|
67
|
-
result = stack->ptr++;
|
68
|
-
|
69
|
-
result->call_info = call_info;
|
70
|
-
result->call_info->depth = (int)(stack->ptr - stack->start); // shortening of 64 bit into 32;
|
71
|
-
result->passes = 0;
|
72
|
-
|
73
|
-
result->start_time = measurement;
|
74
|
-
result->pause_time = -1; // init as not paused.
|
75
|
-
result->switch_time = 0;
|
76
|
-
result->wait_time = 0;
|
77
|
-
result->child_time = 0;
|
78
|
-
result->dead_time = 0;
|
79
|
-
|
80
|
-
method = call_info->target;
|
81
|
-
|
82
|
-
/* If the method was visited previously, it's recursive. */
|
83
|
-
if (method->visits > 0)
|
84
|
-
{
|
85
|
-
method->recursive = 1;
|
86
|
-
call_info->recursive = 1;
|
87
|
-
}
|
88
|
-
/* Enter the method. */
|
89
|
-
method->visits++;
|
90
|
-
|
91
|
-
// Unpause the parent frame, if it exists.
|
92
|
-
// If currently paused then:
|
93
|
-
// 1) The child frame will begin paused.
|
94
|
-
// 2) The parent will inherit the child's dead time.
|
95
|
-
prof_frame_unpause(parent_frame, measurement);
|
96
|
-
|
97
|
-
if (paused) {
|
98
|
-
prof_frame_pause(result, measurement);
|
99
|
-
}
|
100
|
-
|
101
|
-
// Return the result
|
102
|
-
return result;
|
75
|
+
if (prof_frame_is_paused(frame))
|
76
|
+
{
|
77
|
+
frame->dead_time += (current_measurement - frame->pause_time);
|
78
|
+
frame->pause_time = -1;
|
79
|
+
}
|
103
80
|
}
|
104
81
|
|
105
|
-
prof_frame_t *
|
106
|
-
prof_stack_pop(prof_stack_t *stack, double measurement)
|
82
|
+
prof_frame_t* prof_frame_current(prof_stack_t* stack)
|
107
83
|
{
|
108
|
-
|
109
|
-
|
110
|
-
prof_call_info_t *call_info;
|
111
|
-
prof_method_t *method;
|
84
|
+
return prof_stack_last(stack);
|
85
|
+
}
|
112
86
|
|
113
|
-
|
114
|
-
|
87
|
+
prof_frame_t* prof_frame_push(prof_stack_t* stack, prof_call_tree_t* call_tree, double measurement, bool paused)
|
88
|
+
{
|
89
|
+
prof_frame_t* parent_frame = prof_stack_last(stack);
|
90
|
+
prof_frame_t* result = prof_stack_push(stack);
|
91
|
+
|
92
|
+
result->call_tree = call_tree;
|
93
|
+
|
94
|
+
result->start_time = measurement;
|
95
|
+
result->pause_time = -1; // init as not paused.
|
96
|
+
result->switch_time = 0;
|
97
|
+
result->wait_time = 0;
|
98
|
+
result->child_time = 0;
|
99
|
+
result->dead_time = 0;
|
100
|
+
result->source_file = Qnil;
|
101
|
+
result->source_line = 0;
|
102
|
+
|
103
|
+
call_tree->measurement->called++;
|
104
|
+
call_tree->visits++;
|
105
|
+
|
106
|
+
if (call_tree->method->visits > 0)
|
107
|
+
{
|
108
|
+
call_tree->method->recursive = true;
|
109
|
+
}
|
110
|
+
call_tree->method->measurement->called++;
|
111
|
+
call_tree->method->visits++;
|
112
|
+
|
113
|
+
// Unpause the parent frame, if it exists.
|
114
|
+
// If currently paused then:
|
115
|
+
// 1) The child frame will begin paused.
|
116
|
+
// 2) The parent will inherit the child's dead time.
|
117
|
+
if (parent_frame)
|
118
|
+
prof_frame_unpause(parent_frame, measurement);
|
119
|
+
|
120
|
+
if (paused)
|
121
|
+
{
|
122
|
+
prof_frame_pause(result, measurement);
|
123
|
+
}
|
115
124
|
|
116
|
-
|
125
|
+
// Return the result
|
126
|
+
return result;
|
127
|
+
}
|
117
128
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
if (!frame) {
|
123
|
-
return NULL;
|
124
|
-
}
|
129
|
+
prof_frame_t* prof_frame_unshift(prof_stack_t* stack, prof_call_tree_t* parent_call_tree, prof_call_tree_t* call_tree, double measurement)
|
130
|
+
{
|
131
|
+
if (prof_stack_last(stack))
|
132
|
+
rb_raise(rb_eRuntimeError, "Stach unshift can only be called with an empty stack");
|
125
133
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
134
|
+
parent_call_tree->measurement->total_time = call_tree->measurement->total_time;
|
135
|
+
parent_call_tree->measurement->self_time = 0;
|
136
|
+
parent_call_tree->measurement->wait_time = call_tree->measurement->wait_time;
|
137
|
+
|
138
|
+
parent_call_tree->method->measurement->total_time += call_tree->measurement->total_time;
|
139
|
+
parent_call_tree->method->measurement->wait_time += call_tree->measurement->wait_time;
|
140
|
+
|
141
|
+
return prof_frame_push(stack, parent_call_tree, measurement, false);
|
142
|
+
}
|
143
|
+
|
144
|
+
prof_frame_t* prof_frame_pop(prof_stack_t* stack, double measurement)
|
145
|
+
{
|
146
|
+
prof_frame_t* frame = prof_stack_pop(stack);
|
132
147
|
|
133
|
-
|
134
|
-
|
148
|
+
if (!frame)
|
149
|
+
return NULL;
|
135
150
|
|
136
|
-
|
137
|
-
|
138
|
-
total_time = measurement - frame->start_time - frame->dead_time;
|
139
|
-
self_time = total_time - frame->child_time - frame->wait_time;
|
151
|
+
/* Calculate the total time this method took */
|
152
|
+
prof_frame_unpause(frame, measurement);
|
140
153
|
|
141
|
-
|
142
|
-
|
143
|
-
method = call_info->target;
|
154
|
+
double total_time = measurement - frame->start_time - frame->dead_time;
|
155
|
+
double self_time = total_time - frame->child_time - frame->wait_time;
|
144
156
|
|
145
|
-
|
146
|
-
|
147
|
-
call_info->self_time += self_time;
|
148
|
-
call_info->wait_time += frame->wait_time;
|
157
|
+
/* Update information about the current method */
|
158
|
+
prof_call_tree_t* call_tree = frame->call_tree;
|
149
159
|
|
150
|
-
|
151
|
-
|
160
|
+
// Update method measurement
|
161
|
+
call_tree->method->measurement->self_time += self_time;
|
162
|
+
call_tree->method->measurement->wait_time += frame->wait_time;
|
163
|
+
if (call_tree->method->visits == 1)
|
164
|
+
call_tree->method->measurement->total_time += total_time;
|
152
165
|
|
153
|
-
|
154
|
-
if (parent_frame)
|
155
|
-
{
|
156
|
-
parent_frame->child_time += total_time;
|
157
|
-
parent_frame->dead_time += frame->dead_time;
|
166
|
+
call_tree->method->visits--;
|
158
167
|
|
159
|
-
|
160
|
-
|
168
|
+
// Update method measurement
|
169
|
+
call_tree->measurement->self_time += self_time;
|
170
|
+
call_tree->measurement->wait_time += frame->wait_time;
|
171
|
+
if (call_tree->visits == 1)
|
172
|
+
call_tree->measurement->total_time += total_time;
|
161
173
|
|
162
|
-
|
174
|
+
call_tree->visits--;
|
175
|
+
|
176
|
+
prof_frame_t* parent_frame = prof_stack_last(stack);
|
177
|
+
if (parent_frame)
|
178
|
+
{
|
179
|
+
parent_frame->child_time += total_time;
|
180
|
+
parent_frame->dead_time += frame->dead_time;
|
181
|
+
}
|
182
|
+
|
183
|
+
frame->source_file = Qnil;
|
184
|
+
|
185
|
+
return frame;
|
163
186
|
}
|
164
187
|
|
165
|
-
|
166
|
-
prof_stack_pass(prof_stack_t *stack)
|
188
|
+
prof_method_t* prof_find_method(prof_stack_t* stack, VALUE source_file, int source_line)
|
167
189
|
{
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
190
|
+
prof_frame_t* frame = stack->ptr;
|
191
|
+
while (frame >= stack->start)
|
192
|
+
{
|
193
|
+
if (!frame->call_tree)
|
194
|
+
return NULL;
|
195
|
+
|
196
|
+
if (rb_str_equal(source_file, frame->call_tree->method->source_file) &&
|
197
|
+
source_line >= frame->call_tree->method->source_line)
|
198
|
+
{
|
199
|
+
return frame->call_tree->method;
|
200
|
+
}
|
201
|
+
frame--;
|
202
|
+
}
|
203
|
+
return NULL;
|
173
204
|
}
|
data/ext/ruby_prof/rp_stack.h
CHANGED
@@ -4,23 +4,20 @@
|
|
4
4
|
#ifndef __RP_STACK__
|
5
5
|
#define __RP_STACK__
|
6
6
|
|
7
|
-
#include
|
7
|
+
#include "ruby_prof.h"
|
8
|
+
#include "rp_call_tree.h"
|
8
9
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
/* Temporary object that maintains profiling information
|
14
|
-
for active methods. They are created and destroyed
|
15
|
-
as the program moves up and down its stack. */
|
10
|
+
/* Temporary object that maintains profiling information
|
11
|
+
for active methods. They are created and destroyed
|
12
|
+
as the program moves up and down its stack. */
|
16
13
|
typedef struct
|
17
14
|
{
|
18
15
|
/* Caching prof_method_t values significantly
|
19
16
|
increases performance. */
|
20
|
-
|
17
|
+
prof_call_tree_t* call_tree;
|
21
18
|
|
22
|
-
|
23
|
-
unsigned int
|
19
|
+
VALUE source_file;
|
20
|
+
unsigned int source_line;
|
24
21
|
|
25
22
|
double start_time;
|
26
23
|
double switch_time; /* Time at switch to different thread */
|
@@ -30,9 +27,6 @@ typedef struct
|
|
30
27
|
double dead_time; // Time to ignore (i.e. total amount of time between pause/resume blocks)
|
31
28
|
} prof_frame_t;
|
32
29
|
|
33
|
-
#define prof_frame_is_real(f) ((f)->passes == 0)
|
34
|
-
#define prof_frame_is_pass(f) ((f)->passes > 0)
|
35
|
-
|
36
30
|
#define prof_frame_is_paused(f) (f->pause_time >= 0)
|
37
31
|
#define prof_frame_is_unpaused(f) (f->pause_time < 0)
|
38
32
|
|
@@ -42,22 +36,18 @@ void prof_frame_unpause(prof_frame_t*, double current_measurement);
|
|
42
36
|
/* Current stack of active methods.*/
|
43
37
|
typedef struct
|
44
38
|
{
|
45
|
-
prof_frame_t
|
46
|
-
prof_frame_t
|
47
|
-
prof_frame_t
|
39
|
+
prof_frame_t* start;
|
40
|
+
prof_frame_t* end;
|
41
|
+
prof_frame_t* ptr;
|
48
42
|
} prof_stack_t;
|
49
43
|
|
50
|
-
prof_stack_t
|
51
|
-
void prof_stack_free(prof_stack_t
|
52
|
-
|
53
|
-
prof_frame_t *prof_stack_push(prof_stack_t *stack, prof_call_info_t *call_info, double measurement, int paused);
|
54
|
-
prof_frame_t *prof_stack_pop(prof_stack_t *stack, double measurement);
|
55
|
-
prof_frame_t *prof_stack_pass(prof_stack_t *stack);
|
56
|
-
|
57
|
-
static inline prof_frame_t *
|
58
|
-
prof_stack_peek(prof_stack_t *stack) {
|
59
|
-
return stack->ptr != stack->start ? stack->ptr - 1 : NULL;
|
60
|
-
}
|
44
|
+
prof_stack_t* prof_stack_create(void);
|
45
|
+
void prof_stack_free(prof_stack_t* stack);
|
61
46
|
|
47
|
+
prof_frame_t* prof_frame_current(prof_stack_t* stack);
|
48
|
+
prof_frame_t* prof_frame_push(prof_stack_t* stack, prof_call_tree_t* call_tree, double measurement, bool paused);
|
49
|
+
prof_frame_t* prof_frame_unshift(prof_stack_t* stack, prof_call_tree_t* parent_call_tree, prof_call_tree_t* call_tree, double measurement);
|
50
|
+
prof_frame_t* prof_frame_pop(prof_stack_t* stack, double measurement);
|
51
|
+
prof_method_t* prof_find_method(prof_stack_t* stack, VALUE source_file, int source_line);
|
62
52
|
|
63
53
|
#endif //__RP_STACK__
|
data/ext/ruby_prof/rp_thread.c
CHANGED
@@ -1,91 +1,123 @@
|
|
1
1
|
/* Copyright (C) 2005-2013 Shugo Maeda <shugo@ruby-lang.org> and Charlie Savage <cfis@savagexi.com>
|
2
2
|
Please see the LICENSE file for copyright and distribution information */
|
3
3
|
|
4
|
-
|
4
|
+
/* Document-class: RubyProf::Thread
|
5
|
+
|
6
|
+
The Thread class contains profile results for a single fiber (note a Ruby thread can run multiple fibers).
|
7
|
+
You cannot create an instance of RubyProf::Thread, instead you access it from a RubyProf::Profile object.
|
8
|
+
|
9
|
+
profile = RubyProf::Profile.profile do
|
10
|
+
...
|
11
|
+
end
|
12
|
+
|
13
|
+
profile.threads.each do |thread|
|
14
|
+
thread.root_methods.sort.each do |method|
|
15
|
+
puts method.total_time
|
16
|
+
end
|
17
|
+
end */
|
18
|
+
|
19
|
+
#include "rp_thread.h"
|
20
|
+
#include "rp_profile.h"
|
5
21
|
|
6
22
|
VALUE cRpThread;
|
7
23
|
|
8
|
-
|
9
|
-
thread_data_t*
|
10
|
-
thread_data_create()
|
24
|
+
// ====== thread_data_t ======
|
25
|
+
thread_data_t* thread_data_create(void)
|
11
26
|
{
|
12
27
|
thread_data_t* result = ALLOC(thread_data_t);
|
13
28
|
result->stack = prof_stack_create();
|
14
29
|
result->method_table = method_table_create();
|
30
|
+
result->call_tree = NULL;
|
15
31
|
result->object = Qnil;
|
16
32
|
result->methods = Qnil;
|
33
|
+
result->fiber_id = Qnil;
|
34
|
+
result->thread_id = Qnil;
|
35
|
+
result->trace = true;
|
36
|
+
result->fiber = Qnil;
|
17
37
|
return result;
|
18
38
|
}
|
19
39
|
|
20
|
-
|
21
|
-
However, on shutdown the Ruby GC frees objects in any will-nilly order.
|
22
|
-
That means the ruby thread object wrapping the c thread struct may
|
23
|
-
be freed before the parent profile. Thus we add in a free function
|
24
|
-
for the garbage collector so that if it does get called will nil
|
25
|
-
out our Ruby object reference.*/
|
26
|
-
static void
|
27
|
-
thread_data_ruby_gc_free(thread_data_t* thread_data)
|
40
|
+
static int mark_methods(st_data_t key, st_data_t value, st_data_t result)
|
28
41
|
{
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
{
|
33
|
-
RDATA(thread_data->object)->data = NULL;
|
34
|
-
RDATA(thread_data->object)->dfree = NULL;
|
35
|
-
RDATA(thread_data->object)->dmark = NULL;
|
36
|
-
}
|
37
|
-
thread_data->object = Qnil;
|
42
|
+
prof_method_t* method = (prof_method_t*)value;
|
43
|
+
prof_method_mark(method);
|
44
|
+
return ST_CONTINUE;
|
38
45
|
}
|
39
46
|
|
40
|
-
|
41
|
-
thread_data_free(thread_data_t* thread_data)
|
47
|
+
size_t prof_thread_size(const void* data)
|
42
48
|
{
|
43
|
-
|
44
|
-
method_table_free(thread_data->method_table);
|
45
|
-
prof_stack_free(thread_data->stack);
|
46
|
-
|
47
|
-
thread_data->thread_id = Qnil;
|
48
|
-
|
49
|
-
xfree(thread_data);
|
49
|
+
return sizeof(prof_call_tree_t);
|
50
50
|
}
|
51
51
|
|
52
|
-
|
53
|
-
mark_methods(st_data_t key, st_data_t value, st_data_t result)
|
52
|
+
void prof_thread_mark(void* data)
|
54
53
|
{
|
55
|
-
|
56
|
-
prof_method_mark(method);
|
57
|
-
return ST_CONTINUE;
|
58
|
-
}
|
54
|
+
thread_data_t* thread = (thread_data_t*)data;
|
59
55
|
|
60
|
-
void
|
61
|
-
prof_thread_mark(thread_data_t *thread)
|
62
|
-
{
|
63
56
|
if (thread->object != Qnil)
|
64
57
|
rb_gc_mark(thread->object);
|
65
58
|
|
59
|
+
rb_gc_mark(thread->fiber);
|
60
|
+
|
66
61
|
if (thread->methods != Qnil)
|
67
62
|
rb_gc_mark(thread->methods);
|
68
63
|
|
64
|
+
if (thread->fiber_id != Qnil)
|
65
|
+
rb_gc_mark(thread->fiber_id);
|
66
|
+
|
69
67
|
if (thread->thread_id != Qnil)
|
70
68
|
rb_gc_mark(thread->thread_id);
|
71
69
|
|
72
|
-
if (thread->
|
73
|
-
|
70
|
+
if (thread->call_tree)
|
71
|
+
prof_call_tree_mark(thread->call_tree);
|
74
72
|
|
75
|
-
|
73
|
+
rb_st_foreach(thread->method_table, mark_methods, 0);
|
76
74
|
}
|
77
75
|
|
78
|
-
|
79
|
-
prof_thread_wrap(thread_data_t *thread)
|
76
|
+
void prof_thread_ruby_gc_free(void* data)
|
80
77
|
{
|
81
|
-
|
82
|
-
|
78
|
+
thread_data_t* thread_data = (thread_data_t*)data;
|
79
|
+
thread_data->object = Qnil;
|
80
|
+
}
|
81
|
+
|
82
|
+
static void prof_thread_free(thread_data_t* thread_data)
|
83
|
+
{
|
84
|
+
/* Has this method object been accessed by Ruby? If
|
85
|
+
yes then set its data to nil to avoid a segmentation fault on the next mark and sweep. */
|
86
|
+
if (thread_data->object != Qnil)
|
87
|
+
{
|
88
|
+
RDATA(thread_data->object)->dmark = NULL;
|
89
|
+
RDATA(thread_data->object)->dfree = NULL;
|
90
|
+
RDATA(thread_data->object)->data = NULL;
|
91
|
+
thread_data->object = Qnil;
|
92
|
+
}
|
93
|
+
|
94
|
+
method_table_free(thread_data->method_table);
|
95
|
+
|
96
|
+
if (thread_data->call_tree)
|
97
|
+
prof_call_tree_free(thread_data->call_tree);
|
98
|
+
|
99
|
+
prof_stack_free(thread_data->stack);
|
100
|
+
|
101
|
+
xfree(thread_data);
|
102
|
+
}
|
103
|
+
|
104
|
+
VALUE prof_thread_wrap(thread_data_t* thread)
|
105
|
+
{
|
106
|
+
if (thread->object == Qnil)
|
107
|
+
{
|
108
|
+
thread->object = Data_Wrap_Struct(cRpThread, prof_thread_mark, prof_thread_ruby_gc_free, thread);
|
83
109
|
}
|
84
110
|
return thread->object;
|
85
111
|
}
|
86
112
|
|
87
|
-
static
|
88
|
-
|
113
|
+
static VALUE prof_thread_allocate(VALUE klass)
|
114
|
+
{
|
115
|
+
thread_data_t* thread_data = thread_data_create();
|
116
|
+
thread_data->object = prof_thread_wrap(thread_data);
|
117
|
+
return thread_data->object;
|
118
|
+
}
|
119
|
+
|
120
|
+
static thread_data_t* prof_get_thread(VALUE self)
|
89
121
|
{
|
90
122
|
/* Can't use Data_Get_Struct because that triggers the event hook
|
91
123
|
ending up in endless recursion. */
|
@@ -96,105 +128,99 @@ prof_get_thread(VALUE self)
|
|
96
128
|
return result;
|
97
129
|
}
|
98
130
|
|
99
|
-
|
100
|
-
|
101
|
-
of thread_data_t. */
|
131
|
+
// ====== Thread Table ======
|
132
|
+
// The thread table is hash keyed on ruby fiber_id that stores instances of thread_data_t.
|
102
133
|
|
103
|
-
st_table
|
104
|
-
threads_table_create()
|
134
|
+
st_table* threads_table_create()
|
105
135
|
{
|
106
|
-
return
|
136
|
+
return rb_st_init_numtable();
|
107
137
|
}
|
108
138
|
|
109
|
-
static int
|
110
|
-
thread_table_free_iterator(st_data_t key, st_data_t value, st_data_t dummy)
|
139
|
+
static int thread_table_free_iterator(st_data_t key, st_data_t value, st_data_t dummy)
|
111
140
|
{
|
112
|
-
|
141
|
+
prof_thread_free((thread_data_t*)value);
|
113
142
|
return ST_CONTINUE;
|
114
143
|
}
|
115
144
|
|
116
|
-
void
|
117
|
-
threads_table_free(st_table *table)
|
145
|
+
void threads_table_free(st_table* table)
|
118
146
|
{
|
119
|
-
|
120
|
-
|
147
|
+
rb_st_foreach(table, thread_table_free_iterator, 0);
|
148
|
+
rb_st_free_table(table);
|
121
149
|
}
|
122
150
|
|
123
|
-
|
124
|
-
threads_table_insert(prof_profile_t* profile, VALUE key, thread_data_t *thread_data)
|
151
|
+
thread_data_t* threads_table_lookup(void* prof, VALUE fiber)
|
125
152
|
{
|
126
|
-
|
127
|
-
|
153
|
+
prof_profile_t* profile = prof;
|
154
|
+
thread_data_t* result = NULL;
|
155
|
+
st_data_t val;
|
156
|
+
|
157
|
+
if (rb_st_lookup(profile->threads_tbl, fiber, &val))
|
158
|
+
{
|
159
|
+
result = (thread_data_t*)val;
|
160
|
+
}
|
161
|
+
|
162
|
+
return result;
|
128
163
|
}
|
129
164
|
|
130
|
-
thread_data_t *
|
131
|
-
threads_table_lookup(prof_profile_t* profile, VALUE thread_id, VALUE fiber_id)
|
165
|
+
thread_data_t* threads_table_insert(void* prof, VALUE fiber)
|
132
166
|
{
|
133
|
-
|
134
|
-
|
167
|
+
prof_profile_t* profile = prof;
|
168
|
+
thread_data_t* result = thread_data_create();
|
169
|
+
VALUE thread = rb_thread_current();
|
135
170
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
if (
|
171
|
+
result->fiber = fiber;
|
172
|
+
result->fiber_id = rb_obj_id(fiber);
|
173
|
+
result->thread_id = rb_obj_id(thread);
|
174
|
+
rb_st_insert(profile->threads_tbl, (st_data_t)fiber, (st_data_t)result);
|
175
|
+
|
176
|
+
// Are we tracing this thread?
|
177
|
+
if (profile->include_threads_tbl && !rb_st_lookup(profile->include_threads_tbl, thread, 0))
|
178
|
+
{
|
179
|
+
result->trace = false;
|
180
|
+
}
|
181
|
+
else if (profile->exclude_threads_tbl && rb_st_lookup(profile->exclude_threads_tbl, thread, 0))
|
143
182
|
{
|
144
|
-
|
183
|
+
result->trace = false;
|
145
184
|
}
|
146
185
|
else
|
147
186
|
{
|
148
|
-
result =
|
149
|
-
result->thread_id = thread_id;
|
150
|
-
/* We set fiber id to 0 in the merge fiber case. Real fibers never have id 0,
|
151
|
-
so we can identify them later during printing.
|
152
|
-
*/
|
153
|
-
result->fiber_id = profile->merge_fibers ? INT2FIX(0) : fiber_id;
|
154
|
-
/* Insert the table */
|
155
|
-
threads_table_insert(profile, key, result);
|
187
|
+
result->trace = true;
|
156
188
|
}
|
189
|
+
|
157
190
|
return result;
|
158
191
|
}
|
159
192
|
|
160
|
-
|
161
|
-
switch_thread(void* prof,
|
193
|
+
// ====== Profiling Methods ======
|
194
|
+
void switch_thread(void* prof, thread_data_t* thread_data, double measurement)
|
162
195
|
{
|
163
|
-
prof_profile_t* profile =
|
164
|
-
double measurement = profile->measurer->measure();
|
165
|
-
|
166
|
-
/* Get new thread information. */
|
167
|
-
thread_data_t *thread_data = threads_table_lookup(profile, thread_id, fiber_id);
|
196
|
+
prof_profile_t* profile = prof;
|
168
197
|
|
169
198
|
/* Get current frame for this thread */
|
170
|
-
prof_frame_t
|
171
|
-
|
172
|
-
/* Update the time this thread waited for another thread */
|
199
|
+
prof_frame_t* frame = prof_frame_current(thread_data->stack);
|
173
200
|
if (frame)
|
174
201
|
{
|
175
202
|
frame->wait_time += measurement - frame->switch_time;
|
176
|
-
frame->switch_time =
|
203
|
+
frame->switch_time = 0;
|
177
204
|
}
|
178
205
|
|
179
206
|
/* Save on the last thread the time of the context switch
|
180
207
|
and reset this thread's last context switch to 0.*/
|
181
208
|
if (profile->last_thread_data)
|
182
209
|
{
|
183
|
-
|
184
|
-
|
185
|
-
|
210
|
+
prof_frame_t* last_frame = prof_frame_current(profile->last_thread_data->stack);
|
211
|
+
if (last_frame)
|
212
|
+
last_frame->switch_time = measurement;
|
186
213
|
}
|
187
214
|
|
188
215
|
profile->last_thread_data = thread_data;
|
189
|
-
return thread_data;
|
190
216
|
}
|
191
217
|
|
192
218
|
int pause_thread(st_data_t key, st_data_t value, st_data_t data)
|
193
219
|
{
|
194
|
-
thread_data_t* thread_data = (thread_data_t
|
220
|
+
thread_data_t* thread_data = (thread_data_t*)value;
|
195
221
|
prof_profile_t* profile = (prof_profile_t*)data;
|
196
222
|
|
197
|
-
prof_frame_t* frame =
|
223
|
+
prof_frame_t* frame = prof_frame_current(thread_data->stack);
|
198
224
|
prof_frame_pause(frame, profile->measurement_at_pause_resume);
|
199
225
|
|
200
226
|
return ST_CONTINUE;
|
@@ -202,37 +228,33 @@ int pause_thread(st_data_t key, st_data_t value, st_data_t data)
|
|
202
228
|
|
203
229
|
int unpause_thread(st_data_t key, st_data_t value, st_data_t data)
|
204
230
|
{
|
205
|
-
thread_data_t* thread_data = (thread_data_t
|
231
|
+
thread_data_t* thread_data = (thread_data_t*)value;
|
206
232
|
prof_profile_t* profile = (prof_profile_t*)data;
|
207
233
|
|
208
|
-
prof_frame_t* frame =
|
234
|
+
prof_frame_t* frame = prof_frame_current(thread_data->stack);
|
209
235
|
prof_frame_unpause(frame, profile->measurement_at_pause_resume);
|
210
236
|
|
211
237
|
return ST_CONTINUE;
|
212
238
|
}
|
213
239
|
|
214
|
-
|
215
|
-
collect_methods(st_data_t key, st_data_t value, st_data_t result)
|
240
|
+
// ====== Helper Methods ======
|
241
|
+
static int collect_methods(st_data_t key, st_data_t value, st_data_t result)
|
216
242
|
{
|
217
243
|
/* Called for each method stored in a thread's method table.
|
218
244
|
We want to store the method info information into an array.*/
|
219
|
-
VALUE methods = (VALUE)
|
220
|
-
prof_method_t
|
221
|
-
|
222
|
-
if (!method->excluded) {
|
223
|
-
rb_ary_push(methods, prof_method_wrap(method));
|
224
|
-
}
|
245
|
+
VALUE methods = (VALUE)result;
|
246
|
+
prof_method_t* method = (prof_method_t*)value;
|
247
|
+
rb_ary_push(methods, prof_method_wrap(method));
|
225
248
|
|
226
249
|
return ST_CONTINUE;
|
227
250
|
}
|
228
251
|
|
229
|
-
|
252
|
+
// ====== RubyProf::Thread ======
|
230
253
|
/* call-seq:
|
231
254
|
id -> number
|
232
255
|
|
233
|
-
Returns the id of this thread. */
|
234
|
-
static VALUE
|
235
|
-
prof_thread_id(VALUE self)
|
256
|
+
Returns the thread id of this thread. */
|
257
|
+
static VALUE prof_thread_id(VALUE self)
|
236
258
|
{
|
237
259
|
thread_data_t* thread = prof_get_thread(self);
|
238
260
|
return thread->thread_id;
|
@@ -242,36 +264,82 @@ prof_thread_id(VALUE self)
|
|
242
264
|
fiber_id -> number
|
243
265
|
|
244
266
|
Returns the fiber id of this thread. */
|
245
|
-
static VALUE
|
246
|
-
prof_fiber_id(VALUE self)
|
267
|
+
static VALUE prof_fiber_id(VALUE self)
|
247
268
|
{
|
248
269
|
thread_data_t* thread = prof_get_thread(self);
|
249
270
|
return thread->fiber_id;
|
250
271
|
}
|
251
272
|
|
252
273
|
/* call-seq:
|
253
|
-
|
274
|
+
call_tree -> CallTree
|
275
|
+
|
276
|
+
Returns the root of the call tree. */
|
277
|
+
static VALUE prof_call_tree(VALUE self)
|
278
|
+
{
|
279
|
+
thread_data_t* thread = prof_get_thread(self);
|
280
|
+
return prof_call_tree_wrap(thread->call_tree);
|
281
|
+
}
|
282
|
+
|
283
|
+
/* call-seq:
|
284
|
+
methods -> [RubyProf::MethodInfo]
|
254
285
|
|
255
286
|
Returns an array of methods that were called from this
|
256
287
|
thread during program execution. */
|
257
|
-
static VALUE
|
258
|
-
prof_thread_methods(VALUE self)
|
288
|
+
static VALUE prof_thread_methods(VALUE self)
|
259
289
|
{
|
260
290
|
thread_data_t* thread = prof_get_thread(self);
|
261
291
|
if (thread->methods == Qnil)
|
262
292
|
{
|
263
293
|
thread->methods = rb_ary_new();
|
264
|
-
|
294
|
+
rb_st_foreach(thread->method_table, collect_methods, thread->methods);
|
265
295
|
}
|
266
296
|
return thread->methods;
|
267
297
|
}
|
268
298
|
|
269
|
-
|
299
|
+
/* :nodoc: */
|
300
|
+
static VALUE prof_thread_dump(VALUE self)
|
301
|
+
{
|
302
|
+
thread_data_t* thread_data = DATA_PTR(self);
|
303
|
+
|
304
|
+
VALUE result = rb_hash_new();
|
305
|
+
rb_hash_aset(result, ID2SYM(rb_intern("fiber_id")), thread_data->fiber_id);
|
306
|
+
rb_hash_aset(result, ID2SYM(rb_intern("methods")), prof_thread_methods(self));
|
307
|
+
rb_hash_aset(result, ID2SYM(rb_intern("call_tree")), prof_call_tree(self));
|
308
|
+
|
309
|
+
return result;
|
310
|
+
}
|
311
|
+
|
312
|
+
/* :nodoc: */
|
313
|
+
static VALUE prof_thread_load(VALUE self, VALUE data)
|
314
|
+
{
|
315
|
+
thread_data_t* thread_data = DATA_PTR(self);
|
316
|
+
|
317
|
+
VALUE call_tree = rb_hash_aref(data, ID2SYM(rb_intern("call_tree")));
|
318
|
+
thread_data->call_tree = prof_get_call_tree(call_tree);
|
319
|
+
|
320
|
+
thread_data->fiber_id = rb_hash_aref(data, ID2SYM(rb_intern("fiber_id")));
|
321
|
+
|
322
|
+
VALUE methods = rb_hash_aref(data, ID2SYM(rb_intern("methods")));
|
323
|
+
for (int i = 0; i < rb_array_len(methods); i++)
|
324
|
+
{
|
325
|
+
VALUE method = rb_ary_entry(methods, i);
|
326
|
+
prof_method_t* method_data = DATA_PTR(method);
|
327
|
+
method_table_insert(thread_data->method_table, method_data->key, method_data);
|
328
|
+
}
|
329
|
+
|
330
|
+
return data;
|
331
|
+
}
|
332
|
+
|
333
|
+
void rp_init_thread(void)
|
270
334
|
{
|
271
|
-
cRpThread = rb_define_class_under(mProf, "Thread",
|
335
|
+
cRpThread = rb_define_class_under(mProf, "Thread", rb_cData);
|
272
336
|
rb_undef_method(CLASS_OF(cRpThread), "new");
|
337
|
+
rb_define_alloc_func(cRpThread, prof_thread_allocate);
|
273
338
|
|
274
339
|
rb_define_method(cRpThread, "id", prof_thread_id, 0);
|
340
|
+
rb_define_method(cRpThread, "call_tree", prof_call_tree, 0);
|
275
341
|
rb_define_method(cRpThread, "fiber_id", prof_fiber_id, 0);
|
276
342
|
rb_define_method(cRpThread, "methods", prof_thread_methods, 0);
|
343
|
+
rb_define_method(cRpThread, "_dump_data", prof_thread_dump, 0);
|
344
|
+
rb_define_method(cRpThread, "_load_data", prof_thread_load, 1);
|
277
345
|
}
|