ruby-prof 0.10.8 → 0.11.0.rc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGES +38 -18
- data/LICENSE +4 -3
- data/README.rdoc +30 -66
- data/Rakefile +47 -54
- data/bin/ruby-prof +24 -4
- data/ext/ruby_prof/extconf.rb +9 -16
- data/ext/ruby_prof/rp_call_info.c +369 -0
- data/ext/ruby_prof/rp_call_info.h +46 -0
- data/ext/ruby_prof/rp_measure.c +48 -0
- data/ext/ruby_prof/rp_measure.h +45 -0
- data/ext/ruby_prof/rp_measure_allocations.c +86 -0
- data/ext/ruby_prof/rp_measure_cpu_time.c +112 -0
- data/ext/ruby_prof/rp_measure_gc_runs.c +87 -0
- data/ext/ruby_prof/rp_measure_gc_time.c +73 -0
- data/ext/ruby_prof/rp_measure_memory.c +81 -0
- data/ext/ruby_prof/rp_measure_process_time.c +71 -0
- data/ext/ruby_prof/rp_measure_wall_time.c +42 -0
- data/ext/ruby_prof/rp_method.c +363 -0
- data/ext/ruby_prof/rp_method.h +55 -0
- data/ext/ruby_prof/rp_stack.c +61 -0
- data/ext/ruby_prof/rp_stack.h +40 -0
- data/ext/ruby_prof/rp_thread.c +113 -0
- data/ext/ruby_prof/rp_thread.h +20 -0
- data/ext/ruby_prof/ruby_prof.c +259 -1398
- data/ext/ruby_prof/ruby_prof.h +54 -190
- data/ext/ruby_prof/version.h +6 -3
- data/lib/1.8/ruby_prof.so +0 -0
- data/lib/1.9/ruby_prof.exp +0 -0
- data/lib/1.9/ruby_prof.ilk +0 -0
- data/lib/1.9/ruby_prof.lib +0 -0
- data/lib/1.9/ruby_prof.pdb +0 -0
- data/lib/1.9/ruby_prof.so +0 -0
- data/lib/ruby-prof.rb +14 -11
- data/lib/ruby-prof/abstract_printer.rb +10 -0
- data/lib/ruby-prof/aggregate_call_info.rb +2 -0
- data/lib/ruby-prof/call_info.rb +2 -0
- data/lib/ruby-prof/call_stack_printer.rb +2 -4
- data/lib/ruby-prof/call_tree_printer.rb +1 -0
- data/lib/ruby-prof/compatibility.rb +134 -0
- data/lib/ruby-prof/dot_printer.rb +7 -7
- data/lib/ruby-prof/flat_printer.rb +7 -7
- data/lib/ruby-prof/flat_printer_with_line_numbers.rb +2 -5
- data/lib/ruby-prof/graph_html_printer.rb +4 -2
- data/lib/ruby-prof/graph_printer.rb +4 -3
- data/lib/ruby-prof/method_info.rb +2 -0
- data/lib/ruby-prof/multi_printer.rb +2 -0
- data/lib/ruby-prof/{result.rb → profile.rb} +3 -1
- data/lib/ruby-prof/rack.rb +1 -0
- data/lib/ruby-prof/symbol_to_proc.rb +2 -0
- data/lib/ruby-prof/task.rb +1 -0
- data/lib/ruby-prof/test.rb +2 -0
- data/lib/ruby_prof.exp +0 -0
- data/lib/ruby_prof.ilk +0 -0
- data/lib/ruby_prof.lib +0 -0
- data/lib/ruby_prof.pdb +0 -0
- data/lib/ruby_prof.so +0 -0
- data/lib/unprof.rb +2 -0
- data/test/aggregate_test.rb +8 -8
- data/test/basic_test.rb +3 -251
- data/test/bug_test.rb +6 -0
- data/test/duplicate_names_test.rb +2 -2
- data/test/dynamic_method_test.rb +61 -0
- data/test/enumerable_test.rb +2 -2
- data/test/exceptions_test.rb +4 -3
- data/test/exclude_threads_test.rb +2 -2
- data/test/exec_test.rb +3 -3
- data/test/line_number_test.rb +5 -5
- data/test/measure_allocations_test.rb +25 -0
- data/test/measure_cpu_time_test.rb +212 -0
- data/test/measure_gc_runs_test.rb +29 -0
- data/test/measure_gc_time_test.rb +29 -0
- data/test/measure_memory_test.rb +36 -0
- data/test/measure_process_time_test.rb +205 -0
- data/test/measure_wall_time_test.rb +209 -0
- data/test/method_elimination_test.rb +2 -2
- data/test/module_test.rb +3 -2
- data/test/multi_printer_test.rb +2 -2
- data/test/no_method_class_test.rb +3 -1
- data/test/prime_test.rb +3 -3
- data/test/printers_test.rb +106 -8
- data/test/recursive_test.rb +7 -6
- data/test/singleton_test.rb +2 -2
- data/test/stack_printer_test.rb +2 -3
- data/test/stack_test.rb +2 -2
- data/test/start_stop_test.rb +2 -2
- data/test/test_helper.rb +81 -0
- data/test/test_suite.rb +34 -29
- data/test/thread_test.rb +24 -23
- data/test/unique_call_path_test.rb +2 -2
- metadata +101 -69
- data/ext/ruby_prof/measure_allocations.h +0 -83
- data/ext/ruby_prof/measure_cpu_time.h +0 -152
- data/ext/ruby_prof/measure_gc_runs.h +0 -76
- data/ext/ruby_prof/measure_gc_time.h +0 -57
- data/ext/ruby_prof/measure_memory.h +0 -101
- data/ext/ruby_prof/measure_process_time.h +0 -63
- data/ext/ruby_prof/measure_wall_time.h +0 -53
- data/ext/ruby_prof/mingw/Rakefile +0 -23
- data/ext/ruby_prof/mingw/build.rake +0 -38
- data/rails/environment/profile.rb +0 -24
- data/rails/example/example_test.rb +0 -9
- data/rails/profile_test_helper.rb +0 -21
- data/test/current_failures_windows +0 -8
- data/test/measurement_test.rb +0 -132
- data/test/ruby-prof-bin +0 -20
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/* Copyright (C) 2005-2011 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
|
+
#ifndef RUBY_VM
|
|
10
|
+
#include <st.h>
|
|
11
|
+
typedef st_data_t st_index_t;
|
|
12
|
+
#endif
|
|
13
|
+
|
|
14
|
+
extern VALUE cMethodInfo;
|
|
15
|
+
|
|
16
|
+
/* A key used to identify each method */
|
|
17
|
+
typedef struct
|
|
18
|
+
{
|
|
19
|
+
VALUE klass; /* The method's class. */
|
|
20
|
+
ID mid; /* The method id. */
|
|
21
|
+
int depth; /* The recursion depth. */
|
|
22
|
+
st_index_t key; /* Cache calculated key */
|
|
23
|
+
} prof_method_key_t;
|
|
24
|
+
|
|
25
|
+
/* Forward declaration, see rp_call_info.h */
|
|
26
|
+
struct prof_call_infos_t;
|
|
27
|
+
|
|
28
|
+
/* Profiling information for each method. */
|
|
29
|
+
typedef struct
|
|
30
|
+
{
|
|
31
|
+
prof_method_key_t *key; /* Method key */
|
|
32
|
+
const char *source_file; /* The method's source file */
|
|
33
|
+
int line; /* The method's line number. */
|
|
34
|
+
struct prof_call_infos_t *call_infos; /* Call info objects for this method */
|
|
35
|
+
VALUE object; /* Cached ruby object */
|
|
36
|
+
} prof_method_t;
|
|
37
|
+
|
|
38
|
+
void rp_init_method_info(void);
|
|
39
|
+
|
|
40
|
+
st_table * method_table_create();
|
|
41
|
+
prof_method_t * method_table_lookup(st_table *table, const prof_method_key_t* key);
|
|
42
|
+
size_t method_table_insert(st_table *table, const prof_method_key_t *key, prof_method_t *val);
|
|
43
|
+
void method_table_free(st_table *table);
|
|
44
|
+
|
|
45
|
+
prof_method_t* prof_method_create(prof_method_key_t *key, const char* source_file, int line);
|
|
46
|
+
VALUE prof_method_wrap(prof_method_t *result);
|
|
47
|
+
void prof_method_mark(prof_method_t *method);
|
|
48
|
+
|
|
49
|
+
/* Setup infrastructure to use method keys as hash comparisons */
|
|
50
|
+
int method_table_cmp(prof_method_key_t *key1, prof_method_key_t *key2);
|
|
51
|
+
st_index_t method_table_hash(prof_method_key_t *key);
|
|
52
|
+
|
|
53
|
+
extern struct st_hash_type type_method_hash;
|
|
54
|
+
|
|
55
|
+
#endif
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/* Copyright (C) 2005-2011 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
|
+
/* Creates a stack of prof_frame_t to keep track
|
|
10
|
+
of timings for active methods. */
|
|
11
|
+
prof_stack_t *
|
|
12
|
+
stack_create()
|
|
13
|
+
{
|
|
14
|
+
prof_stack_t *stack = ALLOC(prof_stack_t);
|
|
15
|
+
stack->start = ALLOC_N(prof_frame_t, INITIAL_STACK_SIZE);
|
|
16
|
+
stack->ptr = stack->start;
|
|
17
|
+
stack->end = stack->start + INITIAL_STACK_SIZE;
|
|
18
|
+
|
|
19
|
+
return stack;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
void
|
|
23
|
+
stack_free(prof_stack_t *stack)
|
|
24
|
+
{
|
|
25
|
+
xfree(stack->start);
|
|
26
|
+
xfree(stack);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
prof_frame_t *
|
|
30
|
+
stack_push(prof_stack_t *stack)
|
|
31
|
+
{
|
|
32
|
+
/* Is there space on the stack? If not, double
|
|
33
|
+
its size. */
|
|
34
|
+
if (stack->ptr == stack->end )
|
|
35
|
+
{
|
|
36
|
+
size_t len = stack->ptr - stack->start;
|
|
37
|
+
size_t new_capacity = (stack->end - stack->start) * 2;
|
|
38
|
+
REALLOC_N(stack->start, prof_frame_t, new_capacity);
|
|
39
|
+
stack->ptr = stack->start + len;
|
|
40
|
+
stack->end = stack->start + new_capacity;
|
|
41
|
+
}
|
|
42
|
+
return stack->ptr++;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
prof_frame_t *
|
|
46
|
+
stack_pop(prof_stack_t *stack)
|
|
47
|
+
{
|
|
48
|
+
if (stack->ptr == stack->start)
|
|
49
|
+
return NULL;
|
|
50
|
+
else
|
|
51
|
+
return --stack->ptr;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
prof_frame_t *
|
|
55
|
+
stack_peek(prof_stack_t *stack)
|
|
56
|
+
{
|
|
57
|
+
if (stack->ptr == stack->start)
|
|
58
|
+
return NULL;
|
|
59
|
+
else
|
|
60
|
+
return stack->ptr - 1;
|
|
61
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/* Copyright (C) 2005-2011 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
|
+
/* Temporary object that maintains profiling information
|
|
14
|
+
for active methods - there is one per method.*/
|
|
15
|
+
typedef struct
|
|
16
|
+
{
|
|
17
|
+
/* Caching prof_method_t values significantly
|
|
18
|
+
increases performance. */
|
|
19
|
+
prof_call_info_t *call_info;
|
|
20
|
+
double start_time;
|
|
21
|
+
double wait_time;
|
|
22
|
+
double child_time;
|
|
23
|
+
unsigned int line;
|
|
24
|
+
} prof_frame_t;
|
|
25
|
+
|
|
26
|
+
/* Current stack of active methods.*/
|
|
27
|
+
typedef struct
|
|
28
|
+
{
|
|
29
|
+
prof_frame_t *start;
|
|
30
|
+
prof_frame_t *end;
|
|
31
|
+
prof_frame_t *ptr;
|
|
32
|
+
} prof_stack_t;
|
|
33
|
+
|
|
34
|
+
prof_stack_t * stack_create();
|
|
35
|
+
void stack_free(prof_stack_t *stack);
|
|
36
|
+
prof_frame_t * stack_push(prof_stack_t *stack);
|
|
37
|
+
prof_frame_t * stack_pop(prof_stack_t *stack);
|
|
38
|
+
prof_frame_t * stack_peek(prof_stack_t *stack);
|
|
39
|
+
|
|
40
|
+
#endif //__RP_STACK__
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/* Copyright (C) 2005-2011 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
|
+
/* ====== thread_data_t ====== */
|
|
7
|
+
thread_data_t*
|
|
8
|
+
thread_data_create(double measure)
|
|
9
|
+
{
|
|
10
|
+
thread_data_t* result = ALLOC(thread_data_t);
|
|
11
|
+
result->stack = stack_create();
|
|
12
|
+
result->method_table = method_table_create();
|
|
13
|
+
result->last_switch = measure;
|
|
14
|
+
return result;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
void
|
|
18
|
+
thread_data_free(thread_data_t* thread_data)
|
|
19
|
+
{
|
|
20
|
+
method_table_free(thread_data->method_table);
|
|
21
|
+
stack_free(thread_data->stack);
|
|
22
|
+
xfree(thread_data);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
/* ====== Thread Table ====== */
|
|
27
|
+
/* The thread table is hash keyed on ruby thread_id that stores instances
|
|
28
|
+
of thread_data_t. */
|
|
29
|
+
|
|
30
|
+
st_table *
|
|
31
|
+
threads_table_create()
|
|
32
|
+
{
|
|
33
|
+
return st_init_numtable();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
size_t
|
|
37
|
+
threads_table_insert(prof_profile_t* profile, VALUE thread, thread_data_t *thread_data)
|
|
38
|
+
{
|
|
39
|
+
/* Its too slow to key on the real thread id so just typecast thread instead. */
|
|
40
|
+
return st_insert(profile->threads_tbl, (st_data_t) thread, (st_data_t) thread_data);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
thread_data_t *
|
|
44
|
+
threads_table_lookup(prof_profile_t* profile, VALUE thread_id)
|
|
45
|
+
{
|
|
46
|
+
thread_data_t* result;
|
|
47
|
+
st_data_t val;
|
|
48
|
+
|
|
49
|
+
/* Its too slow to key on the real thread id so just typecast thread instead. */
|
|
50
|
+
if (st_lookup(profile->threads_tbl, (st_data_t) thread_id, &val))
|
|
51
|
+
{
|
|
52
|
+
result = (thread_data_t *) val;
|
|
53
|
+
}
|
|
54
|
+
else
|
|
55
|
+
{
|
|
56
|
+
result = thread_data_create(profile->measurer->measure());
|
|
57
|
+
result->thread_id = thread_id;
|
|
58
|
+
|
|
59
|
+
/* Insert the table */
|
|
60
|
+
threads_table_insert(profile, thread_id, result);
|
|
61
|
+
}
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
int
|
|
66
|
+
free_thread_data(st_data_t key, st_data_t value, st_data_t dummy)
|
|
67
|
+
{
|
|
68
|
+
thread_data_free((thread_data_t*)value);
|
|
69
|
+
return ST_CONTINUE;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
void
|
|
73
|
+
threads_table_free(st_table *table)
|
|
74
|
+
{
|
|
75
|
+
st_foreach(table, free_thread_data, 0);
|
|
76
|
+
st_free_table(table);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
thread_data_t *
|
|
80
|
+
switch_thread(void* prof, VALUE thread_id)
|
|
81
|
+
{
|
|
82
|
+
prof_profile_t* profile = (prof_profile_t*)prof;
|
|
83
|
+
prof_frame_t *frame = NULL;
|
|
84
|
+
double wait_time = 0;
|
|
85
|
+
|
|
86
|
+
/* Get new thread information. */
|
|
87
|
+
thread_data_t *thread_data = threads_table_lookup(profile, thread_id);
|
|
88
|
+
|
|
89
|
+
/* How long has this thread been waiting? */
|
|
90
|
+
wait_time = profile->measurement - thread_data->last_switch;
|
|
91
|
+
|
|
92
|
+
thread_data->last_switch = profile->measurement; // XXXX a test that fails if this is 0
|
|
93
|
+
|
|
94
|
+
/* Get the frame at the top of the stack. This may represent
|
|
95
|
+
the current method (EVENT_LINE, EVENT_RETURN) or the
|
|
96
|
+
previous method (EVENT_CALL).*/
|
|
97
|
+
frame = stack_peek(thread_data->stack);
|
|
98
|
+
|
|
99
|
+
if (frame)
|
|
100
|
+
{
|
|
101
|
+
frame->wait_time += wait_time;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/* Save on the last thread the time of the context switch
|
|
105
|
+
and reset this thread's last context switch to 0.*/
|
|
106
|
+
if (profile->last_thread_data)
|
|
107
|
+
{
|
|
108
|
+
profile->last_thread_data->last_switch = profile->measurement;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
profile->last_thread_data = thread_data;
|
|
112
|
+
return thread_data;
|
|
113
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/* Copyright (C) 2005-2011 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_THREAD__
|
|
5
|
+
#define __RP_THREAD__
|
|
6
|
+
|
|
7
|
+
/* Profiling information for a thread. */
|
|
8
|
+
typedef struct
|
|
9
|
+
{
|
|
10
|
+
VALUE thread_id; /* Thread id */
|
|
11
|
+
st_table* method_table; /* Methods called in the thread */
|
|
12
|
+
prof_stack_t* stack; /* Active methods */
|
|
13
|
+
double last_switch; /* Point of last context switch */
|
|
14
|
+
} thread_data_t;
|
|
15
|
+
|
|
16
|
+
st_table * threads_table_create();
|
|
17
|
+
thread_data_t* switch_thread(void* prof, VALUE thread_id);
|
|
18
|
+
void threads_table_free(st_table *table);
|
|
19
|
+
|
|
20
|
+
#endif //__RP_THREAD__
|
data/ext/ruby_prof/ruby_prof.c
CHANGED
|
@@ -1,918 +1,56 @@
|
|
|
1
|
-
/*
|
|
2
|
-
|
|
3
|
-
* Charlie Savage <cfis@savagexi.com>
|
|
4
|
-
* All rights reserved.
|
|
5
|
-
*
|
|
6
|
-
* Redistribution and use in source and binary forms, with or without
|
|
7
|
-
* modification, are permitted provided that the following conditions
|
|
8
|
-
* are met:
|
|
9
|
-
* 1. Redistributions of source code must retain the above copyright
|
|
10
|
-
* notice, this list of conditions and the following disclaimer.
|
|
11
|
-
* 2. Redistributions in binary form must reproduce the above copyright
|
|
12
|
-
* notice, this list of conditions and the following disclaimer in the
|
|
13
|
-
* documentation and/or other materials provided with the distribution.
|
|
14
|
-
*
|
|
15
|
-
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
|
|
16
|
-
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
17
|
-
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
18
|
-
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
|
|
19
|
-
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
20
|
-
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
21
|
-
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
22
|
-
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
23
|
-
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
24
|
-
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
25
|
-
* SUCH DAMAGE.
|
|
26
|
-
*/
|
|
1
|
+
/* Copyright (C) 2005-2011 Shugo Maeda <shugo@ruby-lang.org> and Charlie Savage <cfis@savagexi.com>
|
|
2
|
+
Please see the LICENSE file for copyright and distribution information */
|
|
27
3
|
|
|
28
4
|
/* ruby-prof tracks the time spent executing every method in ruby programming.
|
|
29
5
|
The main players are:
|
|
30
6
|
|
|
31
|
-
|
|
7
|
+
profile_t - This represents 1 profile.
|
|
32
8
|
thread_data_t - Stores data about a single thread.
|
|
33
9
|
prof_stack_t - The method call stack in a particular thread
|
|
34
|
-
prof_method_t - Profiling information
|
|
10
|
+
prof_method_t - Profiling information about each method
|
|
35
11
|
prof_call_info_t - Keeps track a method's callers and callees.
|
|
36
12
|
|
|
37
|
-
The final
|
|
38
|
-
id. Each thread has
|
|
39
|
-
method id. A hash table is used for quick
|
|
40
|
-
However, it is exposed to Ruby as an array.
|
|
13
|
+
The final result is an instance of a profile object which has a hash table of
|
|
14
|
+
thread_data_t, keyed on the thread id. Each thread in turn has a hash table
|
|
15
|
+
of prof_method_t, keyed on the method id. A hash table is used for quick
|
|
16
|
+
look up when doing a profile. However, it is exposed to Ruby as an array.
|
|
41
17
|
|
|
42
18
|
Each prof_method_t has two hash tables, parent and children, of prof_call_info_t.
|
|
43
|
-
These objects keep track of a method's callers (who called the method) and its
|
|
44
|
-
callees (who the method called). These are keyed the method id, but once again,
|
|
45
|
-
are exposed to Ruby as arrays. Each prof_call_into_t maintains a pointer to the
|
|
46
|
-
caller or callee method, thereby making it easy to navigate through the call
|
|
47
|
-
hierarchy in ruby - which is very helpful for creating call graphs.
|
|
48
|
-
*/
|
|
49
|
-
|
|
50
|
-
#include "ruby_prof.h"
|
|
51
|
-
#include <stdio.h>
|
|
52
|
-
#include <assert.h>
|
|
53
|
-
|
|
54
|
-
/* ================ Helper Functions =================*/
|
|
55
|
-
static VALUE
|
|
56
|
-
figure_singleton_name(VALUE klass)
|
|
57
|
-
{
|
|
58
|
-
VALUE result = Qnil;
|
|
59
|
-
|
|
60
|
-
/* We have come across a singleton object. First
|
|
61
|
-
figure out what it is attached to.*/
|
|
62
|
-
VALUE attached = rb_iv_get(klass, "__attached__");
|
|
63
|
-
|
|
64
|
-
/* Is this a singleton class acting as a metaclass? */
|
|
65
|
-
if (BUILTIN_TYPE(attached) == T_CLASS)
|
|
66
|
-
{
|
|
67
|
-
result = rb_str_new2("<Class::");
|
|
68
|
-
rb_str_append(result, rb_inspect(attached));
|
|
69
|
-
rb_str_cat2(result, ">");
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/* Is this for singleton methods on a module? */
|
|
73
|
-
else if (BUILTIN_TYPE(attached) == T_MODULE)
|
|
74
|
-
{
|
|
75
|
-
result = rb_str_new2("<Module::");
|
|
76
|
-
rb_str_append(result, rb_inspect(attached));
|
|
77
|
-
rb_str_cat2(result, ">");
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/* Is this for singleton methods on an object? */
|
|
81
|
-
else if (BUILTIN_TYPE(attached) == T_OBJECT)
|
|
82
|
-
{
|
|
83
|
-
/* Make sure to get the super class so that we don't
|
|
84
|
-
mistakenly grab a T_ICLASS which would lead to
|
|
85
|
-
unknown method errors. */
|
|
86
|
-
#ifdef HAVE_RB_CLASS_SUPERCLASS
|
|
87
|
-
// 1.9.3
|
|
88
|
-
VALUE super = rb_class_superclass(klass);
|
|
89
|
-
#else
|
|
90
|
-
# ifdef RCLASS_SUPER
|
|
91
|
-
VALUE super = rb_class_real(RCLASS_SUPER(klass));
|
|
92
|
-
# else
|
|
93
|
-
VALUE super = rb_class_real(RCLASS(klass)->super);
|
|
94
|
-
# endif
|
|
95
|
-
#endif
|
|
96
|
-
result = rb_str_new2("<Object::");
|
|
97
|
-
rb_str_append(result, rb_inspect(super));
|
|
98
|
-
rb_str_cat2(result, ">");
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/* Ok, this could be other things like an array made put onto
|
|
102
|
-
a singleton object (yeah, it happens, see the singleton
|
|
103
|
-
objects test case). */
|
|
104
|
-
else
|
|
105
|
-
{
|
|
106
|
-
result = rb_inspect(klass);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return result;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
static VALUE
|
|
113
|
-
klass_name(VALUE klass)
|
|
114
|
-
{
|
|
115
|
-
VALUE result = Qnil;
|
|
116
|
-
|
|
117
|
-
if (klass == 0 || klass == Qnil)
|
|
118
|
-
{
|
|
119
|
-
result = rb_str_new2("Global");
|
|
120
|
-
}
|
|
121
|
-
else if (BUILTIN_TYPE(klass) == T_MODULE)
|
|
122
|
-
{
|
|
123
|
-
result = rb_inspect(klass);
|
|
124
|
-
}
|
|
125
|
-
else if (BUILTIN_TYPE(klass) == T_CLASS && FL_TEST(klass, FL_SINGLETON))
|
|
126
|
-
{
|
|
127
|
-
result = figure_singleton_name(klass);
|
|
128
|
-
}
|
|
129
|
-
else if (BUILTIN_TYPE(klass) == T_CLASS)
|
|
130
|
-
{
|
|
131
|
-
result = rb_inspect(klass);
|
|
132
|
-
}
|
|
133
|
-
else
|
|
134
|
-
{
|
|
135
|
-
/* Should never happen. */
|
|
136
|
-
result = rb_str_new2("Unknown");
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
return result;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
static VALUE
|
|
143
|
-
method_name(ID mid)
|
|
144
|
-
{
|
|
145
|
-
VALUE result;
|
|
146
|
-
|
|
147
|
-
if (mid == ID_ALLOCATOR)
|
|
148
|
-
result = rb_str_new2("allocate");
|
|
149
|
-
else if (mid == 0)
|
|
150
|
-
result = rb_str_new2("[No method]");
|
|
151
|
-
else
|
|
152
|
-
result = rb_String(ID2SYM(mid));
|
|
153
|
-
|
|
154
|
-
return result;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
static VALUE
|
|
158
|
-
full_name(VALUE klass, ID mid)
|
|
159
|
-
{
|
|
160
|
-
VALUE result = klass_name(klass);
|
|
161
|
-
rb_str_cat2(result, "#");
|
|
162
|
-
rb_str_append(result, method_name(mid));
|
|
163
|
-
|
|
164
|
-
return result;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/* ================ Stack Handling =================*/
|
|
168
|
-
/* Creates a stack of prof_frame_t to keep track
|
|
169
|
-
of timings for active methods. */
|
|
170
|
-
static prof_stack_t *
|
|
171
|
-
stack_create()
|
|
172
|
-
{
|
|
173
|
-
prof_stack_t *stack = ALLOC(prof_stack_t);
|
|
174
|
-
stack->start = ALLOC_N(prof_frame_t, INITIAL_STACK_SIZE);
|
|
175
|
-
stack->ptr = stack->start;
|
|
176
|
-
stack->end = stack->start + INITIAL_STACK_SIZE;
|
|
177
|
-
return stack;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
static void
|
|
181
|
-
stack_free(prof_stack_t *stack)
|
|
182
|
-
{
|
|
183
|
-
xfree(stack->start);
|
|
184
|
-
xfree(stack);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
static prof_frame_t *
|
|
188
|
-
stack_push(prof_stack_t *stack)
|
|
189
|
-
{
|
|
190
|
-
/* Is there space on the stack? If not, double
|
|
191
|
-
its size. */
|
|
192
|
-
if (stack->ptr == stack->end)
|
|
193
|
-
{
|
|
194
|
-
size_t len = stack->ptr - stack->start;
|
|
195
|
-
size_t new_capacity = (stack->end - stack->start) * 2;
|
|
196
|
-
REALLOC_N(stack->start, prof_frame_t, new_capacity);
|
|
197
|
-
stack->ptr = stack->start + len;
|
|
198
|
-
stack->end = stack->start + new_capacity;
|
|
199
|
-
}
|
|
200
|
-
return stack->ptr++;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
static prof_frame_t *
|
|
204
|
-
stack_pop(prof_stack_t *stack)
|
|
205
|
-
{
|
|
206
|
-
if (stack->ptr == stack->start)
|
|
207
|
-
return NULL;
|
|
208
|
-
else
|
|
209
|
-
return --stack->ptr;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
static prof_frame_t *
|
|
213
|
-
stack_peek(prof_stack_t *stack)
|
|
214
|
-
{
|
|
215
|
-
if (stack->ptr == stack->start)
|
|
216
|
-
return NULL;
|
|
217
|
-
else
|
|
218
|
-
return stack->ptr - 1;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/* ================ Method Key =================*/
|
|
222
|
-
static int
|
|
223
|
-
method_table_cmp(prof_method_key_t *key1, prof_method_key_t *key2)
|
|
224
|
-
{
|
|
225
|
-
return (key1->klass != key2->klass) || (key1->mid != key2->mid);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
static st_index_t
|
|
229
|
-
method_table_hash(prof_method_key_t *key)
|
|
230
|
-
{
|
|
231
|
-
return key->key;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
static struct st_hash_type type_method_hash = {
|
|
235
|
-
method_table_cmp,
|
|
236
|
-
method_table_hash
|
|
237
|
-
};
|
|
238
|
-
|
|
239
|
-
static void
|
|
240
|
-
method_key(prof_method_key_t* key, VALUE klass, ID mid)
|
|
241
|
-
{
|
|
242
|
-
key->klass = klass;
|
|
243
|
-
key->mid = mid;
|
|
244
|
-
key->key = (klass << 4) + (mid << 2);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
/* ================ Call Info =================*/
|
|
249
|
-
static st_table *
|
|
250
|
-
call_info_table_create()
|
|
251
|
-
{
|
|
252
|
-
return st_init_table(&type_method_hash);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
static size_t
|
|
256
|
-
call_info_table_insert(st_table *table, const prof_method_key_t *key, prof_call_info_t *val)
|
|
257
|
-
{
|
|
258
|
-
return st_insert(table, (st_data_t) key, (st_data_t) val);
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
static prof_call_info_t *
|
|
262
|
-
call_info_table_lookup(st_table *table, const prof_method_key_t *key)
|
|
263
|
-
{
|
|
264
|
-
st_data_t val;
|
|
265
|
-
if (st_lookup(table, (st_data_t) key, &val))
|
|
266
|
-
{
|
|
267
|
-
return (prof_call_info_t *) val;
|
|
268
|
-
}
|
|
269
|
-
else
|
|
270
|
-
{
|
|
271
|
-
return NULL;
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
static void
|
|
276
|
-
call_info_table_free(st_table *table)
|
|
277
|
-
{
|
|
278
|
-
st_free_table(table);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
/* Document-class: RubyProf::CallInfo
|
|
282
|
-
RubyProf::CallInfo is a helper class used by RubyProf::MethodInfo
|
|
283
|
-
to keep track of which child methods were called and how long
|
|
284
|
-
they took to execute. */
|
|
285
|
-
|
|
286
|
-
/* :nodoc: */
|
|
287
|
-
static prof_call_info_t *
|
|
288
|
-
prof_call_info_create(prof_method_t* method, prof_call_info_t* parent)
|
|
289
|
-
{
|
|
290
|
-
prof_call_info_t *result = ALLOC(prof_call_info_t);
|
|
291
|
-
result->object = Qnil;
|
|
292
|
-
result->target = method;
|
|
293
|
-
result->parent = parent;
|
|
294
|
-
result->call_infos = call_info_table_create();
|
|
295
|
-
result->children = Qnil;
|
|
296
|
-
|
|
297
|
-
result->called = 0;
|
|
298
|
-
result->total_time = 0;
|
|
299
|
-
result->self_time = 0;
|
|
300
|
-
result->wait_time = 0;
|
|
301
|
-
result->line = 0;
|
|
302
|
-
return result;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
static void prof_method_mark(prof_method_t *method);
|
|
306
|
-
|
|
307
|
-
static void
|
|
308
|
-
prof_call_info_mark(prof_call_info_t *call_info)
|
|
309
|
-
{
|
|
310
|
-
{
|
|
311
|
-
VALUE target = call_info->target->object;
|
|
312
|
-
if (NIL_P(target))
|
|
313
|
-
prof_method_mark(call_info->target);
|
|
314
|
-
else
|
|
315
|
-
rb_gc_mark(target);
|
|
316
|
-
}
|
|
317
|
-
rb_gc_mark(call_info->children);
|
|
318
|
-
if (call_info->parent) {
|
|
319
|
-
VALUE parent = call_info->parent->object;
|
|
320
|
-
if (NIL_P(parent)) {
|
|
321
|
-
prof_call_info_mark(call_info->parent);
|
|
322
|
-
}
|
|
323
|
-
else {
|
|
324
|
-
rb_gc_mark(parent);
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
static void
|
|
330
|
-
prof_call_info_free(prof_call_info_t *call_info)
|
|
331
|
-
{
|
|
332
|
-
call_info_table_free(call_info->call_infos);
|
|
333
|
-
xfree(call_info);
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
static VALUE
|
|
337
|
-
prof_call_info_wrap(prof_call_info_t *call_info)
|
|
338
|
-
{
|
|
339
|
-
if (call_info->object == Qnil)
|
|
340
|
-
{
|
|
341
|
-
call_info->object = Data_Wrap_Struct(cCallInfo, prof_call_info_mark, prof_call_info_free, call_info);
|
|
342
|
-
}
|
|
343
|
-
return call_info->object;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
static prof_call_info_t *
|
|
347
|
-
prof_get_call_info_result(VALUE obj)
|
|
348
|
-
{
|
|
349
|
-
if (BUILTIN_TYPE(obj) != T_DATA)
|
|
350
|
-
{
|
|
351
|
-
/* Should never happen */
|
|
352
|
-
rb_raise(rb_eTypeError, "Not a call info object");
|
|
353
|
-
}
|
|
354
|
-
return (prof_call_info_t *) DATA_PTR(obj);
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
/* call-seq:
|
|
359
|
-
called -> MethodInfo
|
|
360
|
-
|
|
361
|
-
Returns the target method. */
|
|
362
|
-
static VALUE
|
|
363
|
-
prof_call_info_target(VALUE self)
|
|
364
|
-
{
|
|
365
|
-
/* Target is a pointer to a method_info - so we have to be careful
|
|
366
|
-
about the GC. We will wrap the method_info but provide no
|
|
367
|
-
free method so the underlying object is not freed twice! */
|
|
368
|
-
|
|
369
|
-
prof_call_info_t *result = prof_get_call_info_result(self);
|
|
370
|
-
return prof_method_wrap(result->target);
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
/* call-seq:
|
|
374
|
-
called -> int
|
|
375
|
-
|
|
376
|
-
Returns the total amount of times this method was called. */
|
|
377
|
-
static VALUE
|
|
378
|
-
prof_call_info_called(VALUE self)
|
|
379
|
-
{
|
|
380
|
-
prof_call_info_t *result = prof_get_call_info_result(self);
|
|
381
|
-
return INT2NUM(result->called);
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
/* call-seq:
|
|
385
|
-
called=n -> n
|
|
386
|
-
|
|
387
|
-
Sets the call count to n. */
|
|
388
|
-
static VALUE
|
|
389
|
-
prof_call_info_set_called(VALUE self, VALUE called)
|
|
390
|
-
{
|
|
391
|
-
prof_call_info_t *result = prof_get_call_info_result(self);
|
|
392
|
-
result->called = NUM2INT(called);
|
|
393
|
-
return called;
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
/* call-seq:
|
|
397
|
-
line_no -> int
|
|
398
|
-
|
|
399
|
-
returns the line number of the method */
|
|
400
|
-
static VALUE
|
|
401
|
-
prof_call_info_line(VALUE self)
|
|
402
|
-
{
|
|
403
|
-
prof_call_info_t *result = prof_get_call_info_result(self);
|
|
404
|
-
return rb_int_new(result->line);
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
/* call-seq:
|
|
408
|
-
total_time -> float
|
|
409
|
-
|
|
410
|
-
Returns the total amount of time spent in this method and its children. */
|
|
411
|
-
static VALUE
|
|
412
|
-
prof_call_info_total_time(VALUE self)
|
|
413
|
-
{
|
|
414
|
-
prof_call_info_t *result = prof_get_call_info_result(self);
|
|
415
|
-
return rb_float_new(convert_measurement(result->total_time));
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
/* call-seq:
|
|
419
|
-
add_total_time(call_info) -> nil
|
|
420
|
-
|
|
421
|
-
adds total time time from call_info to self. */
|
|
422
|
-
static VALUE
|
|
423
|
-
prof_call_info_add_total_time(VALUE self, VALUE other)
|
|
424
|
-
{
|
|
425
|
-
prof_call_info_t *result = prof_get_call_info_result(self);
|
|
426
|
-
prof_call_info_t *other_info = prof_get_call_info_result(other);
|
|
427
|
-
|
|
428
|
-
result->total_time += other_info->total_time;
|
|
429
|
-
return Qnil;
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
/* call-seq:
|
|
433
|
-
self_time -> float
|
|
434
|
-
|
|
435
|
-
Returns the total amount of time spent in this method. */
|
|
436
|
-
static VALUE
|
|
437
|
-
prof_call_info_self_time(VALUE self)
|
|
438
|
-
{
|
|
439
|
-
prof_call_info_t *result = prof_get_call_info_result(self);
|
|
440
|
-
|
|
441
|
-
return rb_float_new(convert_measurement(result->self_time));
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
/* call-seq:
|
|
445
|
-
add_self_time(call_info) -> nil
|
|
446
|
-
|
|
447
|
-
adds self time from call_info to self. */
|
|
448
|
-
static VALUE
|
|
449
|
-
prof_call_info_add_self_time(VALUE self, VALUE other)
|
|
450
|
-
{
|
|
451
|
-
prof_call_info_t *result = prof_get_call_info_result(self);
|
|
452
|
-
prof_call_info_t *other_info = prof_get_call_info_result(other);
|
|
453
|
-
|
|
454
|
-
result->self_time += other_info->self_time;
|
|
455
|
-
return Qnil;
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
/* call-seq:
|
|
459
|
-
wait_time -> float
|
|
460
|
-
|
|
461
|
-
Returns the total amount of time this method waited for other threads. */
|
|
462
|
-
static VALUE
|
|
463
|
-
prof_call_info_wait_time(VALUE self)
|
|
464
|
-
{
|
|
465
|
-
prof_call_info_t *result = prof_get_call_info_result(self);
|
|
466
|
-
|
|
467
|
-
return rb_float_new(convert_measurement(result->wait_time));
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
/* call-seq:
|
|
471
|
-
add_wait_time(call_info) -> nil
|
|
472
|
-
|
|
473
|
-
adds wait time from call_info to self. */
|
|
474
|
-
|
|
475
|
-
static VALUE
|
|
476
|
-
prof_call_info_add_wait_time(VALUE self, VALUE other)
|
|
477
|
-
{
|
|
478
|
-
prof_call_info_t *result = prof_get_call_info_result(self);
|
|
479
|
-
prof_call_info_t *other_info = prof_get_call_info_result(other);
|
|
480
|
-
|
|
481
|
-
result->wait_time += other_info->wait_time;
|
|
482
|
-
return Qnil;
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
/* call-seq:
|
|
486
|
-
parent -> call_info
|
|
487
|
-
|
|
488
|
-
Returns the call_infos parent call_info object (the method that called this method).*/
|
|
489
|
-
static VALUE
|
|
490
|
-
prof_call_info_parent(VALUE self)
|
|
491
|
-
{
|
|
492
|
-
prof_call_info_t *result = prof_get_call_info_result(self);
|
|
493
|
-
if (result->parent)
|
|
494
|
-
return prof_call_info_wrap(result->parent);
|
|
495
|
-
else
|
|
496
|
-
return Qnil;
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
/* call-seq:
|
|
500
|
-
parent=new_parent -> new_parent
|
|
501
|
-
|
|
502
|
-
Changes the parent of self to new_parent and returns it.*/
|
|
503
|
-
static VALUE
|
|
504
|
-
prof_call_info_set_parent(VALUE self, VALUE new_parent)
|
|
505
|
-
{
|
|
506
|
-
prof_call_info_t *result = prof_get_call_info_result(self);
|
|
507
|
-
if (new_parent == Qnil)
|
|
508
|
-
result->parent = NULL;
|
|
509
|
-
else
|
|
510
|
-
result->parent = prof_get_call_info_result(new_parent);
|
|
511
|
-
return prof_call_info_parent(self);
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
static int
|
|
515
|
-
prof_call_info_collect_children(st_data_t key, st_data_t value, st_data_t result)
|
|
516
|
-
{
|
|
517
|
-
prof_call_info_t *call_info = (prof_call_info_t *) value;
|
|
518
|
-
VALUE arr = (VALUE) result;
|
|
519
|
-
rb_ary_push(arr, prof_call_info_wrap(call_info));
|
|
520
|
-
return ST_CONTINUE;
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
/* call-seq:
|
|
524
|
-
children -> hash
|
|
525
|
-
|
|
526
|
-
Returns an array of call info objects of methods that this method
|
|
527
|
-
called (ie, children).*/
|
|
528
|
-
static VALUE
|
|
529
|
-
prof_call_info_children(VALUE self)
|
|
530
|
-
{
|
|
531
|
-
prof_call_info_t *call_info = prof_get_call_info_result(self);
|
|
532
|
-
if (call_info->children == Qnil)
|
|
533
|
-
{
|
|
534
|
-
call_info->children = rb_ary_new();
|
|
535
|
-
st_foreach(call_info->call_infos, prof_call_info_collect_children, call_info->children);
|
|
536
|
-
}
|
|
537
|
-
return call_info->children;
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
/* ================ Call Infos =================*/
|
|
541
|
-
static prof_call_infos_t*
|
|
542
|
-
prof_call_infos_create()
|
|
543
|
-
{
|
|
544
|
-
prof_call_infos_t *result = ALLOC(prof_call_infos_t);
|
|
545
|
-
result->start = ALLOC_N(prof_call_info_t*, INITIAL_CALL_INFOS_SIZE);
|
|
546
|
-
result->end = result->start + INITIAL_CALL_INFOS_SIZE;
|
|
547
|
-
result->ptr = result->start;
|
|
548
|
-
result->object = Qnil;
|
|
549
|
-
return result;
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
static void
|
|
553
|
-
prof_call_infos_free(prof_call_infos_t *call_infos)
|
|
554
|
-
{
|
|
555
|
-
xfree(call_infos->start);
|
|
556
|
-
xfree(call_infos);
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
static void
|
|
560
|
-
prof_add_call_info(prof_call_infos_t *call_infos, prof_call_info_t *call_info)
|
|
561
|
-
{
|
|
562
|
-
if (call_infos->ptr == call_infos->end)
|
|
563
|
-
{
|
|
564
|
-
size_t len = call_infos->ptr - call_infos->start;
|
|
565
|
-
size_t new_capacity = (call_infos->end - call_infos->start) * 2;
|
|
566
|
-
REALLOC_N(call_infos->start, prof_call_info_t*, new_capacity);
|
|
567
|
-
call_infos->ptr = call_infos->start + len;
|
|
568
|
-
call_infos->end = call_infos->start + new_capacity;
|
|
569
|
-
}
|
|
570
|
-
*call_infos->ptr = call_info;
|
|
571
|
-
call_infos->ptr++;
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
static VALUE
|
|
575
|
-
prof_call_infos_wrap(prof_call_infos_t *call_infos)
|
|
576
|
-
{
|
|
577
|
-
if (call_infos->object == Qnil)
|
|
578
|
-
{
|
|
579
|
-
prof_call_info_t **i;
|
|
580
|
-
call_infos->object = rb_ary_new();
|
|
581
|
-
for(i=call_infos->start; i<call_infos->ptr; i++)
|
|
582
|
-
{
|
|
583
|
-
VALUE call_info = prof_call_info_wrap(*i);
|
|
584
|
-
rb_ary_push(call_infos->object, call_info);
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
return call_infos->object;
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
/* ================ Method Info =================*/
|
|
592
|
-
/* Document-class: RubyProf::MethodInfo
|
|
593
|
-
The RubyProf::MethodInfo class stores profiling data for a method.
|
|
594
|
-
One instance of the RubyProf::MethodInfo class is created per method
|
|
595
|
-
called per thread. Thus, if a method is called in two different
|
|
596
|
-
thread then there will be two RubyProf::MethodInfo objects
|
|
597
|
-
created. RubyProf::MethodInfo objects can be accessed via
|
|
598
|
-
the RubyProf::Result object.
|
|
599
|
-
*/
|
|
600
|
-
|
|
601
|
-
static prof_method_t*
|
|
602
|
-
prof_method_create(prof_method_key_t *key, const char* source_file, int line)
|
|
603
|
-
{
|
|
604
|
-
prof_method_t *result = ALLOC(prof_method_t);
|
|
605
|
-
result->object = Qnil;
|
|
606
|
-
result->key = ALLOC(prof_method_key_t);
|
|
607
|
-
method_key(result->key, key->klass, key->mid);
|
|
608
|
-
|
|
609
|
-
result->call_infos = prof_call_infos_create();
|
|
610
|
-
|
|
611
|
-
if (source_file != NULL)
|
|
612
|
-
{
|
|
613
|
-
size_t len = strlen(source_file) + 1;
|
|
614
|
-
char *buffer = ALLOC_N(char, len);
|
|
615
|
-
|
|
616
|
-
MEMCPY(buffer, source_file, char, len);
|
|
617
|
-
result->source_file = buffer;
|
|
618
|
-
}
|
|
619
|
-
else
|
|
620
|
-
{
|
|
621
|
-
result->source_file = source_file;
|
|
622
|
-
}
|
|
623
|
-
result->line = line;
|
|
624
|
-
|
|
625
|
-
return result;
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
static void
|
|
629
|
-
prof_method_mark(prof_method_t *method)
|
|
630
|
-
{
|
|
631
|
-
rb_gc_mark(method->call_infos->object);
|
|
632
|
-
rb_gc_mark(method->key->klass);
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
static void
|
|
636
|
-
prof_method_free(prof_method_t *method)
|
|
637
|
-
{
|
|
638
|
-
if (method->source_file)
|
|
639
|
-
{
|
|
640
|
-
xfree((char*)method->source_file);
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
prof_call_infos_free(method->call_infos);
|
|
644
|
-
xfree(method->key);
|
|
645
|
-
xfree(method);
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
static VALUE
|
|
649
|
-
prof_method_wrap(prof_method_t *result)
|
|
650
|
-
{
|
|
651
|
-
if (result->object == Qnil)
|
|
652
|
-
{
|
|
653
|
-
result->object = Data_Wrap_Struct(cMethodInfo, prof_method_mark, prof_method_free, result);
|
|
654
|
-
}
|
|
655
|
-
return result->object;
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
static prof_method_t *
|
|
659
|
-
get_prof_method(VALUE obj)
|
|
660
|
-
{
|
|
661
|
-
return (prof_method_t *) DATA_PTR(obj);
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
/* call-seq:
|
|
665
|
-
line_no -> int
|
|
666
|
-
|
|
667
|
-
returns the line number of the method */
|
|
668
|
-
static VALUE
|
|
669
|
-
prof_method_line(VALUE self)
|
|
670
|
-
{
|
|
671
|
-
return rb_int_new(get_prof_method(self)->line);
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
/* call-seq:
|
|
675
|
-
source_file => string
|
|
676
|
-
|
|
677
|
-
return the source file of the method
|
|
678
|
-
*/
|
|
679
|
-
static VALUE prof_method_source_file(VALUE self)
|
|
680
|
-
{
|
|
681
|
-
const char* sf = get_prof_method(self)->source_file;
|
|
682
|
-
if(!sf)
|
|
683
|
-
{
|
|
684
|
-
return rb_str_new2("ruby_runtime");
|
|
685
|
-
}
|
|
686
|
-
else
|
|
687
|
-
{
|
|
688
|
-
return rb_str_new2(sf);
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
/* call-seq:
|
|
694
|
-
method_class -> klass
|
|
695
|
-
|
|
696
|
-
Returns the Ruby klass that owns this method. */
|
|
697
|
-
static VALUE
|
|
698
|
-
prof_method_klass(VALUE self)
|
|
699
|
-
{
|
|
700
|
-
prof_method_t *result = get_prof_method(self);
|
|
701
|
-
return result->key->klass;
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
/* call-seq:
|
|
705
|
-
method_id -> ID
|
|
706
|
-
|
|
707
|
-
Returns the id of this method. */
|
|
708
|
-
static VALUE
|
|
709
|
-
prof_method_id(VALUE self)
|
|
710
|
-
{
|
|
711
|
-
prof_method_t *result = get_prof_method(self);
|
|
712
|
-
return ID2SYM(result->key->mid);
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
/* call-seq:
|
|
716
|
-
klass_name -> string
|
|
717
|
-
|
|
718
|
-
Returns the name of this method's class. Singleton classes
|
|
719
|
-
will have the form <Object::Object>. */
|
|
720
|
-
|
|
721
|
-
static VALUE
|
|
722
|
-
prof_klass_name(VALUE self)
|
|
723
|
-
{
|
|
724
|
-
prof_method_t *method = get_prof_method(self);
|
|
725
|
-
return klass_name(method->key->klass);
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
/* call-seq:
|
|
729
|
-
method_name -> string
|
|
730
|
-
|
|
731
|
-
Returns the name of this method in the format Object#method. Singletons
|
|
732
|
-
methods will be returned in the format <Object::Object>#method.*/
|
|
733
|
-
|
|
734
|
-
static VALUE
|
|
735
|
-
prof_method_name(VALUE self)
|
|
736
|
-
{
|
|
737
|
-
prof_method_t *method = get_prof_method(self);
|
|
738
|
-
return method_name(method->key->mid);
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
/* call-seq:
|
|
742
|
-
full_name -> string
|
|
743
|
-
|
|
744
|
-
Returns the full name of this method in the format Object#method.*/
|
|
745
|
-
|
|
746
|
-
static VALUE
|
|
747
|
-
prof_full_name(VALUE self)
|
|
748
|
-
{
|
|
749
|
-
prof_method_t *method = get_prof_method(self);
|
|
750
|
-
return full_name(method->key->klass, method->key->mid);
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
/* call-seq:
|
|
754
|
-
call_infos -> Array of call_info
|
|
755
|
-
|
|
756
|
-
Returns an array of call info objects that contain profiling information
|
|
757
|
-
about the current method.*/
|
|
758
|
-
static VALUE
|
|
759
|
-
prof_method_call_infos(VALUE self)
|
|
760
|
-
{
|
|
761
|
-
prof_method_t *method = get_prof_method(self);
|
|
762
|
-
return prof_call_infos_wrap(method->call_infos);
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
static int
|
|
766
|
-
collect_methods(st_data_t key, st_data_t value, st_data_t result)
|
|
767
|
-
{
|
|
768
|
-
/* Called for each method stored in a thread's method table.
|
|
769
|
-
We want to store the method info information into an array.*/
|
|
770
|
-
VALUE methods = (VALUE) result;
|
|
771
|
-
prof_method_t *method = (prof_method_t *) value;
|
|
772
|
-
rb_ary_push(methods, prof_method_wrap(method));
|
|
773
|
-
|
|
774
|
-
/* Wrap call info objects */
|
|
775
|
-
prof_call_infos_wrap(method->call_infos);
|
|
776
|
-
|
|
777
|
-
return ST_CONTINUE;
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
/* ================ Method Table =================*/
|
|
781
|
-
static st_table *
|
|
782
|
-
method_table_create()
|
|
783
|
-
{
|
|
784
|
-
return st_init_table(&type_method_hash);
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
static size_t
|
|
788
|
-
method_table_insert(st_table *table, const prof_method_key_t *key, prof_method_t *val)
|
|
789
|
-
{
|
|
790
|
-
return st_insert(table, (st_data_t) key, (st_data_t) val);
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
static prof_method_t *
|
|
794
|
-
method_table_lookup(st_table *table, const prof_method_key_t* key)
|
|
795
|
-
{
|
|
796
|
-
st_data_t val;
|
|
797
|
-
if (st_lookup(table, (st_data_t)key, &val))
|
|
798
|
-
{
|
|
799
|
-
return (prof_method_t *) val;
|
|
800
|
-
}
|
|
801
|
-
else
|
|
802
|
-
{
|
|
803
|
-
return NULL;
|
|
804
|
-
}
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
static void
|
|
809
|
-
method_table_free(st_table *table)
|
|
810
|
-
{
|
|
811
|
-
/* Don't free the contents since they are wrapped by
|
|
812
|
-
Ruby objects! */
|
|
813
|
-
st_free_table(table);
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
/* ================ Thread Handling =================*/
|
|
818
|
-
|
|
819
|
-
/* ---- Keeps track of thread's stack and methods ---- */
|
|
820
|
-
static thread_data_t*
|
|
821
|
-
thread_data_create()
|
|
822
|
-
{
|
|
823
|
-
thread_data_t* result = ALLOC(thread_data_t);
|
|
824
|
-
result->stack = stack_create();
|
|
825
|
-
result->method_table = method_table_create();
|
|
826
|
-
result->last_switch = get_measurement();
|
|
827
|
-
return result;
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
static void
|
|
831
|
-
thread_data_free(thread_data_t* thread_data)
|
|
832
|
-
{
|
|
833
|
-
method_table_free(thread_data->method_table);
|
|
834
|
-
stack_free(thread_data->stack);
|
|
835
|
-
xfree(thread_data);
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
/* ---- Hash, keyed on thread, that stores thread's stack
|
|
839
|
-
and methods---- */
|
|
840
|
-
|
|
841
|
-
static st_table *
|
|
842
|
-
threads_table_create()
|
|
843
|
-
{
|
|
844
|
-
return st_init_numtable();
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
static size_t
|
|
848
|
-
threads_table_insert(st_table *table, VALUE thread, thread_data_t *thread_data)
|
|
849
|
-
{
|
|
850
|
-
/* Its too slow to key on the real thread id so just typecast thread instead. */
|
|
851
|
-
return st_insert(table, (st_data_t) thread, (st_data_t) thread_data);
|
|
852
|
-
}
|
|
853
|
-
|
|
854
|
-
static thread_data_t *
|
|
855
|
-
threads_table_lookup(st_table *table, VALUE thread_id)
|
|
856
|
-
{
|
|
857
|
-
thread_data_t* result;
|
|
858
|
-
st_data_t val;
|
|
859
|
-
|
|
860
|
-
/* Its too slow to key on the real thread id so just typecast thread instead. */
|
|
861
|
-
if (st_lookup(table, (st_data_t) thread_id, &val))
|
|
862
|
-
{
|
|
863
|
-
result = (thread_data_t *) val;
|
|
864
|
-
}
|
|
865
|
-
else
|
|
866
|
-
{
|
|
867
|
-
result = thread_data_create();
|
|
868
|
-
result->thread_id = thread_id;
|
|
869
|
-
|
|
870
|
-
/* Insert the table */
|
|
871
|
-
threads_table_insert(threads_tbl, thread_id, result);
|
|
872
|
-
}
|
|
873
|
-
return result;
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
static int
|
|
877
|
-
free_thread_data(st_data_t key, st_data_t value, st_data_t dummy)
|
|
878
|
-
{
|
|
879
|
-
thread_data_free((thread_data_t*)value);
|
|
880
|
-
return ST_CONTINUE;
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
static void
|
|
885
|
-
threads_table_free(st_table *table)
|
|
886
|
-
{
|
|
887
|
-
st_foreach(table, free_thread_data, 0);
|
|
888
|
-
st_free_table(table);
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
static int
|
|
893
|
-
collect_threads(st_data_t key, st_data_t value, st_data_t result)
|
|
894
|
-
{
|
|
895
|
-
/* Although threads are keyed on an id, that is actually a
|
|
896
|
-
pointer to the VALUE object of the thread. So its bogus.
|
|
897
|
-
However, in thread_data is the real thread id stored
|
|
898
|
-
as an int. */
|
|
899
|
-
thread_data_t* thread_data = (thread_data_t*) value;
|
|
900
|
-
VALUE threads_hash = (VALUE) result;
|
|
19
|
+
These objects keep track of a method's callers (who called the method) and its
|
|
20
|
+
callees (who the method called). These are keyed the method id, but once again,
|
|
21
|
+
are exposed to Ruby as arrays. Each prof_call_into_t maintains a pointer to the
|
|
22
|
+
caller or callee method, thereby making it easy to navigate through the call
|
|
23
|
+
hierarchy in ruby - which is very helpful for creating call graphs.
|
|
24
|
+
*/
|
|
901
25
|
|
|
902
|
-
|
|
26
|
+
#include "ruby_prof.h"
|
|
27
|
+
#include <stdio.h>
|
|
28
|
+
#include <assert.h>
|
|
903
29
|
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
st_foreach(method_table, collect_methods, methods);
|
|
30
|
+
VALUE mProf;
|
|
31
|
+
VALUE cProfile;
|
|
907
32
|
|
|
908
|
-
|
|
909
|
-
|
|
33
|
+
#ifndef RUBY_VM
|
|
34
|
+
/* Global variable to hold current profile - needed
|
|
35
|
+
prior to Ruby 1.9 */
|
|
36
|
+
static prof_profile_t* pCurrentProfile;
|
|
37
|
+
#endif
|
|
910
38
|
|
|
911
|
-
|
|
39
|
+
static prof_profile_t*
|
|
40
|
+
prof_get_profile(VALUE self)
|
|
41
|
+
{
|
|
42
|
+
/* Can't use Data_Get_Struct because that triggers the event hook
|
|
43
|
+
ending up in endless recursion. */
|
|
44
|
+
return (prof_profile_t*)RDATA(self)->data;
|
|
912
45
|
}
|
|
913
46
|
|
|
914
|
-
|
|
915
|
-
|
|
47
|
+
void
|
|
48
|
+
method_key(prof_method_key_t* key, VALUE klass, ID mid)
|
|
49
|
+
{
|
|
50
|
+
key->klass = klass;
|
|
51
|
+
key->mid = mid;
|
|
52
|
+
key->key = (klass << 4) + (mid << 2);
|
|
53
|
+
}
|
|
916
54
|
|
|
917
55
|
/* support tracing ruby events from ruby-prof. useful for getting at
|
|
918
56
|
what actually happens inside the ruby interpreter (and ruby-prof).
|
|
@@ -986,11 +124,11 @@ static prof_method_t*
|
|
|
986
124
|
}
|
|
987
125
|
|
|
988
126
|
static void
|
|
989
|
-
update_result(
|
|
127
|
+
update_result(double total_time,
|
|
990
128
|
prof_frame_t *parent_frame,
|
|
991
129
|
prof_frame_t *frame)
|
|
992
130
|
{
|
|
993
|
-
|
|
131
|
+
double self_time = total_time - frame->child_time - frame->wait_time;
|
|
994
132
|
prof_call_info_t *call_info = frame->call_info;
|
|
995
133
|
|
|
996
134
|
/* Update information about the current method */
|
|
@@ -1004,44 +142,12 @@ update_result(prof_measure_t total_time,
|
|
|
1004
142
|
call_info->line = parent_frame->line;
|
|
1005
143
|
}
|
|
1006
144
|
|
|
1007
|
-
static thread_data_t *
|
|
1008
|
-
switch_thread(VALUE thread_id, prof_measure_t now)
|
|
1009
|
-
{
|
|
1010
|
-
prof_frame_t *frame = NULL;
|
|
1011
|
-
prof_measure_t wait_time = 0;
|
|
1012
|
-
/* Get new thread information. */
|
|
1013
|
-
thread_data_t *thread_data = threads_table_lookup(threads_tbl, thread_id);
|
|
1014
|
-
|
|
1015
|
-
/* How long has this thread been waiting? */
|
|
1016
|
-
wait_time = now - thread_data->last_switch;
|
|
1017
|
-
|
|
1018
|
-
thread_data->last_switch = now; // XXXX a test that fails if this is 0
|
|
1019
|
-
|
|
1020
|
-
/* Get the frame at the top of the stack. This may represent
|
|
1021
|
-
the current method (EVENT_LINE, EVENT_RETURN) or the
|
|
1022
|
-
previous method (EVENT_CALL).*/
|
|
1023
|
-
frame = stack_peek(thread_data->stack);
|
|
1024
|
-
|
|
1025
|
-
if (frame) {
|
|
1026
|
-
frame->wait_time += wait_time;
|
|
1027
|
-
}
|
|
1028
|
-
|
|
1029
|
-
/* Save on the last thread the time of the context switch
|
|
1030
|
-
and reset this thread's last context switch to 0.*/
|
|
1031
|
-
if (last_thread_data) {
|
|
1032
|
-
last_thread_data->last_switch = now;
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
|
-
last_thread_data = thread_data;
|
|
1036
|
-
return thread_data;
|
|
1037
|
-
}
|
|
1038
|
-
|
|
1039
145
|
static prof_frame_t*
|
|
1040
|
-
pop_frame(thread_data_t *thread_data
|
|
146
|
+
pop_frame(prof_profile_t* profile, thread_data_t *thread_data)
|
|
1041
147
|
{
|
|
1042
148
|
prof_frame_t *frame = NULL;
|
|
1043
149
|
prof_frame_t* parent_frame = NULL;
|
|
1044
|
-
|
|
150
|
+
double total_time;
|
|
1045
151
|
|
|
1046
152
|
frame = stack_pop(thread_data->stack); // only time it's called
|
|
1047
153
|
/* Frame can be null. This can happen if RubProf.start is called from
|
|
@@ -1051,7 +157,7 @@ pop_frame(thread_data_t *thread_data, prof_measure_t now)
|
|
|
1051
157
|
if (frame == NULL) return NULL;
|
|
1052
158
|
|
|
1053
159
|
/* Calculate the total time this method took */
|
|
1054
|
-
total_time =
|
|
160
|
+
total_time = profile->measurement - frame->start_time;
|
|
1055
161
|
|
|
1056
162
|
parent_frame = stack_peek(thread_data->stack);
|
|
1057
163
|
if (parent_frame)
|
|
@@ -1064,18 +170,18 @@ pop_frame(thread_data_t *thread_data, prof_measure_t now)
|
|
|
1064
170
|
}
|
|
1065
171
|
|
|
1066
172
|
static int
|
|
1067
|
-
pop_frames(st_data_t key, st_data_t value, st_data_t
|
|
173
|
+
pop_frames(st_data_t key, st_data_t value, st_data_t data)
|
|
1068
174
|
{
|
|
1069
175
|
VALUE thread_id = (VALUE)key;
|
|
1070
176
|
thread_data_t* thread_data = (thread_data_t *) value;
|
|
1071
|
-
|
|
177
|
+
prof_profile_t* profile = (prof_profile_t*) data;
|
|
1072
178
|
|
|
1073
|
-
if (!last_thread_data || last_thread_data->thread_id != thread_id)
|
|
1074
|
-
thread_data = switch_thread(
|
|
179
|
+
if (!profile->last_thread_data || profile->last_thread_data->thread_id != thread_id)
|
|
180
|
+
thread_data = switch_thread(profile, thread_id);
|
|
1075
181
|
else
|
|
1076
|
-
thread_data = last_thread_data;
|
|
182
|
+
thread_data = profile->last_thread_data;
|
|
1077
183
|
|
|
1078
|
-
while (pop_frame(
|
|
184
|
+
while (pop_frame(profile, thread_data))
|
|
1079
185
|
{
|
|
1080
186
|
}
|
|
1081
187
|
|
|
@@ -1083,25 +189,11 @@ pop_frames(st_data_t key, st_data_t value, st_data_t now_arg)
|
|
|
1083
189
|
}
|
|
1084
190
|
|
|
1085
191
|
static void
|
|
1086
|
-
prof_pop_threads(
|
|
192
|
+
prof_pop_threads(prof_profile_t* profile)
|
|
1087
193
|
{
|
|
1088
|
-
st_foreach(threads_tbl, pop_frames, (st_data_t)
|
|
194
|
+
st_foreach(profile->threads_tbl, pop_frames, (st_data_t) profile);
|
|
1089
195
|
}
|
|
1090
196
|
|
|
1091
|
-
#if RUBY_VERSION == 190
|
|
1092
|
-
# error 1.9.0 not supported (ask for it if you desire it to be supported)
|
|
1093
|
-
#endif
|
|
1094
|
-
|
|
1095
|
-
#if RUBY_VERSION >= 191
|
|
1096
|
-
|
|
1097
|
-
/* Avoid bugs in 1.9.1 */
|
|
1098
|
-
|
|
1099
|
-
static inline void walk_up_until_right_frame(prof_frame_t *frame, thread_data_t* thread_data, ID mid, VALUE klass, prof_measure_t now);
|
|
1100
|
-
void prof_install_hook();
|
|
1101
|
-
void prof_remove_hook();
|
|
1102
|
-
|
|
1103
|
-
#endif
|
|
1104
|
-
|
|
1105
197
|
#ifdef RUBY_VM
|
|
1106
198
|
static void
|
|
1107
199
|
prof_event_hook(rb_event_flag_t event, VALUE data, VALUE self, ID mid, VALUE klass)
|
|
@@ -1110,9 +202,14 @@ static void
|
|
|
1110
202
|
prof_event_hook(rb_event_flag_t event, NODE *node, VALUE self, ID mid, VALUE klass)
|
|
1111
203
|
#endif
|
|
1112
204
|
{
|
|
1113
|
-
|
|
205
|
+
#ifndef RUBY_VM
|
|
206
|
+
prof_profile_t* profile = pCurrentProfile;
|
|
207
|
+
#else
|
|
208
|
+
prof_profile_t* profile = prof_get_profile(data);
|
|
209
|
+
#endif
|
|
210
|
+
|
|
211
|
+
VALUE thread = Qnil;
|
|
1114
212
|
VALUE thread_id = Qnil;
|
|
1115
|
-
prof_measure_t now = 0;
|
|
1116
213
|
thread_data_t* thread_data = NULL;
|
|
1117
214
|
prof_frame_t *frame = NULL;
|
|
1118
215
|
|
|
@@ -1123,8 +220,8 @@ prof_event_hook(rb_event_flag_t event, NODE *node, VALUE self, ID mid, VALUE kla
|
|
|
1123
220
|
}
|
|
1124
221
|
#endif
|
|
1125
222
|
|
|
1126
|
-
/* Get current
|
|
1127
|
-
|
|
223
|
+
/* Get current measurement */
|
|
224
|
+
profile->measurement = profile->measurer->measure();
|
|
1128
225
|
|
|
1129
226
|
if (trace_file != NULL)
|
|
1130
227
|
{
|
|
@@ -1149,38 +246,30 @@ prof_event_hook(rb_event_flag_t event, NODE *node, VALUE self, ID mid, VALUE kla
|
|
|
1149
246
|
}
|
|
1150
247
|
|
|
1151
248
|
fprintf(trace_file, "%2u:%2ums %-8s %s:%2d %s#%s\n",
|
|
1152
|
-
(unsigned int) thread_id, (unsigned int)
|
|
249
|
+
(unsigned int) thread_id, (unsigned int) profile->measurement, event_name, source_file, source_line, class_name, method_name);
|
|
1153
250
|
/* fflush(trace_file); */
|
|
1154
251
|
last_thread_id = thread_id;
|
|
1155
252
|
}
|
|
1156
253
|
|
|
1157
254
|
/* Special case - skip any methods from the mProf
|
|
1158
|
-
module
|
|
255
|
+
module or cProfile class since they clutter
|
|
1159
256
|
the results but aren't important to them results. */
|
|
1160
|
-
if (self == mProf) return;
|
|
257
|
+
if (self == mProf || klass == cProfile) return;
|
|
1161
258
|
|
|
1162
259
|
/* Get the current thread information. */
|
|
1163
260
|
thread = rb_thread_current();
|
|
1164
261
|
thread_id = rb_obj_id(thread);
|
|
1165
262
|
|
|
1166
|
-
|
|
1167
|
-
/* ensure that new threads are hooked [sigh] (bug in core) */
|
|
1168
|
-
prof_remove_hook();
|
|
1169
|
-
prof_install_hook();
|
|
1170
|
-
# endif
|
|
1171
|
-
|
|
1172
|
-
if (exclude_threads_tbl &&
|
|
1173
|
-
st_lookup(exclude_threads_tbl, (st_data_t) thread_id, 0))
|
|
263
|
+
if (st_lookup(profile->exclude_threads_tbl, (st_data_t) thread_id, 0))
|
|
1174
264
|
{
|
|
1175
265
|
return;
|
|
1176
266
|
}
|
|
1177
267
|
|
|
1178
|
-
|
|
1179
268
|
/* Was there a context switch? */
|
|
1180
|
-
if (!last_thread_data || last_thread_data->thread_id != thread_id)
|
|
1181
|
-
thread_data = switch_thread(
|
|
269
|
+
if (!profile->last_thread_data || profile->last_thread_data->thread_id != thread_id)
|
|
270
|
+
thread_data = switch_thread(profile, thread_id);
|
|
1182
271
|
else
|
|
1183
|
-
thread_data = last_thread_data;
|
|
272
|
+
thread_data = profile->last_thread_data;
|
|
1184
273
|
|
|
1185
274
|
|
|
1186
275
|
switch (event) {
|
|
@@ -1196,13 +285,6 @@ prof_event_hook(rb_event_flag_t event, NODE *node, VALUE self, ID mid, VALUE kla
|
|
|
1196
285
|
if (frame)
|
|
1197
286
|
{
|
|
1198
287
|
frame->line = rb_sourceline();
|
|
1199
|
-
|
|
1200
|
-
# if RUBY_VERSION >= 191
|
|
1201
|
-
// disabled it causes
|
|
1202
|
-
// us to lose valuable frame information...maybe mid comes in wrong sometimes?
|
|
1203
|
-
// walk_up_until_right_frame(frame, thread_data, mid, klass, now);
|
|
1204
|
-
# endif
|
|
1205
|
-
|
|
1206
288
|
break;
|
|
1207
289
|
}
|
|
1208
290
|
|
|
@@ -1252,7 +334,7 @@ prof_event_hook(rb_event_flag_t event, NODE *node, VALUE self, ID mid, VALUE kla
|
|
|
1252
334
|
/* Push a new frame onto the stack for a new c-call or ruby call (into a method) */
|
|
1253
335
|
frame = stack_push(thread_data->stack);
|
|
1254
336
|
frame->call_info = call_info;
|
|
1255
|
-
frame->start_time =
|
|
337
|
+
frame->start_time = profile->measurement;
|
|
1256
338
|
frame->wait_time = 0;
|
|
1257
339
|
frame->child_time = 0;
|
|
1258
340
|
frame->line = rb_sourceline();
|
|
@@ -1261,269 +343,172 @@ prof_event_hook(rb_event_flag_t event, NODE *node, VALUE self, ID mid, VALUE kla
|
|
|
1261
343
|
case RUBY_EVENT_RETURN:
|
|
1262
344
|
case RUBY_EVENT_C_RETURN:
|
|
1263
345
|
{
|
|
1264
|
-
frame = pop_frame(
|
|
1265
|
-
|
|
1266
|
-
# if RUBY_VERSION >= 191
|
|
1267
|
-
// we need to walk up the stack to find the right one [http://redmine.ruby-lang.org/issues/show/2610] (for now)
|
|
1268
|
-
// sometimes frames don't have line and source somehow [like blank]
|
|
1269
|
-
// if we hit one there's not much we can do...I guess...
|
|
1270
|
-
// or maybe we don't have one because we're at the top or something.
|
|
1271
|
-
walk_up_until_right_frame(frame, thread_data, mid, klass, now);
|
|
1272
|
-
# endif
|
|
1273
|
-
|
|
346
|
+
frame = pop_frame(profile, thread_data);
|
|
1274
347
|
break;
|
|
1275
348
|
}
|
|
1276
|
-
}
|
|
1277
|
-
}
|
|
1278
|
-
|
|
1279
|
-
#if RUBY_VERSION >= 191
|
|
1280
|
-
|
|
1281
|
-
static inline void walk_up_until_right_frame(prof_frame_t *frame, thread_data_t* thread_data, ID mid, VALUE klass, prof_measure_t now) {
|
|
1282
|
-
// while it doesn't match, pop on up until we have found where we belong...
|
|
1283
|
-
while( frame && frame->call_info->target->key->mid && frame->call_info->target->key->klass && ((frame->call_info->target->key->mid != mid) || (frame->call_info->target->key->klass != klass))){
|
|
1284
|
-
frame = pop_frame(thread_data, now);
|
|
1285
349
|
}
|
|
1286
350
|
}
|
|
1287
|
-
#endif
|
|
1288
351
|
|
|
1289
|
-
/* ======== ProfResult ============== */
|
|
1290
352
|
|
|
1291
|
-
/*
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
the methods RubyProf#stop and RubyProf#profile.
|
|
1295
|
-
|
|
1296
|
-
RubyProf::Result has one field, called threads, which is a hash
|
|
1297
|
-
table keyed on thread ID. For each thread id, the hash table
|
|
1298
|
-
stores another hash table that contains profiling information
|
|
1299
|
-
for each method called during the threads execution. That
|
|
1300
|
-
hash table is keyed on method name and contains
|
|
1301
|
-
RubyProf::MethodInfo objects. */
|
|
1302
|
-
|
|
1303
|
-
static void
|
|
1304
|
-
prof_result_mark(prof_result_t *prof_result)
|
|
353
|
+
/* =========== Profiling ================= */
|
|
354
|
+
void
|
|
355
|
+
prof_install_hook(VALUE self)
|
|
1305
356
|
{
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
357
|
+
#ifdef RUBY_VM
|
|
358
|
+
rb_add_event_hook(prof_event_hook,
|
|
359
|
+
RUBY_EVENT_CALL | RUBY_EVENT_RETURN |
|
|
360
|
+
RUBY_EVENT_C_CALL | RUBY_EVENT_C_RETURN
|
|
361
|
+
| RUBY_EVENT_LINE, self); // RUBY_EVENT_SWITCH
|
|
362
|
+
#else
|
|
363
|
+
rb_add_event_hook(prof_event_hook,
|
|
364
|
+
RUBY_EVENT_CALL | RUBY_EVENT_RETURN |
|
|
365
|
+
RUBY_EVENT_C_CALL | RUBY_EVENT_C_RETURN
|
|
366
|
+
| RUBY_EVENT_LINE);
|
|
1309
367
|
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
368
|
+
pCurrentProfile = prof_get_profile(self);
|
|
369
|
+
#endif
|
|
370
|
+
|
|
371
|
+
#if defined(TOGGLE_GC_STATS)
|
|
372
|
+
rb_gc_enable_stats();
|
|
373
|
+
#endif
|
|
1315
374
|
}
|
|
1316
375
|
|
|
1317
|
-
|
|
1318
|
-
|
|
376
|
+
void
|
|
377
|
+
prof_remove_hook()
|
|
1319
378
|
{
|
|
1320
|
-
|
|
379
|
+
#if defined(TOGGLE_GC_STATS)
|
|
380
|
+
rb_gc_disable_stats();
|
|
381
|
+
#endif
|
|
1321
382
|
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
383
|
+
#ifndef RUBY_VM
|
|
384
|
+
pCurrentProfile = NULL;
|
|
385
|
+
#endif
|
|
1325
386
|
|
|
1326
|
-
|
|
387
|
+
/* Now unregister from event */
|
|
388
|
+
rb_remove_event_hook(prof_event_hook);
|
|
1327
389
|
}
|
|
1328
390
|
|
|
1329
391
|
|
|
1330
|
-
static
|
|
1331
|
-
|
|
392
|
+
static int
|
|
393
|
+
collect_methods(st_data_t key, st_data_t value, st_data_t result)
|
|
1332
394
|
{
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
}
|
|
1339
|
-
return (prof_result_t *) DATA_PTR(obj);
|
|
1340
|
-
}
|
|
395
|
+
/* Called for each method stored in a thread's method table.
|
|
396
|
+
We want to store the method info information into an array.*/
|
|
397
|
+
VALUE methods = (VALUE) result;
|
|
398
|
+
prof_method_t *method = (prof_method_t *) value;
|
|
399
|
+
rb_ary_push(methods, prof_method_wrap(method));
|
|
1341
400
|
|
|
1342
|
-
/* call
|
|
1343
|
-
|
|
401
|
+
/* Wrap call info objects */
|
|
402
|
+
prof_call_infos_wrap(method->call_infos);
|
|
1344
403
|
|
|
1345
|
-
|
|
1346
|
-
the hash table stores another hash table that contains profiling
|
|
1347
|
-
information for each method called during the threads execution.
|
|
1348
|
-
That hash table is keyed on method name and contains
|
|
1349
|
-
RubyProf::MethodInfo objects. */
|
|
1350
|
-
static VALUE
|
|
1351
|
-
prof_result_threads(VALUE self)
|
|
1352
|
-
{
|
|
1353
|
-
prof_result_t *prof_result = get_prof_result(self);
|
|
1354
|
-
return prof_result->threads;
|
|
404
|
+
return ST_CONTINUE;
|
|
1355
405
|
}
|
|
1356
406
|
|
|
407
|
+
static int
|
|
408
|
+
collect_threads(st_data_t key, st_data_t value, st_data_t result)
|
|
409
|
+
{
|
|
410
|
+
/* Although threads are keyed on an id, that is actually a
|
|
411
|
+
pointer to the VALUE object of the thread. So its bogus.
|
|
412
|
+
However, in thread_data is the real thread id stored
|
|
413
|
+
as an int. */
|
|
414
|
+
thread_data_t* thread_data = (thread_data_t*) value;
|
|
415
|
+
VALUE threads_hash = (VALUE) result;
|
|
1357
416
|
|
|
417
|
+
VALUE methods = rb_ary_new();
|
|
1358
418
|
|
|
1359
|
-
/*
|
|
1360
|
-
|
|
419
|
+
/* Now collect an array of all the called methods */
|
|
420
|
+
st_table* method_table = thread_data->method_table;
|
|
421
|
+
st_foreach(method_table, collect_methods, methods);
|
|
1361
422
|
|
|
1362
|
-
|
|
423
|
+
/* Store the results in the threads hash keyed on the thread id. */
|
|
424
|
+
rb_hash_aset(threads_hash, thread_data->thread_id, methods);
|
|
1363
425
|
|
|
1364
|
-
|
|
1365
|
-
*RubyProf::WALL_TIME - Measure wall time using gettimeofday on Linx and GetLocalTime on Windows
|
|
1366
|
-
*RubyProf::CPU_TIME - Measure time using the CPU clock counter. This mode is only supported on Pentium or PowerPC platforms.
|
|
1367
|
-
*RubyProf::ALLOCATIONS - Measure object allocations. This requires a patched Ruby interpreter.
|
|
1368
|
-
*RubyProf::MEMORY - Measure memory size. This requires a patched Ruby interpreter.
|
|
1369
|
-
*RubyProf::GC_RUNS - Measure number of garbage collections. This requires a patched Ruby interpreter.
|
|
1370
|
-
*RubyProf::GC_TIME - Measure time spent doing garbage collection. This requires a patched Ruby interpreter.*/
|
|
1371
|
-
static VALUE
|
|
1372
|
-
prof_get_measure_mode(VALUE self)
|
|
1373
|
-
{
|
|
1374
|
-
return INT2NUM(measure_mode);
|
|
426
|
+
return ST_CONTINUE;
|
|
1375
427
|
}
|
|
1376
428
|
|
|
1377
|
-
/*
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
Specifies what ruby-prof should measure. Valid values include:
|
|
1381
|
-
|
|
1382
|
-
*RubyProf::PROCESS_TIME - Measure process time. This is default. It is implemented using the clock functions in the C Runtime library.
|
|
1383
|
-
*RubyProf::WALL_TIME - Measure wall time using gettimeofday on Linx and GetLocalTime on Windows
|
|
1384
|
-
*RubyProf::CPU_TIME - Measure time using the CPU clock counter. This mode is only supported on Pentium or PowerPC platforms.
|
|
1385
|
-
*RubyProf::ALLOCATIONS - Measure object allocations. This requires a patched Ruby interpreter.
|
|
1386
|
-
*RubyProf::MEMORY - Measure memory size. This requires a patched Ruby interpreter.
|
|
1387
|
-
*RubyProf::GC_RUNS - Measure number of garbage collections. This requires a patched Ruby interpreter.
|
|
1388
|
-
*RubyProf::GC_TIME - Measure time spent doing garbage collection. This requires a patched Ruby interpreter.*/
|
|
1389
|
-
static VALUE
|
|
1390
|
-
prof_set_measure_mode(VALUE self, VALUE val)
|
|
429
|
+
/* ======== Profile Class ====== */
|
|
430
|
+
static void
|
|
431
|
+
prof_mark(prof_profile_t *profile)
|
|
1391
432
|
{
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
{
|
|
1396
|
-
rb_raise(rb_eRuntimeError, "can't set measure_mode while profiling");
|
|
1397
|
-
}
|
|
1398
|
-
|
|
1399
|
-
switch (mode) {
|
|
1400
|
-
case MEASURE_PROCESS_TIME:
|
|
1401
|
-
get_measurement = measure_process_time;
|
|
1402
|
-
convert_measurement = convert_process_time;
|
|
1403
|
-
break;
|
|
1404
|
-
|
|
1405
|
-
case MEASURE_WALL_TIME:
|
|
1406
|
-
get_measurement = measure_wall_time;
|
|
1407
|
-
convert_measurement = convert_wall_time;
|
|
1408
|
-
break;
|
|
1409
|
-
|
|
1410
|
-
#if defined(MEASURE_CPU_TIME)
|
|
1411
|
-
case MEASURE_CPU_TIME:
|
|
1412
|
-
if (cpu_frequency == 0)
|
|
1413
|
-
cpu_frequency = get_cpu_frequency();
|
|
1414
|
-
get_measurement = measure_cpu_time;
|
|
1415
|
-
convert_measurement = convert_cpu_time;
|
|
1416
|
-
break;
|
|
1417
|
-
#endif
|
|
1418
|
-
|
|
1419
|
-
#if defined(MEASURE_ALLOCATIONS)
|
|
1420
|
-
case MEASURE_ALLOCATIONS:
|
|
1421
|
-
get_measurement = measure_allocations;
|
|
1422
|
-
convert_measurement = convert_allocations;
|
|
1423
|
-
break;
|
|
1424
|
-
#endif
|
|
1425
|
-
|
|
1426
|
-
#if defined(MEASURE_MEMORY)
|
|
1427
|
-
case MEASURE_MEMORY:
|
|
1428
|
-
get_measurement = measure_memory;
|
|
1429
|
-
convert_measurement = convert_memory;
|
|
1430
|
-
break;
|
|
1431
|
-
#endif
|
|
1432
|
-
|
|
1433
|
-
#if defined(MEASURE_GC_RUNS)
|
|
1434
|
-
case MEASURE_GC_RUNS:
|
|
1435
|
-
get_measurement = measure_gc_runs;
|
|
1436
|
-
convert_measurement = convert_gc_runs;
|
|
1437
|
-
break;
|
|
1438
|
-
#endif
|
|
1439
|
-
|
|
1440
|
-
#if defined(MEASURE_GC_TIME)
|
|
1441
|
-
case MEASURE_GC_TIME:
|
|
1442
|
-
get_measurement = measure_gc_time;
|
|
1443
|
-
convert_measurement = convert_gc_time;
|
|
1444
|
-
break;
|
|
1445
|
-
#endif
|
|
433
|
+
VALUE threads = profile->threads;
|
|
434
|
+
rb_gc_mark(threads);
|
|
435
|
+
}
|
|
1446
436
|
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
437
|
+
static void
|
|
438
|
+
prof_free(prof_profile_t *profile)
|
|
439
|
+
{
|
|
440
|
+
profile->threads = Qnil;
|
|
441
|
+
st_free_table(profile->exclude_threads_tbl);
|
|
442
|
+
profile->exclude_threads_tbl = NULL;
|
|
1451
443
|
|
|
1452
|
-
|
|
1453
|
-
return val;
|
|
444
|
+
xfree(profile);
|
|
1454
445
|
}
|
|
1455
446
|
|
|
1456
|
-
/* call-seq:
|
|
1457
|
-
exclude_threads= -> void
|
|
1458
|
-
|
|
1459
|
-
Specifies what threads ruby-prof should exclude from profiling */
|
|
1460
447
|
static VALUE
|
|
1461
|
-
|
|
448
|
+
prof_allocate(VALUE klass)
|
|
1462
449
|
{
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
/* Stay simple, first free the old hash table */
|
|
1471
|
-
if (exclude_threads_tbl)
|
|
1472
|
-
{
|
|
1473
|
-
st_free_table(exclude_threads_tbl);
|
|
1474
|
-
exclude_threads_tbl = NULL;
|
|
1475
|
-
}
|
|
450
|
+
VALUE result;
|
|
451
|
+
prof_profile_t* profile;
|
|
452
|
+
result = Data_Make_Struct(klass, prof_profile_t, prof_mark, prof_free, profile);
|
|
453
|
+
profile->exclude_threads_tbl = threads_table_create();
|
|
454
|
+
profile->running = Qfalse;
|
|
455
|
+
return result;
|
|
456
|
+
}
|
|
1476
457
|
|
|
1477
|
-
|
|
1478
|
-
|
|
458
|
+
/* call-seq:
|
|
459
|
+
RubyProf::Profile.new(mode, exclude_threads) -> instance
|
|
460
|
+
|
|
461
|
+
Returns a new profiler.
|
|
462
|
+
|
|
463
|
+
== Parameters
|
|
464
|
+
mode:: Measure mode (optional). Specifies the profile measure mode. If not specified, defaults
|
|
465
|
+
to RubyProf::WALL_TIME.
|
|
466
|
+
exclude_threads:: Threads to exclude from the profiling results (optional). */
|
|
467
|
+
static VALUE
|
|
468
|
+
prof_initialize(int argc, VALUE *argv, VALUE self)
|
|
469
|
+
{
|
|
470
|
+
prof_profile_t* profile = prof_get_profile(self);
|
|
471
|
+
VALUE mode;
|
|
472
|
+
prof_measure_mode_t measurer;
|
|
473
|
+
VALUE exclude_threads;
|
|
474
|
+
int i;
|
|
475
|
+
|
|
476
|
+
switch (rb_scan_args(argc, argv, "02", &mode, &exclude_threads))
|
|
1479
477
|
{
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
478
|
+
case 0:
|
|
479
|
+
{
|
|
480
|
+
measurer = MEASURE_WALL_TIME;
|
|
481
|
+
exclude_threads = rb_ary_new();
|
|
482
|
+
break;
|
|
483
|
+
}
|
|
484
|
+
case 1:
|
|
1484
485
|
{
|
|
1485
|
-
|
|
1486
|
-
|
|
486
|
+
measurer = (prof_measure_mode_t)NUM2INT(mode);
|
|
487
|
+
exclude_threads = rb_ary_new();
|
|
488
|
+
break;
|
|
489
|
+
}
|
|
490
|
+
case 2:
|
|
491
|
+
{
|
|
492
|
+
Check_Type(exclude_threads, T_ARRAY);
|
|
493
|
+
measurer = (prof_measure_mode_t)NUM2INT(mode);
|
|
494
|
+
break;
|
|
1487
495
|
}
|
|
1488
496
|
}
|
|
1489
|
-
return threads;
|
|
1490
|
-
}
|
|
1491
|
-
|
|
1492
497
|
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
prof_install_hook()
|
|
1496
|
-
{
|
|
1497
|
-
#ifdef RUBY_VM
|
|
1498
|
-
rb_add_event_hook(prof_event_hook,
|
|
1499
|
-
RUBY_EVENT_CALL | RUBY_EVENT_RETURN |
|
|
1500
|
-
RUBY_EVENT_C_CALL | RUBY_EVENT_C_RETURN
|
|
1501
|
-
| RUBY_EVENT_LINE, Qnil); // RUBY_EVENT_SWITCH
|
|
1502
|
-
#else
|
|
1503
|
-
rb_add_event_hook(prof_event_hook,
|
|
1504
|
-
RUBY_EVENT_CALL | RUBY_EVENT_RETURN |
|
|
1505
|
-
RUBY_EVENT_C_CALL | RUBY_EVENT_C_RETURN
|
|
1506
|
-
| RUBY_EVENT_LINE);
|
|
1507
|
-
#endif
|
|
498
|
+
profile->measurer = prof_get_measurer(measurer);
|
|
499
|
+
profile->threads = rb_hash_new();
|
|
1508
500
|
|
|
1509
|
-
#if defined(TOGGLE_GC_STATS)
|
|
1510
|
-
rb_gc_enable_stats();
|
|
1511
|
-
#endif
|
|
1512
|
-
}
|
|
1513
501
|
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
502
|
+
for (i = 0; i < RARRAY_LEN(exclude_threads); i++)
|
|
503
|
+
{
|
|
504
|
+
VALUE thread = rb_ary_entry(exclude_threads, i);
|
|
505
|
+
VALUE thread_id = rb_obj_id(thread);
|
|
506
|
+
st_insert(profile->exclude_threads_tbl, thread_id, Qtrue);
|
|
507
|
+
}
|
|
1520
508
|
|
|
1521
|
-
|
|
1522
|
-
rb_remove_event_hook(prof_event_hook);
|
|
509
|
+
return self;
|
|
1523
510
|
}
|
|
1524
511
|
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
512
|
/* call-seq:
|
|
1528
513
|
running? -> boolean
|
|
1529
514
|
|
|
@@ -1531,10 +516,8 @@ prof_remove_hook()
|
|
|
1531
516
|
static VALUE
|
|
1532
517
|
prof_running(VALUE self)
|
|
1533
518
|
{
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
else
|
|
1537
|
-
return Qfalse;
|
|
519
|
+
prof_profile_t* profile = prof_get_profile(self);
|
|
520
|
+
return profile->running;
|
|
1538
521
|
}
|
|
1539
522
|
|
|
1540
523
|
/* call-seq:
|
|
@@ -1544,17 +527,20 @@ prof_running(VALUE self)
|
|
|
1544
527
|
static VALUE
|
|
1545
528
|
prof_start(VALUE self)
|
|
1546
529
|
{
|
|
1547
|
-
|
|
530
|
+
char* trace_file_name;
|
|
531
|
+
prof_profile_t* profile = prof_get_profile(self);
|
|
532
|
+
|
|
533
|
+
if (profile->running == Qtrue)
|
|
1548
534
|
{
|
|
1549
535
|
rb_raise(rb_eRuntimeError, "RubyProf.start was already called");
|
|
1550
536
|
}
|
|
1551
537
|
|
|
1552
|
-
|
|
1553
|
-
last_thread_data = NULL;
|
|
1554
|
-
threads_tbl = threads_table_create();
|
|
538
|
+
profile->running = Qtrue;
|
|
539
|
+
profile->last_thread_data = NULL;
|
|
540
|
+
profile->threads_tbl = threads_table_create();
|
|
1555
541
|
|
|
1556
542
|
/* open trace file if environment wants it */
|
|
1557
|
-
|
|
543
|
+
trace_file_name = getenv("RUBY_PROF_TRACE");
|
|
1558
544
|
if (trace_file_name != NULL) {
|
|
1559
545
|
if (0==strcmp(trace_file_name, "stdout")) {
|
|
1560
546
|
trace_file = stdout;
|
|
@@ -1565,7 +551,7 @@ prof_start(VALUE self)
|
|
|
1565
551
|
}
|
|
1566
552
|
}
|
|
1567
553
|
|
|
1568
|
-
prof_install_hook();
|
|
554
|
+
prof_install_hook(self);
|
|
1569
555
|
return self;
|
|
1570
556
|
}
|
|
1571
557
|
|
|
@@ -1576,11 +562,14 @@ prof_start(VALUE self)
|
|
|
1576
562
|
static VALUE
|
|
1577
563
|
prof_pause(VALUE self)
|
|
1578
564
|
{
|
|
1579
|
-
|
|
565
|
+
prof_profile_t* profile = prof_get_profile(self);
|
|
566
|
+
if (profile->running == Qfalse)
|
|
1580
567
|
{
|
|
1581
568
|
rb_raise(rb_eRuntimeError, "RubyProf is not running.");
|
|
1582
569
|
}
|
|
1583
570
|
|
|
571
|
+
profile->running = Qfalse;
|
|
572
|
+
|
|
1584
573
|
prof_remove_hook();
|
|
1585
574
|
return self;
|
|
1586
575
|
}
|
|
@@ -1592,13 +581,15 @@ prof_pause(VALUE self)
|
|
|
1592
581
|
static VALUE
|
|
1593
582
|
prof_resume(VALUE self)
|
|
1594
583
|
{
|
|
1595
|
-
|
|
584
|
+
prof_profile_t* profile = prof_get_profile(self);
|
|
585
|
+
if (profile->running == Qfalse)
|
|
1596
586
|
{
|
|
1597
587
|
prof_start(self);
|
|
1598
588
|
}
|
|
1599
589
|
else
|
|
1600
590
|
{
|
|
1601
|
-
|
|
591
|
+
profile->running = Qtrue;
|
|
592
|
+
prof_install_hook(self);
|
|
1602
593
|
}
|
|
1603
594
|
|
|
1604
595
|
if (rb_block_given_p())
|
|
@@ -1610,23 +601,23 @@ prof_resume(VALUE self)
|
|
|
1610
601
|
}
|
|
1611
602
|
|
|
1612
603
|
/* call-seq:
|
|
1613
|
-
stop ->
|
|
604
|
+
stop -> self
|
|
1614
605
|
|
|
1615
|
-
Stops collecting profile data
|
|
606
|
+
Stops collecting profile data.*/
|
|
1616
607
|
static VALUE
|
|
1617
608
|
prof_stop(VALUE self)
|
|
1618
609
|
{
|
|
1619
|
-
|
|
610
|
+
prof_profile_t* profile = prof_get_profile(self);
|
|
611
|
+
|
|
612
|
+
/* get 'now' before prof emove hook because it calls GC.disable_stats
|
|
1620
613
|
which makes the call within prof_pop_threads of now return 0, which is wrong
|
|
1621
614
|
*/
|
|
1622
|
-
|
|
1623
|
-
if (
|
|
615
|
+
profile->measurement = profile->measurer->measure();
|
|
616
|
+
if (profile->running == Qfalse)
|
|
1624
617
|
{
|
|
1625
618
|
rb_raise(rb_eRuntimeError, "RubyProf.start was not yet called");
|
|
1626
619
|
}
|
|
1627
620
|
|
|
1628
|
-
VALUE result = Qnil;
|
|
1629
|
-
|
|
1630
621
|
/* close trace file if open */
|
|
1631
622
|
if (trace_file != NULL) {
|
|
1632
623
|
if (trace_file!=stderr && trace_file!=stdout)
|
|
@@ -1635,22 +626,22 @@ prof_stop(VALUE self)
|
|
|
1635
626
|
}
|
|
1636
627
|
|
|
1637
628
|
prof_remove_hook();
|
|
1638
|
-
|
|
1639
|
-
prof_pop_threads(now);
|
|
1640
|
-
|
|
1641
|
-
/* Create the result */
|
|
1642
|
-
result = prof_result_new();
|
|
629
|
+
prof_pop_threads(profile);
|
|
1643
630
|
|
|
1644
631
|
/* Unset the last_thread_data (very important!)
|
|
1645
632
|
and the threads table */
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
633
|
+
profile->running = Qfalse;
|
|
634
|
+
profile->last_thread_data = NULL;
|
|
635
|
+
|
|
636
|
+
/* Save the result */
|
|
637
|
+
st_foreach(profile->threads_tbl, collect_threads, profile->threads);
|
|
638
|
+
threads_table_free(profile->threads_tbl);
|
|
639
|
+
profile->threads_tbl = NULL;
|
|
1649
640
|
|
|
1650
641
|
/* compute minimality of call_infos */
|
|
1651
|
-
rb_funcall(
|
|
642
|
+
rb_funcall(self, rb_intern("compute_minimality") , 0);
|
|
1652
643
|
|
|
1653
|
-
return
|
|
644
|
+
return self;
|
|
1654
645
|
}
|
|
1655
646
|
|
|
1656
647
|
/* call-seq:
|
|
@@ -1658,184 +649,54 @@ prof_stop(VALUE self)
|
|
|
1658
649
|
|
|
1659
650
|
Profiles the specified block and returns a RubyProf::Result object. */
|
|
1660
651
|
static VALUE
|
|
1661
|
-
prof_profile(VALUE
|
|
652
|
+
prof_profile(int argc, VALUE *argv, VALUE klass)
|
|
1662
653
|
{
|
|
1663
654
|
int result;
|
|
655
|
+
VALUE profile = rb_class_new_instance(argc, argv, cProfile);
|
|
1664
656
|
|
|
1665
657
|
if (!rb_block_given_p())
|
|
1666
658
|
{
|
|
1667
659
|
rb_raise(rb_eArgError, "A block must be provided to the profile method.");
|
|
1668
660
|
}
|
|
1669
661
|
|
|
1670
|
-
prof_start(
|
|
1671
|
-
rb_protect(rb_yield,
|
|
1672
|
-
return prof_stop(
|
|
662
|
+
prof_start(profile);
|
|
663
|
+
rb_protect(rb_yield, profile, &result);
|
|
664
|
+
return prof_stop(profile);
|
|
1673
665
|
}
|
|
1674
666
|
|
|
1675
|
-
/*
|
|
1676
|
-
|
|
1677
|
-
/* Document-method: measure_process_time
|
|
1678
|
-
call-seq:
|
|
1679
|
-
measure_process_time -> float
|
|
1680
|
-
|
|
1681
|
-
Returns the process time.*/
|
|
1682
|
-
|
|
1683
|
-
/* Document-method: measure_wall_time
|
|
1684
|
-
call-seq:
|
|
1685
|
-
measure_wall_time -> float
|
|
1686
|
-
|
|
1687
|
-
Returns the wall time.*/
|
|
1688
|
-
|
|
1689
|
-
/* Document-method: measure_cpu_time
|
|
1690
|
-
call-seq:
|
|
1691
|
-
measure_cpu_time -> float
|
|
1692
|
-
|
|
1693
|
-
Returns the cpu time.*/
|
|
1694
|
-
|
|
1695
|
-
/* Document-method: get_cpu_frequency
|
|
1696
|
-
call-seq:
|
|
1697
|
-
cpu_frequency -> int
|
|
1698
|
-
|
|
1699
|
-
Returns the cpu's frequency. This value is needed when
|
|
1700
|
-
RubyProf::measure_mode is set to CPU_TIME. */
|
|
1701
|
-
|
|
1702
|
-
/* Document-method: cpu_frequency
|
|
1703
|
-
call-seq:
|
|
1704
|
-
cpu_frequency -> int
|
|
1705
|
-
|
|
1706
|
-
Returns the cpu's frequency. This value is needed when
|
|
1707
|
-
RubyProf::measure_mode is set to CPU_TIME. */
|
|
1708
|
-
|
|
1709
|
-
/* Document-method: cpu_frequency=
|
|
1710
|
-
call-seq:
|
|
1711
|
-
cpu_frequency = frequency
|
|
1712
|
-
|
|
1713
|
-
Sets the cpu's frequency. This value is needed when
|
|
1714
|
-
RubyProf::measure_mode is set to CPU_TIME. */
|
|
1715
|
-
|
|
1716
|
-
/* Document-method: measure_allocations
|
|
1717
|
-
call-seq:
|
|
1718
|
-
measure_allocations -> int
|
|
1719
|
-
|
|
1720
|
-
Returns the total number of object allocations since Ruby started.*/
|
|
1721
|
-
|
|
1722
|
-
/* Document-method: measure_memory
|
|
1723
|
-
call-seq:
|
|
1724
|
-
measure_memory -> int
|
|
1725
|
-
|
|
1726
|
-
Returns total allocated memory in bytes.*/
|
|
1727
|
-
|
|
1728
|
-
/* Document-method: measure_gc_runs
|
|
1729
|
-
call-seq:
|
|
1730
|
-
gc_runs -> Integer
|
|
1731
|
-
|
|
1732
|
-
Returns the total number of garbage collections.*/
|
|
1733
|
-
|
|
1734
|
-
/* Document-method: measure_gc_time
|
|
1735
|
-
call-seq:
|
|
1736
|
-
gc_time -> Integer
|
|
1737
|
-
|
|
1738
|
-
Returns the time spent doing garbage collections in microseconds.*/
|
|
1739
|
-
|
|
667
|
+
/* call-seq:
|
|
668
|
+
threads -> Hash
|
|
1740
669
|
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
670
|
+
Returns a hash table keyed on thread ID. For each thread id,
|
|
671
|
+
the hash table stores another hash table that contains profiling
|
|
672
|
+
information for each method called during the threads execution.
|
|
673
|
+
That hash table is keyed on method name and contains
|
|
674
|
+
RubyProf::MethodInfo objects. */
|
|
675
|
+
static VALUE
|
|
676
|
+
prof_threads(VALUE self)
|
|
677
|
+
{
|
|
678
|
+
prof_profile_t* profile = prof_get_profile(self);
|
|
679
|
+
return profile->threads;
|
|
680
|
+
}
|
|
1747
681
|
|
|
1748
|
-
|
|
682
|
+
void Init_ruby_prof()
|
|
1749
683
|
{
|
|
1750
684
|
mProf = rb_define_module("RubyProf");
|
|
1751
685
|
rb_define_const(mProf, "VERSION", rb_str_new2(RUBY_PROF_VERSION));
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
#ifndef MEASURE_CPU_TIME
|
|
1770
|
-
rb_define_const(mProf, "CPU_TIME", Qnil);
|
|
1771
|
-
#else
|
|
1772
|
-
rb_define_const(mProf, "CPU_TIME", INT2NUM(MEASURE_CPU_TIME));
|
|
1773
|
-
rb_define_singleton_method(mProf, "measure_cpu_time", prof_measure_cpu_time, 0); /* in measure_cpu_time.h */
|
|
1774
|
-
rb_define_singleton_method(mProf, "cpu_frequency", prof_get_cpu_frequency, 0); /* in measure_cpu_time.h */
|
|
1775
|
-
rb_define_singleton_method(mProf, "cpu_frequency=", prof_set_cpu_frequency, 1); /* in measure_cpu_time.h */
|
|
1776
|
-
#endif
|
|
1777
|
-
|
|
1778
|
-
#ifndef MEASURE_ALLOCATIONS
|
|
1779
|
-
rb_define_const(mProf, "ALLOCATIONS", Qnil);
|
|
1780
|
-
#else
|
|
1781
|
-
rb_define_const(mProf, "ALLOCATIONS", INT2NUM(MEASURE_ALLOCATIONS));
|
|
1782
|
-
rb_define_singleton_method(mProf, "measure_allocations", prof_measure_allocations, 0); /* in measure_allocations.h */
|
|
1783
|
-
#endif
|
|
1784
|
-
|
|
1785
|
-
#ifndef MEASURE_MEMORY
|
|
1786
|
-
rb_define_const(mProf, "MEMORY", Qnil);
|
|
1787
|
-
#else
|
|
1788
|
-
rb_define_const(mProf, "MEMORY", INT2NUM(MEASURE_MEMORY));
|
|
1789
|
-
rb_define_singleton_method(mProf, "measure_memory", prof_measure_memory, 0); /* in measure_memory.h */
|
|
1790
|
-
#endif
|
|
1791
|
-
|
|
1792
|
-
#ifndef MEASURE_GC_RUNS
|
|
1793
|
-
rb_define_const(mProf, "GC_RUNS", Qnil);
|
|
1794
|
-
#else
|
|
1795
|
-
rb_define_const(mProf, "GC_RUNS", INT2NUM(MEASURE_GC_RUNS));
|
|
1796
|
-
rb_define_singleton_method(mProf, "measure_gc_runs", prof_measure_gc_runs, 0); /* in measure_gc_runs.h */
|
|
1797
|
-
#endif
|
|
1798
|
-
|
|
1799
|
-
#ifndef MEASURE_GC_TIME
|
|
1800
|
-
rb_define_const(mProf, "GC_TIME", Qnil);
|
|
1801
|
-
#else
|
|
1802
|
-
rb_define_const(mProf, "GC_TIME", INT2NUM(MEASURE_GC_TIME));
|
|
1803
|
-
rb_define_singleton_method(mProf, "measure_gc_time", prof_measure_gc_time, 0); /* in measure_gc_time.h */
|
|
1804
|
-
#endif
|
|
1805
|
-
|
|
1806
|
-
cResult = rb_define_class_under(mProf, "Result", rb_cObject);
|
|
1807
|
-
rb_undef_method(CLASS_OF(cMethodInfo), "new");
|
|
1808
|
-
rb_define_method(cResult, "threads", prof_result_threads, 0);
|
|
1809
|
-
|
|
1810
|
-
/* MethodInfo */
|
|
1811
|
-
cMethodInfo = rb_define_class_under(mProf, "MethodInfo", rb_cObject);
|
|
1812
|
-
rb_undef_method(CLASS_OF(cMethodInfo), "new");
|
|
1813
|
-
|
|
1814
|
-
rb_define_method(cMethodInfo, "klass", prof_method_klass, 0);
|
|
1815
|
-
rb_define_method(cMethodInfo, "klass_name", prof_klass_name, 0);
|
|
1816
|
-
rb_define_method(cMethodInfo, "method_name", prof_method_name, 0);
|
|
1817
|
-
rb_define_method(cMethodInfo, "full_name", prof_full_name, 0);
|
|
1818
|
-
rb_define_method(cMethodInfo, "method_id", prof_method_id, 0);
|
|
1819
|
-
|
|
1820
|
-
rb_define_method(cMethodInfo, "source_file", prof_method_source_file,0);
|
|
1821
|
-
rb_define_method(cMethodInfo, "line", prof_method_line, 0);
|
|
1822
|
-
|
|
1823
|
-
rb_define_method(cMethodInfo, "call_infos", prof_method_call_infos, 0);
|
|
1824
|
-
|
|
1825
|
-
/* CallInfo */
|
|
1826
|
-
cCallInfo = rb_define_class_under(mProf, "CallInfo", rb_cObject);
|
|
1827
|
-
rb_undef_method(CLASS_OF(cCallInfo), "new");
|
|
1828
|
-
rb_define_method(cCallInfo, "parent", prof_call_info_parent, 0);
|
|
1829
|
-
rb_define_method(cCallInfo, "parent=", prof_call_info_set_parent, 1);
|
|
1830
|
-
rb_define_method(cCallInfo, "children", prof_call_info_children, 0);
|
|
1831
|
-
rb_define_method(cCallInfo, "target", prof_call_info_target, 0);
|
|
1832
|
-
rb_define_method(cCallInfo, "called", prof_call_info_called, 0);
|
|
1833
|
-
rb_define_method(cCallInfo, "called=", prof_call_info_set_called, 1);
|
|
1834
|
-
rb_define_method(cCallInfo, "total_time", prof_call_info_total_time, 0);
|
|
1835
|
-
rb_define_method(cCallInfo, "add_total_time", prof_call_info_add_total_time, 1);
|
|
1836
|
-
rb_define_method(cCallInfo, "self_time", prof_call_info_self_time, 0);
|
|
1837
|
-
rb_define_method(cCallInfo, "add_self_time", prof_call_info_add_self_time, 1);
|
|
1838
|
-
rb_define_method(cCallInfo, "wait_time", prof_call_info_wait_time, 0);
|
|
1839
|
-
rb_define_method(cCallInfo, "add_wait_time", prof_call_info_add_wait_time, 1);
|
|
1840
|
-
rb_define_method(cCallInfo, "line", prof_call_info_line, 0);
|
|
686
|
+
|
|
687
|
+
rp_init_measure();
|
|
688
|
+
rp_init_method_info();
|
|
689
|
+
rp_init_call_info();
|
|
690
|
+
|
|
691
|
+
cProfile = rb_define_class_under(mProf, "Profile", rb_cObject);
|
|
692
|
+
rb_define_singleton_method(cProfile, "profile", prof_profile, -1);
|
|
693
|
+
rb_define_alloc_func (cProfile, prof_allocate);
|
|
694
|
+
rb_define_method(cProfile, "initialize", prof_initialize, -1);
|
|
695
|
+
rb_define_method(cProfile, "start", prof_start, 0);
|
|
696
|
+
rb_define_method(cProfile, "start", prof_start, 0);
|
|
697
|
+
rb_define_method(cProfile, "stop", prof_stop, 0);
|
|
698
|
+
rb_define_method(cProfile, "resume", prof_resume, 0);
|
|
699
|
+
rb_define_method(cProfile, "pause", prof_pause, 0);
|
|
700
|
+
rb_define_method(cProfile, "running?", prof_running, 0);
|
|
701
|
+
rb_define_method(cProfile, "threads", prof_threads, 0);
|
|
1841
702
|
}
|