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.
Files changed (129) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES +44 -1
  3. data/LICENSE +2 -2
  4. data/README.rdoc +1 -483
  5. data/Rakefile +3 -6
  6. data/bin/ruby-prof +111 -128
  7. data/ext/ruby_prof/extconf.rb +6 -38
  8. data/ext/ruby_prof/rp_aggregate_call_tree.c +41 -0
  9. data/ext/ruby_prof/rp_aggregate_call_tree.h +13 -0
  10. data/ext/ruby_prof/rp_allocation.c +259 -0
  11. data/ext/ruby_prof/rp_allocation.h +31 -0
  12. data/ext/ruby_prof/rp_call_tree.c +353 -0
  13. data/ext/ruby_prof/rp_call_tree.h +43 -0
  14. data/ext/ruby_prof/rp_call_trees.c +266 -0
  15. data/ext/ruby_prof/rp_call_trees.h +29 -0
  16. data/ext/ruby_prof/rp_measure_allocations.c +25 -51
  17. data/ext/ruby_prof/rp_measure_memory.c +21 -56
  18. data/ext/ruby_prof/rp_measure_process_time.c +37 -43
  19. data/ext/ruby_prof/rp_measure_wall_time.c +40 -21
  20. data/ext/ruby_prof/rp_measurement.c +221 -0
  21. data/ext/ruby_prof/rp_measurement.h +50 -0
  22. data/ext/ruby_prof/rp_method.c +279 -439
  23. data/ext/ruby_prof/rp_method.h +33 -45
  24. data/ext/ruby_prof/rp_profile.c +902 -0
  25. data/ext/ruby_prof/rp_profile.h +36 -0
  26. data/ext/ruby_prof/rp_stack.c +163 -132
  27. data/ext/ruby_prof/rp_stack.h +18 -28
  28. data/ext/ruby_prof/rp_thread.c +192 -124
  29. data/ext/ruby_prof/rp_thread.h +18 -8
  30. data/ext/ruby_prof/ruby_prof.c +36 -778
  31. data/ext/ruby_prof/ruby_prof.h +11 -45
  32. data/ext/ruby_prof/vc/ruby_prof.vcxproj +18 -12
  33. data/lib/ruby-prof.rb +4 -21
  34. data/lib/ruby-prof/assets/call_stack_printer.html.erb +710 -0
  35. data/lib/ruby-prof/assets/call_stack_printer.png +0 -0
  36. data/lib/ruby-prof/assets/graph_printer.html.erb +355 -0
  37. data/lib/ruby-prof/call_tree.rb +57 -0
  38. data/lib/ruby-prof/call_tree_visitor.rb +36 -0
  39. data/lib/ruby-prof/compatibility.rb +37 -107
  40. data/lib/ruby-prof/exclude_common_methods.rb +198 -0
  41. data/lib/ruby-prof/measurement.rb +17 -0
  42. data/lib/ruby-prof/method_info.rb +47 -90
  43. data/lib/ruby-prof/printers/abstract_printer.rb +73 -50
  44. data/lib/ruby-prof/printers/call_info_printer.rb +24 -12
  45. data/lib/ruby-prof/printers/call_stack_printer.rb +66 -152
  46. data/lib/ruby-prof/printers/call_tree_printer.rb +20 -12
  47. data/lib/ruby-prof/printers/dot_printer.rb +5 -5
  48. data/lib/ruby-prof/printers/flat_printer.rb +6 -24
  49. data/lib/ruby-prof/printers/graph_html_printer.rb +6 -192
  50. data/lib/ruby-prof/printers/graph_printer.rb +11 -14
  51. data/lib/ruby-prof/printers/multi_printer.rb +66 -23
  52. data/lib/ruby-prof/profile.rb +10 -3
  53. data/lib/ruby-prof/thread.rb +5 -20
  54. data/lib/ruby-prof/version.rb +1 -1
  55. data/ruby-prof.gemspec +9 -2
  56. data/test/abstract_printer_test.rb +0 -27
  57. data/test/alias_test.rb +126 -0
  58. data/test/basic_test.rb +1 -86
  59. data/test/call_tree_visitor_test.rb +32 -0
  60. data/test/call_trees_test.rb +66 -0
  61. data/test/dynamic_method_test.rb +0 -2
  62. data/test/exclude_methods_test.rb +17 -12
  63. data/test/fiber_test.rb +214 -23
  64. data/test/gc_test.rb +105 -0
  65. data/test/inverse_call_tree_test.rb +175 -0
  66. data/test/line_number_test.rb +118 -40
  67. data/test/marshal_test.rb +115 -0
  68. data/test/measure_allocations.rb +30 -0
  69. data/test/measure_allocations_test.rb +361 -12
  70. data/test/measure_allocations_trace_test.rb +375 -0
  71. data/test/measure_memory_trace_test.rb +1101 -0
  72. data/test/measure_process_time_test.rb +757 -33
  73. data/test/measure_times.rb +56 -0
  74. data/test/measure_wall_time_test.rb +329 -149
  75. data/test/multi_printer_test.rb +1 -34
  76. data/test/pause_resume_test.rb +24 -15
  77. data/test/prime.rb +1 -1
  78. data/test/prime_script.rb +6 -0
  79. data/test/printer_call_stack_test.rb +28 -0
  80. data/test/printer_call_tree_test.rb +31 -0
  81. data/test/printer_flat_test.rb +68 -0
  82. data/test/printer_graph_html_test.rb +60 -0
  83. data/test/printer_graph_test.rb +41 -0
  84. data/test/printers_test.rb +32 -166
  85. data/test/printing_recursive_graph_test.rb +26 -72
  86. data/test/recursive_test.rb +68 -77
  87. data/test/stack_printer_test.rb +2 -15
  88. data/test/start_stop_test.rb +22 -25
  89. data/test/test_helper.rb +6 -261
  90. data/test/thread_test.rb +11 -54
  91. data/test/unique_call_path_test.rb +25 -107
  92. data/test/yarv_test.rb +1 -0
  93. metadata +43 -41
  94. data/examples/flat.txt +0 -50
  95. data/examples/graph.dot +0 -84
  96. data/examples/graph.html +0 -823
  97. data/examples/graph.txt +0 -139
  98. data/examples/multi.flat.txt +0 -23
  99. data/examples/multi.graph.html +0 -760
  100. data/examples/multi.grind.dat +0 -114
  101. data/examples/multi.stack.html +0 -547
  102. data/examples/stack.html +0 -547
  103. data/ext/ruby_prof/rp_call_info.c +0 -425
  104. data/ext/ruby_prof/rp_call_info.h +0 -53
  105. data/ext/ruby_prof/rp_measure.c +0 -40
  106. data/ext/ruby_prof/rp_measure.h +0 -45
  107. data/ext/ruby_prof/rp_measure_cpu_time.c +0 -136
  108. data/ext/ruby_prof/rp_measure_gc_runs.c +0 -73
  109. data/ext/ruby_prof/rp_measure_gc_time.c +0 -60
  110. data/lib/ruby-prof/aggregate_call_info.rb +0 -76
  111. data/lib/ruby-prof/assets/call_stack_printer.css.html +0 -117
  112. data/lib/ruby-prof/assets/call_stack_printer.js.html +0 -385
  113. data/lib/ruby-prof/call_info.rb +0 -115
  114. data/lib/ruby-prof/call_info_visitor.rb +0 -40
  115. data/lib/ruby-prof/printers/flat_printer_with_line_numbers.rb +0 -83
  116. data/lib/ruby-prof/profile/exclude_common_methods.rb +0 -207
  117. data/lib/ruby-prof/profile/legacy_method_elimination.rb +0 -50
  118. data/test/aggregate_test.rb +0 -136
  119. data/test/block_test.rb +0 -74
  120. data/test/call_info_test.rb +0 -78
  121. data/test/call_info_visitor_test.rb +0 -31
  122. data/test/issue137_test.rb +0 -63
  123. data/test/measure_cpu_time_test.rb +0 -212
  124. data/test/measure_gc_runs_test.rb +0 -32
  125. data/test/measure_gc_time_test.rb +0 -36
  126. data/test/measure_memory_test.rb +0 -33
  127. data/test/method_elimination_test.rb +0 -84
  128. data/test/module_test.rb +0 -45
  129. 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__
