airbnb-ruby-prof 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. data/CHANGES +483 -0
  2. data/LICENSE +25 -0
  3. data/README.rdoc +426 -0
  4. data/Rakefile +51 -0
  5. data/bin/ruby-prof +279 -0
  6. data/bin/ruby-prof-check-trace +45 -0
  7. data/examples/flat.txt +50 -0
  8. data/examples/graph.dot +84 -0
  9. data/examples/graph.html +823 -0
  10. data/examples/graph.txt +139 -0
  11. data/examples/multi.flat.txt +23 -0
  12. data/examples/multi.graph.html +760 -0
  13. data/examples/multi.grind.dat +114 -0
  14. data/examples/multi.stack.html +547 -0
  15. data/examples/stack.html +547 -0
  16. data/ext/ruby_prof/extconf.rb +67 -0
  17. data/ext/ruby_prof/rp_call_info.c +374 -0
  18. data/ext/ruby_prof/rp_call_info.h +59 -0
  19. data/ext/ruby_prof/rp_fast_call_tree_printer.c +247 -0
  20. data/ext/ruby_prof/rp_fast_call_tree_printer.h +10 -0
  21. data/ext/ruby_prof/rp_measure.c +71 -0
  22. data/ext/ruby_prof/rp_measure.h +56 -0
  23. data/ext/ruby_prof/rp_measure_allocations.c +74 -0
  24. data/ext/ruby_prof/rp_measure_cpu_time.c +134 -0
  25. data/ext/ruby_prof/rp_measure_gc_runs.c +71 -0
  26. data/ext/ruby_prof/rp_measure_gc_time.c +58 -0
  27. data/ext/ruby_prof/rp_measure_memory.c +75 -0
  28. data/ext/ruby_prof/rp_measure_process_time.c +69 -0
  29. data/ext/ruby_prof/rp_measure_wall_time.c +43 -0
  30. data/ext/ruby_prof/rp_method.c +717 -0
  31. data/ext/ruby_prof/rp_method.h +79 -0
  32. data/ext/ruby_prof/rp_stack.c +221 -0
  33. data/ext/ruby_prof/rp_stack.h +81 -0
  34. data/ext/ruby_prof/rp_thread.c +312 -0
  35. data/ext/ruby_prof/rp_thread.h +36 -0
  36. data/ext/ruby_prof/ruby_prof.c +800 -0
  37. data/ext/ruby_prof/ruby_prof.h +64 -0
  38. data/ext/ruby_prof/vc/ruby_prof.sln +32 -0
  39. data/ext/ruby_prof/vc/ruby_prof_18.vcxproj +108 -0
  40. data/ext/ruby_prof/vc/ruby_prof_19.vcxproj +110 -0
  41. data/ext/ruby_prof/vc/ruby_prof_20.vcxproj +110 -0
  42. data/lib/ruby-prof.rb +63 -0
  43. data/lib/ruby-prof/aggregate_call_info.rb +76 -0
  44. data/lib/ruby-prof/assets/call_stack_printer.css.html +117 -0
  45. data/lib/ruby-prof/assets/call_stack_printer.js.html +385 -0
  46. data/lib/ruby-prof/assets/call_stack_printer.png +0 -0
  47. data/lib/ruby-prof/assets/flame_graph_printer.lib.css.html +149 -0
  48. data/lib/ruby-prof/assets/flame_graph_printer.lib.js.html +707 -0
  49. data/lib/ruby-prof/assets/flame_graph_printer.page.js.html +56 -0
  50. data/lib/ruby-prof/assets/flame_graph_printer.tmpl.html.erb +39 -0
  51. data/lib/ruby-prof/call_info.rb +111 -0
  52. data/lib/ruby-prof/call_info_visitor.rb +40 -0
  53. data/lib/ruby-prof/compatibility.rb +186 -0
  54. data/lib/ruby-prof/method_info.rb +109 -0
  55. data/lib/ruby-prof/printers/abstract_printer.rb +85 -0
  56. data/lib/ruby-prof/printers/call_info_printer.rb +41 -0
  57. data/lib/ruby-prof/printers/call_stack_printer.rb +260 -0
  58. data/lib/ruby-prof/printers/call_tree_printer.rb +130 -0
  59. data/lib/ruby-prof/printers/dot_printer.rb +132 -0
  60. data/lib/ruby-prof/printers/fast_call_tree_printer.rb +87 -0
  61. data/lib/ruby-prof/printers/flame_graph_html_printer.rb +59 -0
  62. data/lib/ruby-prof/printers/flame_graph_json_printer.rb +157 -0
  63. data/lib/ruby-prof/printers/flat_printer.rb +70 -0
  64. data/lib/ruby-prof/printers/flat_printer_with_line_numbers.rb +64 -0
  65. data/lib/ruby-prof/printers/graph_html_printer.rb +244 -0
  66. data/lib/ruby-prof/printers/graph_printer.rb +116 -0
  67. data/lib/ruby-prof/printers/multi_printer.rb +58 -0
  68. data/lib/ruby-prof/profile.rb +22 -0
  69. data/lib/ruby-prof/profile/exclude_common_methods.rb +201 -0
  70. data/lib/ruby-prof/rack.rb +95 -0
  71. data/lib/ruby-prof/task.rb +147 -0
  72. data/lib/ruby-prof/thread.rb +35 -0
  73. data/lib/ruby-prof/version.rb +4 -0
  74. data/lib/ruby-prof/walker.rb +95 -0
  75. data/lib/unprof.rb +10 -0
  76. data/ruby-prof.gemspec +56 -0
  77. data/test/aggregate_test.rb +136 -0
  78. data/test/basic_test.rb +128 -0
  79. data/test/block_test.rb +74 -0
  80. data/test/call_info_test.rb +78 -0
  81. data/test/call_info_visitor_test.rb +31 -0
  82. data/test/duplicate_names_test.rb +32 -0
  83. data/test/dynamic_method_test.rb +55 -0
  84. data/test/enumerable_test.rb +21 -0
  85. data/test/exceptions_test.rb +16 -0
  86. data/test/exclude_methods_test.rb +146 -0
  87. data/test/exclude_threads_test.rb +53 -0
  88. data/test/fiber_test.rb +79 -0
  89. data/test/issue137_test.rb +63 -0
  90. data/test/line_number_test.rb +71 -0
  91. data/test/measure_allocations_test.rb +26 -0
  92. data/test/measure_cpu_time_test.rb +213 -0
  93. data/test/measure_gc_runs_test.rb +32 -0
  94. data/test/measure_gc_time_test.rb +36 -0
  95. data/test/measure_memory_test.rb +33 -0
  96. data/test/measure_process_time_test.rb +63 -0
  97. data/test/measure_wall_time_test.rb +255 -0
  98. data/test/module_test.rb +45 -0
  99. data/test/multi_measure_test.rb +38 -0
  100. data/test/multi_printer_test.rb +83 -0
  101. data/test/no_method_class_test.rb +15 -0
  102. data/test/pause_resume_test.rb +166 -0
  103. data/test/prime.rb +54 -0
  104. data/test/printers_test.rb +255 -0
  105. data/test/printing_recursive_graph_test.rb +127 -0
  106. data/test/rack_test.rb +93 -0
  107. data/test/recursive_test.rb +212 -0
  108. data/test/singleton_test.rb +38 -0
  109. data/test/stack_printer_test.rb +65 -0
  110. data/test/stack_test.rb +138 -0
  111. data/test/start_stop_test.rb +112 -0
  112. data/test/test_helper.rb +264 -0
  113. data/test/thread_test.rb +187 -0
  114. data/test/unique_call_path_test.rb +202 -0
  115. data/test/yarv_test.rb +55 -0
  116. metadata +211 -0