@@ -3,171 +3,202 @@
3
3
 
4
4
  #include "rp_stack.h"
5
5
 
6
- #define INITIAL_STACK_SIZE 8
6
+ #define INITIAL_STACK_SIZE 16
7
7
 
8
- void
9
- prof_frame_pause(prof_frame_t *frame, double current_measurement)
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
- if (frame && prof_frame_is_unpaused(frame))
12
- frame->pause_time = current_measurement;
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
- if (frame && prof_frame_is_paused(frame)) {
19
- frame->dead_time += (current_measurement - frame->pause_time);
20
- frame->pause_time = -1;
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
- /* Creates a stack of prof_frame_t to keep track
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
- prof_stack_t *stack = ALLOC(prof_stack_t);
31
- stack->start = ALLOC_N(prof_frame_t, INITIAL_STACK_SIZE);
32
- stack->ptr = stack->start;
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 stack;
63
+ return result;
36
64
  }
37
65
 
38
- void
39
- prof_stack_free(prof_stack_t *stack)
66
+ // ---------------- Frame Methods ----------------------------
67
+ void prof_frame_pause(prof_frame_t* frame, double current_measurement)
40
68
  {
41
- xfree(stack->start);
42
- xfree(stack);
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
- prof_frame_t *result;
49
- prof_frame_t* parent_frame;
50
- prof_method_t *method;
51
-
52
- /* Is there space on the stack? If not, double
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
- prof_frame_t *frame;
109
- prof_frame_t *parent_frame;
110
- prof_call_info_t *call_info;
111
- prof_method_t *method;
84
+ return prof_stack_last(stack);
85
+ }
112
86
 
113
- double total_time;
114
- double self_time;
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
- frame = prof_stack_peek(stack);
125
+ // Return the result
126
+ return result;
127
+ }
117
128
 
118
- /* Frame can be null, which means the stack is empty. This can happen if
119
- RubProf.start is called from a method that exits. And it can happen if an
120
- exception is raised in code that is being profiled and the stack unwinds
121
- (RubyProf is not notified of that by the ruby runtime. */
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
- /* Match passes until we reach the frame itself. */
127
- if (prof_frame_is_pass(frame)) {
128
- frame->passes--;
129
- /* Additional frames can be consumed. See pop_frames(). */
130
- return frame;
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
- /* Consume this frame. */
134
- stack->ptr--;
148
+ if (!frame)
149
+ return NULL;
135
150
 
136
- /* Calculate the total time this method took */
137
- prof_frame_unpause(frame, measurement);
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
- /* Update information about the current method */
142
- call_info = frame->call_info;
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
- call_info->called++;
146
- call_info->total_time += total_time;
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
- /* Leave the method. */
151
- method->visits--;
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
- parent_frame = prof_stack_peek(stack);
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
- call_info->line = parent_frame->line;
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
- return frame;
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
- prof_frame_t *
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
- prof_frame_t *frame = prof_stack_peek(stack);
169
- if (frame) {
170
- frame->passes++;
171
- }
172
- return frame;
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
  }
@@ -4,23 +4,20 @@
4
4
  #ifndef __RP_STACK__
5
5
  #define __RP_STACK__
6
6
 
7
- #include <ruby.h>
7
+ #include "ruby_prof.h"
8
+ #include "rp_call_tree.h"
8
9
 
9
- #include "rp_measure.h"
10
- #include "rp_call_info.h"
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
- prof_call_info_t *call_info;
17
+ prof_call_tree_t* call_tree;
21
18
 
22
- unsigned int line;
23
- unsigned int passes; /* Count of "pass" frames, _after_ this one. */
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 *start;
46
- prof_frame_t *end;
47
- prof_frame_t *ptr;
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 *prof_stack_create();
51
- void prof_stack_free(prof_stack_t *stack);
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__
@@ -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
- #include "ruby_prof.h"
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
- /* ====== thread_data_t ====== */
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
- /* The underlying c structures are freed when the parent profile is freed.
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
- /* Has this thread object been accessed by Ruby? If
30
- yes clean it up so to avoid a segmentation fault. */
31
- if (thread_data->object != Qnil)
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
- static void
41
- thread_data_free(thread_data_t* thread_data)
47
+ size_t prof_thread_size(const void* data)
42
48
  {
43
- thread_data_ruby_gc_free(thread_data);
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
- static int
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
- prof_method_t *method = (prof_method_t *) value;
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->fiber_id != Qnil)
73
- rb_gc_mark(thread->fiber_id);
70
+ if (thread->call_tree)
71
+ prof_call_tree_mark(thread->call_tree);
74
72
 
75
- st_foreach(thread->method_table, mark_methods, 0);
73
+ rb_st_foreach(thread->method_table, mark_methods, 0);
76
74
  }
77
75
 
78
- VALUE
79
- prof_thread_wrap(thread_data_t *thread)
76
+ void prof_thread_ruby_gc_free(void* data)
80
77
  {
81
- if (thread->object == Qnil) {
82
- thread->object = Data_Wrap_Struct(cRpThread, prof_thread_mark, thread_data_ruby_gc_free, thread);
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 thread_data_t*
88
- prof_get_thread(VALUE self)
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
- /* ====== Thread Table ====== */
100
- /* The thread table is hash keyed on ruby thread_id that stores instances
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 st_init_numtable();
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
- thread_data_free((thread_data_t*)value);
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
- st_foreach(table, thread_table_free_iterator, 0);
120
- st_free_table(table);
147
+ rb_st_foreach(table, thread_table_free_iterator, 0);
148
+ rb_st_free_table(table);
121
149
  }
122
150
 
123
- size_t
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
- unsigned LONG_LONG key_value = NUM2ULL(key);
127
- return st_insert(profile->threads_tbl, key_value, (st_data_t) thread_data);
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
- thread_data_t* result;
134
- st_data_t val;
167
+ prof_profile_t* profile = prof;
168
+ thread_data_t* result = thread_data_create();
169
+ VALUE thread = rb_thread_current();
135
170
 
136
- /* If we should merge fibers, we use the thread_id as key, otherwise the fiber id.
137
- None of this is perfect, as garbage collected fiber/thread might be reused again later.
138
- A real solution would require integration with the garbage collector.
139
- */
140
- VALUE key = profile->merge_fibers ? thread_id : fiber_id;
141
- unsigned LONG_LONG key_value = NUM2ULL(key);
142
- if (st_lookup(profile->threads_tbl, key_value, &val))
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
- result = (thread_data_t *) val;
183
+ result->trace = false;
145
184
  }
146
185
  else
147
186
  {
148
- result = thread_data_create();
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
- thread_data_t *
161
- switch_thread(void* prof, VALUE thread_id, VALUE fiber_id)
193
+ // ====== Profiling Methods ======
194
+ void switch_thread(void* prof, thread_data_t* thread_data, double measurement)
162
195
  {
163
- prof_profile_t* profile = (prof_profile_t*)prof;
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 *frame = prof_stack_peek(thread_data->stack);
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 = measurement;
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
- prof_frame_t *last_frame = prof_stack_peek(profile->last_thread_data->stack);
184
- if (last_frame)
185
- last_frame->switch_time = measurement;
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 *) value;
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 = prof_stack_peek(thread_data->stack);
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 *) value;
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 = prof_stack_peek(thread_data->stack);
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
- static int
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) result;
220
- prof_method_t *method = (prof_method_t *) value;
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
- methods -> Array of MethodInfo
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
- st_foreach(thread->method_table, collect_methods, thread->methods);
294
+ rb_st_foreach(thread->method_table, collect_methods, thread->methods);
265
295
  }
266
296
  return thread->methods;
267
297
  }
268
298
 
269
- void rp_init_thread()
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", rb_cObject);
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
  }