@@ -0,0 +1,79 @@
1
+ /* Copyright (C) 2005-2013 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_METHOD_INFO__
5
+ #define __RP_METHOD_INFO__
6
+
7
+ #include <ruby.h>
8
+
9
+ extern VALUE cMethodInfo;
10
+
11
+ /* A key used to identify each method */
12
+ typedef struct
13
+ {
14
+ VALUE klass; /* The method's class. */
15
+ ID mid; /* The method id. */
16
+ st_index_t key; /* Cache calculated key */
17
+ } prof_method_key_t;
18
+
19
+ /* Source relation bit offsets. */
20
+ enum {
21
+ kModuleIncludee = 0, /* Included module */
22
+ kModuleSingleton, /* Singleton class of a module */
23
+ kObjectSingleton /* Singleton class of an object */
24
+ };
25
+
26
+ /* Forward declaration, see rp_call_info.h */
27
+ struct prof_call_infos_t;
28
+
29
+ /* Profiling information for each method. */
30
+ /* Excluded methods have no call_infos, source_klass, or source_file. */
31
+ typedef struct
32
+ {
33
+ /* Hot */
34
+
35
+ prof_method_key_t *key; /* Table key */
36
+
37
+ struct prof_call_infos_t *call_infos; /* Call infos */
38
+ int visits; /* Current visits on the stack */
39
+
40
+ unsigned int excluded : 1; /* Exclude from profile? */
41
+ unsigned int recursive : 1; /* Recursive (direct or mutual)? */
42
+
43
+ /* Cold */
44
+
45
+ VALUE object; /* Cached ruby object */
46
+ VALUE source_klass; /* Source class */
47
+ const char *source_file; /* Source file */
48
+ int line; /* Line number */
49
+
50
+ unsigned int resolved : 1; /* Source resolved? */
51
+ unsigned int relation : 3; /* Source relation bits */
52
+ } prof_method_t;
53
+
54
+ void rp_init_method_info(void);
55
+
56
+ void method_key(prof_method_key_t* key, VALUE klass, ID mid);
57
+
58
+ st_table * method_table_create();
59
+ prof_method_t * method_table_lookup(st_table *table, const prof_method_key_t* key);
60
+ size_t method_table_insert(st_table *table, const prof_method_key_t *key, prof_method_t *val);
61
+ void method_table_free(st_table *table);
62
+
63
+ prof_method_t* prof_method_create(VALUE klass, ID mid, const char* source_file, int line);
64
+ prof_method_t* prof_method_create_excluded(VALUE klass, ID mid);
65
+
66
+ VALUE prof_method_wrap(prof_method_t *result);
67
+ void prof_method_mark(prof_method_t *method);
68
+
69
+ const char* prof_method_t_source_file(prof_method_t *method);
70
+ VALUE prof_method_t_calltree_name(prof_method_t *method);
71
+ double prof_method_t_self_time(prof_method_t *method, int idx);
72
+
73
+ /* Setup infrastructure to use method keys as hash comparisons */
74
+ int method_table_cmp(prof_method_key_t *key1, prof_method_key_t *key2);
75
+ st_index_t method_table_hash(prof_method_key_t *key);
76
+
77
+ extern struct st_hash_type type_method_hash;
78
+
79
+ #endif
@@ -0,0 +1,221 @@
1
+ /* Copyright (C) 2005-2013 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
+ #include "rp_stack.h"
5
+
6
+ #define INITIAL_STACK_SIZE 8
7
+
8
+
9
+ void
10
+ prof_frame_pause(prof_frame_t *frame, prof_measurements_t *current_measurements)
11
+ {
12
+ if (frame && prof_frame_is_unpaused(frame)) {
13
+ for(size_t i = 0; i < current_measurements->len; i++) {
14
+ frame->measurements[i].pause = current_measurements->values[i];
15
+ }
16
+ }
17
+ }
18
+
19
+ void
20
+ prof_frame_unpause(prof_frame_t *frame, prof_measurements_t *current_measurements)
21
+ {
22
+ if (frame && prof_frame_is_paused(frame)) {
23
+ for(size_t i = 0; i < current_measurements->len; i++) {
24
+ frame->measurements[i].dead += (current_measurements->values[i] - frame->measurements[i].pause);
25
+ frame->measurements[i].pause = -1;
26
+ }
27
+ }
28
+ }
29
+
30
+ prof_frame_t*
31
+ prof_stack_frame_get(size_t measurements_len, size_t i, prof_frame_t *stack_start)
32
+ {
33
+ size_t offset = i * FRAME_SIZE(measurements_len);
34
+
35
+ return (prof_frame_t*)((uintptr_t)stack_start + offset);
36
+ }
37
+
38
+ /* Creates a stack of prof_frame_t to keep track
39
+ of timings for active methods. */
40
+ prof_stack_t *
41
+ prof_stack_create(size_t measurements_len)
42
+ {
43
+ prof_stack_t *stack = ALLOC(prof_stack_t);
44
+ stack->measurements_len = measurements_len;
45
+ stack->start = (prof_frame_t*) ruby_xmalloc2(INITIAL_STACK_SIZE, FRAME_SIZE(measurements_len));
46
+ for (size_t i = 0; i < INITIAL_STACK_SIZE; i++) {
47
+ prof_frame_t *frame = prof_stack_frame_get(measurements_len, i, stack->start);
48
+ frame->measurements_len = measurements_len;
49
+ }
50
+ stack->ptr = stack->start;
51
+ stack->end = (prof_frame_t*)((uintptr_t)(stack->start) +
52
+ (INITIAL_STACK_SIZE * FRAME_SIZE(measurements_len)));
53
+
54
+ return stack;
55
+ }
56
+
57
+ void
58
+ prof_stack_free(prof_stack_t *stack)
59
+ {
60
+ xfree(stack->start);
61
+ xfree(stack);
62
+ }
63
+
64
+ static void
65
+ prof_stack_realloc(prof_stack_t *stack, size_t measurements_len)
66
+ {
67
+
68
+ size_t len = ((uintptr_t)(stack->ptr) - (uintptr_t)(stack->start)) / FRAME_SIZE(measurements_len);
69
+ size_t new_capacity =
70
+ (((uintptr_t)(stack->end) - (uintptr_t)(stack->start)) * 2) / FRAME_SIZE(measurements_len);
71
+
72
+ stack->start =
73
+ (prof_frame_t*) ruby_xrealloc2(
74
+ (char*)(stack->start), new_capacity, FRAME_SIZE(measurements_len));
75
+
76
+ for (int i = 0; i < new_capacity; i++) {
77
+ prof_frame_t *frame = prof_stack_frame_get(measurements_len, i, stack->start);
78
+ frame->measurements_len = measurements_len;
79
+ }
80
+
81
+ /* Memory just got moved, reset pointers */
82
+ stack->ptr = (prof_frame_t*) ((uintptr_t)(stack->start) + len * FRAME_SIZE(measurements_len));
83
+ stack->end = (prof_frame_t*) ((uintptr_t)(stack->start) + new_capacity * FRAME_SIZE(measurements_len));
84
+ }
85
+
86
+ prof_frame_t *
87
+ prof_stack_push(prof_stack_t *stack, prof_call_info_t *call_info, prof_measurements_t *measurements, int paused)
88
+ {
89
+ prof_frame_t *result;
90
+ prof_frame_t* parent_frame;
91
+ prof_method_t *method;
92
+ size_t measurements_len = stack->measurements_len;
93
+
94
+ parent_frame = prof_stack_peek(stack);
95
+
96
+ /* Is there space on the stack? If not, double
97
+ its size. */
98
+ if (stack->ptr == stack->end)
99
+ {
100
+ prof_stack_realloc(stack, measurements_len);
101
+ }
102
+
103
+ // Reserve the next available frame pointer.
104
+ result = stack->ptr;
105
+ stack->ptr = NEXT_FRAME(stack);
106
+
107
+ result->call_info = call_info;
108
+ // shortening of 64 bit into 32;
109
+ result->call_info->depth = (int)
110
+ (((uintptr_t)(stack->ptr) - (uintptr_t)(stack->start)) / FRAME_SIZE(measurements_len));
111
+ result->passes = 0;
112
+
113
+ for (size_t i = 0; i < measurements_len; i++) {
114
+ result->measurements[i].start = measurements->values[i];
115
+ result->measurements[i].pause = -1; // init as not paused
116
+ result->measurements[i].switch_t = 0;
117
+ result->measurements[i].wait = 0;
118
+ result->measurements[i].child = 0;
119
+ result->measurements[i].dead = 0;
120
+ }
121
+
122
+ method = call_info->target;
123
+
124
+ /* If the method was visited previously, it's recursive. */
125
+ if (method->visits > 0)
126
+ {
127
+ method->recursive = 1;
128
+ call_info->recursive = 1;
129
+ }
130
+ /* Enter the method. */
131
+ method->visits++;
132
+
133
+ // Unpause the parent frame, if it exists.
134
+ // If currently paused then:
135
+ // 1) The child frame will begin paused.
136
+ // 2) The parent will inherit the child's dead time.
137
+ prof_frame_unpause(parent_frame, measurements);
138
+
139
+ if (paused) {
140
+ prof_frame_pause(result, measurements);
141
+ }
142
+
143
+ // Return the result
144
+ return result;
145
+ }
146
+
147
+ prof_frame_t *
148
+ prof_stack_pop(prof_stack_t *stack, prof_measurements_t *measurements)
149
+ {
150
+ prof_frame_t *frame;
151
+ prof_frame_t *parent_frame;
152
+ prof_call_info_t *call_info;
153
+ prof_method_t *method;
154
+
155
+ double total_time;
156
+ double self_time;
157
+
158
+ frame = prof_stack_peek(stack);
159
+
160
+ /* Frame can be null. This can happen if RubProf.start is called from
161
+ a method that exits. And it can happen if an exception is raised
162
+ in code that is being profiled and the stack unwinds (RubyProf is
163
+ not notified of that by the ruby runtime. */
164
+ if (!frame) {
165
+ return NULL;
166
+ }
167
+
168
+ /* Match passes until we reach the frame itself. */
169
+ if (prof_frame_is_pass(frame)) {
170
+ frame->passes--;
171
+ /* Additional frames can be consumed. See pop_frames(). */
172
+ return frame;
173
+ }
174
+
175
+ /* Consume this frame. */
176
+ stack->ptr = PREVIOUS_FRAME(stack);
177
+
178
+ prof_frame_unpause(frame, measurements);
179
+
180
+ /* Update information about the current method */
181
+ call_info = frame->call_info;
182
+ method = call_info->target;
183
+
184
+ call_info->called++;
185
+
186
+ for (size_t i = 0; i < measurements->len; i++) {
187
+ total_time = measurements->values[i] - frame->measurements[i].start - frame->measurements[i].dead;
188
+ self_time = total_time - frame->measurements[i].child - frame->measurements[i].wait;
189
+
190
+ call_info->measure_values[i].total += total_time;
191
+ call_info->measure_values[i].self += self_time;
192
+ call_info->measure_values[i].wait += frame->measurements[i].wait;
193
+ }
194
+
195
+ /* Leave the method. */
196
+ method->visits--;
197
+
198
+ parent_frame = prof_stack_peek(stack);
199
+ if (parent_frame)
200
+ {
201
+ for (size_t i = 0; i < measurements->len; i++) {
202
+ parent_frame->measurements[i].child +=
203
+ measurements->values[i] - frame->measurements[i].start - frame->measurements[i].dead;
204
+ parent_frame->measurements[i].dead += frame->measurements[i].dead;
205
+ }
206
+
207
+ call_info->line = parent_frame->line;
208
+ }
209
+
210
+ return frame;
211
+ }
212
+
213
+ prof_frame_t *
214
+ prof_stack_pass(prof_stack_t *stack)
215
+ {
216
+ prof_frame_t *frame = prof_stack_peek(stack);
217
+ if (frame) {
218
+ frame->passes++;
219
+ }
220
+ return frame;
221
+ }
@@ -0,0 +1,81 @@
1
+ /* Copyright (C) 2005-2013 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_STACK__
5
+ #define __RP_STACK__
6
+
7
+ #include <ruby.h>
8
+
9
+ #include "rp_measure.h"
10
+ #include "rp_call_info.h"
11
+
12
+
13
+ typedef struct
14
+ {
15
+ double start;
16
+ double switch_t; // Time at switch to different thread
17
+ double wait;
18
+ double child;
19
+ double pause; //Time pause() was initiated
20
+ double dead; // Time to ignore (i.e. total amount of time between pause/resume blocks)
21
+ } prof_frame_measurement_t;
22
+
23
+ /* Temporary object that maintains profiling information
24
+ for active methods. They are created and destroyed
25
+ as the program moves up and down its stack. */
26
+ typedef struct
27
+ {
28
+ /* Caching prof_method_t values significantly
29
+ increases performance. */
30
+ prof_call_info_t *call_info;
31
+
32
+ unsigned int line;
33
+ unsigned int passes; /* Count of "pass" frames, _after_ this one. */
34
+
35
+ size_t measurements_len;
36
+ prof_frame_measurement_t measurements[];
37
+ } prof_frame_t;
38
+
39
+ #define prof_frame_is_real(f) ((f)->passes == 0)
40
+ #define prof_frame_is_pass(f) ((f)->passes > 0)
41
+
42
+ #define prof_frame_is_paused(f) (f->measurements[0].pause >= 0)
43
+ #define prof_frame_is_unpaused(f) (f->measurements[0].pause < 0)
44
+
45
+ void prof_frame_pause(prof_frame_t*, prof_measurements_t *current_measurements);
46
+ void prof_frame_unpause(prof_frame_t*, prof_measurements_t *current_measurements);
47
+
48
+ /* Current stack of active methods.*/
49
+ typedef struct prof_stack_t
50
+ {
51
+ size_t measurements_len;
52
+ prof_frame_t *start;
53
+ prof_frame_t *end;
54
+ prof_frame_t *ptr;
55
+ } prof_stack_t;
56
+
57
+ prof_stack_t *prof_stack_create(size_t measurements_len);
58
+ void prof_stack_free(prof_stack_t *stack);
59
+
60
+ prof_frame_t *prof_stack_push(prof_stack_t *stack, prof_call_info_t *call_info,
61
+ prof_measurements_t *measurements, int paused);
62
+ prof_frame_t *prof_stack_pop(prof_stack_t *stack, prof_measurements_t *measurements);
63
+ prof_frame_t *prof_stack_pass(prof_stack_t *stack);
64
+
65
+ #define FRAME_SIZE(measurements_len) (sizeof(prof_frame_t) + (measurements_len) * sizeof(prof_frame_measurement_t))
66
+ #define NEXT_FRAME(stack) (prof_frame_t*) \
67
+ ((uintptr_t)((stack)->ptr) + FRAME_SIZE((stack)->measurements_len))
68
+ #define PREVIOUS_FRAME(stack) (prof_frame_t*) \
69
+ ((uintptr_t)((stack)->ptr) - FRAME_SIZE((stack)->measurements_len))
70
+
71
+ static inline prof_frame_t *
72
+ prof_stack_peek(prof_stack_t *stack) {
73
+ if (stack->ptr != stack->start) {
74
+ return (prof_frame_t*) ((uintptr_t)(stack->ptr) - 1 * FRAME_SIZE(stack->measurements_len));
75
+ } else {
76
+ return NULL;
77
+ }
78
+ }
79
+
80
+
81
+ #endif //__RP_STACK__
@@ -0,0 +1,312 @@
1
+ /* Copyright (C) 2005-2013 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
+ #include "ruby_prof.h"
5
+
6
+ VALUE cRpThread;
7
+
8
+ /* ====== thread_data_t ====== */
9
+ thread_data_t*
10
+ thread_data_create(size_t measurements_len)
11
+ {
12
+ thread_data_t* result = ALLOC(thread_data_t);
13
+ result->stack = prof_stack_create(measurements_len);
14
+ result->method_table = method_table_create();
15
+ result->object = Qnil;
16
+ result->methods = Qnil;
17
+ return result;
18
+ }
19
+
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)
28
+ {
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;
38
+ }
39
+
40
+ static void
41
+ thread_data_free(thread_data_t* thread_data)
42
+ {
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);
50
+ }
51
+
52
+ static int
53
+ mark_methods(st_data_t key, st_data_t value, st_data_t result)
54
+ {
55
+ prof_method_t *method = (prof_method_t *) value;
56
+ prof_method_mark(method);
57
+ return ST_CONTINUE;
58
+ }
59
+
60
+ void
61
+ prof_thread_mark(thread_data_t *thread)
62
+ {
63
+ if (thread->object != Qnil)
64
+ rb_gc_mark(thread->object);
65
+
66
+ if (thread->methods != Qnil)
67
+ rb_gc_mark(thread->methods);
68
+
69
+ if (thread->thread_id != Qnil)
70
+ rb_gc_mark(thread->thread_id);
71
+
72
+ if (thread->fiber_id != Qnil)
73
+ rb_gc_mark(thread->fiber_id);
74
+
75
+ st_foreach(thread->method_table, mark_methods, 0);
76
+ }
77
+
78
+ VALUE
79
+ prof_thread_wrap(thread_data_t *thread)
80
+ {
81
+ if (thread->object == Qnil) {
82
+ thread->object = Data_Wrap_Struct(cRpThread, prof_thread_mark, thread_data_ruby_gc_free, thread);
83
+ }
84
+ return thread->object;
85
+ }
86
+
87
+ thread_data_t*
88
+ prof_get_thread(VALUE self)
89
+ {
90
+ /* Can't use Data_Get_Struct because that triggers the event hook
91
+ ending up in endless recursion. */
92
+ thread_data_t* result = DATA_PTR(self);
93
+ if (!result)
94
+ rb_raise(rb_eRuntimeError, "This RubyProf::Thread instance has already been freed, likely because its profile has been freed.");
95
+
96
+ return result;
97
+ }
98
+
99
+ /* ====== Thread Table ====== */
100
+ /* The thread table is hash keyed on ruby thread_id that stores instances
101
+ of thread_data_t. */
102
+
103
+ st_table *
104
+ threads_table_create()
105
+ {
106
+ return st_init_numtable();
107
+ }
108
+
109
+ static int
110
+ thread_table_free_iterator(st_data_t key, st_data_t value, st_data_t dummy)
111
+ {
112
+ thread_data_free((thread_data_t*)value);
113
+ return ST_CONTINUE;
114
+ }
115
+
116
+ void
117
+ threads_table_free(st_table *table)
118
+ {
119
+ st_foreach(table, thread_table_free_iterator, 0);
120
+ st_free_table(table);
121
+ }
122
+
123
+ size_t
124
+ threads_table_insert(prof_profile_t* profile, VALUE fiber, thread_data_t *thread_data)
125
+ {
126
+ /* Its too slow to key on the real thread id so just typecast thread instead. */
127
+ return st_insert(profile->threads_tbl, (st_data_t) fiber, (st_data_t) thread_data);
128
+ }
129
+
130
+ thread_data_t *
131
+ threads_table_lookup(prof_profile_t* profile, VALUE thread_id, VALUE fiber_id)
132
+ {
133
+ thread_data_t* result;
134
+ st_data_t val;
135
+
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
+ if (st_lookup(profile->threads_tbl, (st_data_t) key, &val))
142
+ {
143
+ result = (thread_data_t *) val;
144
+ }
145
+ else
146
+ {
147
+ result = thread_data_create(profile->measurer->len);
148
+
149
+ result->thread_index = 0;
150
+ result->thread_id = thread_id;
151
+ /* We set fiber id to 0 in the merge fiber case. Real fibers never have id 0,
152
+ so we can identify them later during printing.
153
+ */
154
+ result->fiber_id = profile->merge_fibers ? INT2FIX(0) : fiber_id;
155
+
156
+ if (thread_id != profile->main_thread_id) {
157
+ result->thread_index = profile->next_thread_index++;
158
+ }
159
+
160
+ /* Insert the table */
161
+ threads_table_insert(profile, key, result);
162
+ }
163
+ return result;
164
+ }
165
+
166
+ thread_data_t *
167
+ switch_thread(void* prof, VALUE thread_id, VALUE fiber_id)
168
+ {
169
+ prof_profile_t* profile = (prof_profile_t*)prof;
170
+ prof_measurer_take_measurements(profile->measurer, profile->measurements);
171
+
172
+ /* Get new thread information. */
173
+ thread_data_t *thread_data = threads_table_lookup(profile, thread_id, fiber_id);
174
+
175
+ /* Get current frame for this thread */
176
+ prof_frame_t *frame = prof_stack_peek(thread_data->stack);
177
+
178
+ /* Update the time this thread waited for another thread */
179
+ if (frame)
180
+ {
181
+ for (size_t i; i < frame->measurements_len; i++) {
182
+ frame->measurements[i].wait += profile->measurements->values[i] - frame->measurements[i].switch_t;
183
+ frame->measurements[i].switch_t = profile->measurements->values[i];
184
+ }
185
+ }
186
+
187
+ /* Save on the last thread the time of the context switch
188
+ and reset this thread's last context switch to 0.*/
189
+ if (profile->last_thread_data)
190
+ {
191
+ prof_frame_t *last_frame = prof_stack_peek(profile->last_thread_data->stack);
192
+ if (last_frame) {
193
+ for(size_t i; i < last_frame->measurements_len; i++) {
194
+ last_frame->measurements[i].switch_t = profile->measurements->values[i];
195
+ }
196
+ }
197
+ }
198
+
199
+ profile->last_thread_data = thread_data;
200
+ return thread_data;
201
+ }
202
+
203
+ int pause_thread(st_data_t key, st_data_t value, st_data_t data)
204
+ {
205
+ thread_data_t* thread_data = (thread_data_t *) value;
206
+ prof_profile_t* profile = (prof_profile_t*)data;
207
+
208
+ prof_frame_t* frame = prof_stack_peek(thread_data->stack);
209
+ prof_frame_pause(frame, profile->measurements_at_pause_resume);
210
+
211
+ return ST_CONTINUE;
212
+ }
213
+
214
+ int unpause_thread(st_data_t key, st_data_t value, st_data_t data)
215
+ {
216
+ thread_data_t* thread_data = (thread_data_t *) value;
217
+ prof_profile_t* profile = (prof_profile_t*)data;
218
+
219
+ prof_frame_t* frame = prof_stack_peek(thread_data->stack);
220
+ prof_frame_unpause(frame, profile->measurements_at_pause_resume);
221
+
222
+ return ST_CONTINUE;
223
+ }
224
+
225
+ static int
226
+ collect_methods(st_data_t key, st_data_t value, st_data_t result)
227
+ {
228
+ /* Called for each method stored in a thread's method table.
229
+ We want to store the method info information into an array.*/
230
+ VALUE methods = (VALUE) result;
231
+ prof_method_t *method = (prof_method_t *) value;
232
+
233
+ if (!method->excluded) {
234
+ rb_ary_push(methods, prof_method_wrap(method));
235
+ }
236
+
237
+ return ST_CONTINUE;
238
+ }
239
+
240
+ /* call-seq:
241
+ index -> number
242
+
243
+ Returns the index of this thread. */
244
+ static VALUE
245
+ prof_thread_index(VALUE self)
246
+ {
247
+ thread_data_t* thread = prof_get_thread(self);
248
+ return ULL2NUM(thread->thread_index);
249
+ }
250
+
251
+ /* call-seq:
252
+ main? -> bool
253
+
254
+ Returns the true if this is the main thread. */
255
+ static VALUE
256
+ prof_thread_main(VALUE self)
257
+ {
258
+ thread_data_t* thread = prof_get_thread(self);
259
+ return thread->thread_index == 0 ? Qtrue : Qfalse;
260
+ }
261
+
262
+ /* call-seq:
263
+ id -> number
264
+
265
+ Returns the id of this thread. */
266
+ static VALUE
267
+ prof_thread_id(VALUE self)
268
+ {
269
+ thread_data_t* thread = prof_get_thread(self);
270
+ return thread->thread_id;
271
+ }
272
+
273
+ /* call-seq:
274
+ fiber_id -> number
275
+
276
+ Returns the fiber id of this thread. */
277
+ static VALUE
278
+ prof_fiber_id(VALUE self)
279
+ {
280
+ thread_data_t* thread = prof_get_thread(self);
281
+ return thread->fiber_id;
282
+ }
283
+
284
+ /* call-seq:
285
+ methods -> Array of MethodInfo
286
+
287
+ Returns an array of methods that were called from this
288
+ thread during program execution. */
289
+ static VALUE
290
+ prof_thread_methods(VALUE self)
291
+ {
292
+ thread_data_t* thread = prof_get_thread(self);
293
+ if (thread->methods == Qnil)
294
+ {
295
+ thread->methods = rb_ary_new();
296
+ st_foreach(thread->method_table, collect_methods, thread->methods);
297
+ }
298
+ return thread->methods;
299
+ }
300
+
301
+ void rp_init_thread()
302
+ {
303
+ cRpThread = rb_define_class_under(mProf, "Thread", rb_cObject);
304
+ rb_undef_method(CLASS_OF(cRpThread), "new");
305
+
306
+ rb_define_method(cRpThread, "index", prof_thread_index, 0);
307
+ rb_define_method(cRpThread, "main?", prof_thread_main, 0);
308
+
309
+ rb_define_method(cRpThread, "id", prof_thread_id, 0);
310
+ rb_define_method(cRpThread, "fiber_id", prof_fiber_id, 0);
311
+ rb_define_method(cRpThread, "methods", prof_thread_methods, 0);
312
+ }