ruby-prof 0.8.1-x86-mingw32
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 +221 -0
- data/LICENSE +23 -0
- data/README +432 -0
- data/Rakefile +159 -0
- data/bin/ruby-prof +224 -0
- data/examples/flat.txt +55 -0
- data/examples/graph.html +823 -0
- data/examples/graph.txt +170 -0
- data/ext/ruby_prof/measure_allocations.h +58 -0
- data/ext/ruby_prof/measure_cpu_time.h +152 -0
- data/ext/ruby_prof/measure_gc_runs.h +76 -0
- data/ext/ruby_prof/measure_gc_time.h +57 -0
- data/ext/ruby_prof/measure_memory.h +101 -0
- data/ext/ruby_prof/measure_process_time.h +52 -0
- data/ext/ruby_prof/measure_wall_time.h +53 -0
- data/ext/ruby_prof/mingw/Rakefile +23 -0
- data/ext/ruby_prof/mingw/build.rake +38 -0
- data/ext/ruby_prof/ruby_prof.c +1747 -0
- data/ext/ruby_prof/ruby_prof.h +188 -0
- data/ext/ruby_prof/version.h +4 -0
- data/lib/1.8/ruby_prof.so +0 -0
- data/lib/1.9/ruby_prof.so +0 -0
- data/lib/ruby-prof.rb +56 -0
- data/lib/ruby-prof/abstract_printer.rb +41 -0
- data/lib/ruby-prof/aggregate_call_info.rb +62 -0
- data/lib/ruby-prof/call_info.rb +47 -0
- data/lib/ruby-prof/call_tree_printer.rb +84 -0
- data/lib/ruby-prof/flat_printer.rb +78 -0
- data/lib/ruby-prof/flat_printer_with_line_numbers.rb +72 -0
- data/lib/ruby-prof/graph_html_printer.rb +256 -0
- data/lib/ruby-prof/graph_printer.rb +157 -0
- data/lib/ruby-prof/method_info.rb +111 -0
- data/lib/ruby-prof/symbol_to_proc.rb +8 -0
- data/lib/ruby-prof/task.rb +146 -0
- data/lib/ruby-prof/test.rb +148 -0
- data/lib/unprof.rb +8 -0
- data/rails/environment/profile.rb +24 -0
- data/rails/example/example_test.rb +9 -0
- data/rails/profile_test_helper.rb +21 -0
- data/test/aggregate_test.rb +121 -0
- data/test/basic_test.rb +290 -0
- data/test/current_failures_windows +8 -0
- data/test/do_nothing.rb +0 -0
- data/test/duplicate_names_test.rb +32 -0
- data/test/enumerable_test.rb +16 -0
- data/test/exceptions_test.rb +15 -0
- data/test/exclude_threads_test.rb +54 -0
- data/test/exec_test.rb +14 -0
- data/test/line_number_test.rb +73 -0
- data/test/measurement_test.rb +121 -0
- data/test/module_test.rb +54 -0
- data/test/no_method_class_test.rb +13 -0
- data/test/prime.rb +58 -0
- data/test/prime_test.rb +13 -0
- data/test/printers_test.rb +130 -0
- data/test/recursive_test.rb +275 -0
- data/test/ruby-prof-bin +20 -0
- data/test/singleton_test.rb +37 -0
- data/test/stack_test.rb +138 -0
- data/test/start_stop_test.rb +95 -0
- data/test/test_suite.rb +23 -0
- data/test/thread_test.rb +173 -0
- data/test/unique_call_path_test.rb +225 -0
- metadata +143 -0
@@ -0,0 +1,57 @@
|
|
1
|
+
/* :nodoc:
|
2
|
+
* Copyright (C) 2008 Shugo Maeda <shugo@ruby-lang.org>
|
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
|
+
|
27
|
+
#if defined(HAVE_RB_GC_TIME)
|
28
|
+
#define MEASURE_GC_TIME 6
|
29
|
+
|
30
|
+
static prof_measure_t
|
31
|
+
measure_gc_time()
|
32
|
+
{
|
33
|
+
#if HAVE_LONG_LONG
|
34
|
+
return NUM2LL(rb_gc_time());
|
35
|
+
#else
|
36
|
+
return NUM2LONG(rb_gc_time());
|
37
|
+
#endif
|
38
|
+
}
|
39
|
+
|
40
|
+
static double
|
41
|
+
convert_gc_time(prof_measure_t c)
|
42
|
+
{
|
43
|
+
return (double) c / 1000000;
|
44
|
+
}
|
45
|
+
|
46
|
+
/* Document-method: prof_measure_gc_time
|
47
|
+
call-seq:
|
48
|
+
gc_time -> Integer
|
49
|
+
|
50
|
+
Returns the time spent doing garbage collections in microseconds.*/
|
51
|
+
static VALUE
|
52
|
+
prof_measure_gc_time(VALUE self)
|
53
|
+
{
|
54
|
+
return rb_gc_time();
|
55
|
+
}
|
56
|
+
|
57
|
+
#endif
|
@@ -0,0 +1,101 @@
|
|
1
|
+
/* :nodoc:
|
2
|
+
* Copyright (C) 2008 Alexander Dymo <adymo@pluron.com>
|
3
|
+
*
|
4
|
+
* All rights reserved.
|
5
|
+
*
|
6
|
+
* Redistribution and use in source and binary forms, with or without
|
7
|
+
* modification, are permitted provided that the following conditions
|
8
|
+
* are met:
|
9
|
+
* 1. Redistributions of source code must retain the above copyright
|
10
|
+
* notice, this list of conditions and the following disclaimer.
|
11
|
+
* 2. Redistributions in binary form must reproduce the above copyright
|
12
|
+
* notice, this list of conditions and the following disclaimer in the
|
13
|
+
* documentation and/or other materials provided with the distribution.
|
14
|
+
*
|
15
|
+
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
|
16
|
+
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
17
|
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
18
|
+
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
|
19
|
+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
20
|
+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
21
|
+
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
22
|
+
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
23
|
+
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
24
|
+
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
25
|
+
* SUCH DAMAGE. */
|
26
|
+
|
27
|
+
|
28
|
+
#if defined(HAVE_RB_GC_ALLOCATED_SIZE)
|
29
|
+
#define MEASURE_MEMORY 4
|
30
|
+
#define TOGGLE_GC_STATS 1
|
31
|
+
|
32
|
+
static prof_measure_t
|
33
|
+
measure_memory()
|
34
|
+
{
|
35
|
+
#if defined(HAVE_LONG_LONG)
|
36
|
+
return NUM2LL(rb_gc_allocated_size());
|
37
|
+
#else
|
38
|
+
return NUM2ULONG(rb_gc_allocated_size());
|
39
|
+
#endif
|
40
|
+
}
|
41
|
+
|
42
|
+
static double
|
43
|
+
convert_memory(prof_measure_t c)
|
44
|
+
{
|
45
|
+
return (double) c / 1024;
|
46
|
+
}
|
47
|
+
|
48
|
+
/* Document-method: prof_measure_memory
|
49
|
+
call-seq:
|
50
|
+
measure_memory -> int
|
51
|
+
|
52
|
+
Returns total allocated memory in bytes.*/
|
53
|
+
static VALUE
|
54
|
+
prof_measure_memory(VALUE self)
|
55
|
+
{
|
56
|
+
return rb_gc_allocated_size();
|
57
|
+
}
|
58
|
+
|
59
|
+
#elif defined(HAVE_RB_GC_MALLOC_ALLOCATED_SIZE)
|
60
|
+
#define MEASURE_MEMORY 4
|
61
|
+
|
62
|
+
static prof_measure_t
|
63
|
+
measure_memory()
|
64
|
+
{
|
65
|
+
return rb_gc_malloc_allocated_size();
|
66
|
+
}
|
67
|
+
|
68
|
+
static double
|
69
|
+
convert_memory(prof_measure_t c)
|
70
|
+
{
|
71
|
+
return (double) c / 1024;
|
72
|
+
}
|
73
|
+
|
74
|
+
static VALUE
|
75
|
+
prof_measure_memory(VALUE self)
|
76
|
+
{
|
77
|
+
return UINT2NUM(rb_gc_malloc_allocated_size());
|
78
|
+
}
|
79
|
+
|
80
|
+
#elif defined(HAVE_RB_HEAP_TOTAL_MEM)
|
81
|
+
#define MEASURE_MEMORY 4
|
82
|
+
|
83
|
+
static prof_measure_t
|
84
|
+
measure_memory()
|
85
|
+
{
|
86
|
+
return rb_heap_total_mem();
|
87
|
+
}
|
88
|
+
|
89
|
+
static double
|
90
|
+
convert_memory(prof_measure_t c)
|
91
|
+
{
|
92
|
+
return (double) c / 1024;
|
93
|
+
}
|
94
|
+
|
95
|
+
static VALUE
|
96
|
+
prof_measure_memory(VALUE self)
|
97
|
+
{
|
98
|
+
return ULONG2NUM(rb_heap_total_mem());
|
99
|
+
}
|
100
|
+
|
101
|
+
#endif
|
@@ -0,0 +1,52 @@
|
|
1
|
+
/*
|
2
|
+
* Copyright (C) 2008 Shugo Maeda <shugo@ruby-lang.org>
|
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
|
+
|
27
|
+
#include <time.h>
|
28
|
+
|
29
|
+
#define MEASURE_PROCESS_TIME 0
|
30
|
+
|
31
|
+
static prof_measure_t
|
32
|
+
measure_process_time()
|
33
|
+
{
|
34
|
+
return clock();
|
35
|
+
}
|
36
|
+
|
37
|
+
static double
|
38
|
+
convert_process_time(prof_measure_t c)
|
39
|
+
{
|
40
|
+
return (double) c / CLOCKS_PER_SEC;
|
41
|
+
}
|
42
|
+
|
43
|
+
/* Document-method: measure_process_time
|
44
|
+
call-seq:
|
45
|
+
measure_process_time -> float
|
46
|
+
|
47
|
+
Returns the process time.*/
|
48
|
+
static VALUE
|
49
|
+
prof_measure_process_time(VALUE self)
|
50
|
+
{
|
51
|
+
return rb_float_new(convert_process_time(measure_process_time()));
|
52
|
+
}
|
@@ -0,0 +1,53 @@
|
|
1
|
+
/* :nodoc:
|
2
|
+
* Copyright (C) 2008 Shugo Maeda <shugo@ruby-lang.org>
|
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
|
+
|
27
|
+
|
28
|
+
#define MEASURE_WALL_TIME 1
|
29
|
+
|
30
|
+
static prof_measure_t
|
31
|
+
measure_wall_time()
|
32
|
+
{
|
33
|
+
struct timeval tv;
|
34
|
+
gettimeofday(&tv, NULL);
|
35
|
+
return tv.tv_sec * 1000000 + tv.tv_usec;
|
36
|
+
}
|
37
|
+
|
38
|
+
static double
|
39
|
+
convert_wall_time(prof_measure_t c)
|
40
|
+
{
|
41
|
+
return (double) c / 1000000;
|
42
|
+
}
|
43
|
+
|
44
|
+
/* Document-method: prof_measure_wall_time
|
45
|
+
call-seq:
|
46
|
+
measure_wall_time -> float
|
47
|
+
|
48
|
+
Returns the wall time.*/
|
49
|
+
static VALUE
|
50
|
+
prof_measure_wall_time(VALUE self)
|
51
|
+
{
|
52
|
+
return rb_float_new(convert_wall_time(measure_wall_time()));
|
53
|
+
}
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# We can't use Ruby's standard build procedures
|
2
|
+
# on Windows because the Ruby executable is
|
3
|
+
# built with VC++ while here we want to build
|
4
|
+
# with MingW. So just roll our own...
|
5
|
+
|
6
|
+
require 'fileutils'
|
7
|
+
require 'rbconfig'
|
8
|
+
|
9
|
+
EXTENSION_NAME = "ruby_prof.#{Config::CONFIG["DLEXT"]}"
|
10
|
+
|
11
|
+
# This is called when the Windows GEM is installed!
|
12
|
+
task :install do
|
13
|
+
# Gems will pass these two environment variables:
|
14
|
+
# RUBYARCHDIR=#{dest_path}
|
15
|
+
# RUBYLIBDIR=#{dest_path}
|
16
|
+
|
17
|
+
dest_path = ENV['RUBYLIBDIR']
|
18
|
+
|
19
|
+
# Copy the extension
|
20
|
+
cp(EXTENSION_NAME, dest_path)
|
21
|
+
end
|
22
|
+
|
23
|
+
task :default => :install
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# We can't use Ruby's standard build procedures
|
2
|
+
# on Windows because the Ruby executable is
|
3
|
+
# built with VC++ while here we want to build
|
4
|
+
# with MingW. So just roll our own...
|
5
|
+
|
6
|
+
require 'rake/clean'
|
7
|
+
require 'rbconfig'
|
8
|
+
|
9
|
+
RUBY_INCLUDE_DIR = Config::CONFIG["archdir"]
|
10
|
+
RUBY_BIN_DIR = Config::CONFIG["bindir"]
|
11
|
+
RUBY_LIB_DIR = Config::CONFIG["libdir"]
|
12
|
+
RUBY_SHARED_LIB = Config::CONFIG["LIBRUBY"]
|
13
|
+
RUBY_SHARED_DLL = RUBY_SHARED_LIB.gsub(/lib$/, 'dll')
|
14
|
+
|
15
|
+
EXTENSION_NAME = "ruby_prof.#{Config::CONFIG["DLEXT"]}"
|
16
|
+
|
17
|
+
CLEAN.include('*.o')
|
18
|
+
CLOBBER.include(EXTENSION_NAME)
|
19
|
+
|
20
|
+
task :default => "ruby_prof"
|
21
|
+
|
22
|
+
SRC = FileList['../*.c']
|
23
|
+
OBJ = SRC.collect do |file_name|
|
24
|
+
File.basename(file_name).ext('o')
|
25
|
+
end
|
26
|
+
|
27
|
+
SRC.each do |srcfile|
|
28
|
+
objfile = File.basename(srcfile).ext('o')
|
29
|
+
file objfile => srcfile do
|
30
|
+
command = "gcc -c -fPIC -O2 -Wall -o #{objfile} -I/usr/local/include #{srcfile} -I#{RUBY_INCLUDE_DIR}"
|
31
|
+
sh "sh -c '#{command}'"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
file "ruby_prof" => OBJ do
|
36
|
+
command = "gcc -shared -o #{EXTENSION_NAME} -L/usr/local/lib #{OBJ} #{RUBY_BIN_DIR}/#{RUBY_SHARED_DLL}"
|
37
|
+
sh "sh -c '#{command}'"
|
38
|
+
end
|
@@ -0,0 +1,1747 @@
|
|
1
|
+
/*
|
2
|
+
* Copyright (C) 2008 Shugo Maeda <shugo@ruby-lang.org>
|
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
|
+
*/
|
27
|
+
|
28
|
+
/* ruby-prof tracks the time spent executing every method in ruby programming.
|
29
|
+
The main players are:
|
30
|
+
|
31
|
+
prof_result_t - Its one field, values, contains the overall results
|
32
|
+
thread_data_t - Stores data about a single thread.
|
33
|
+
prof_stack_t - The method call stack in a particular thread
|
34
|
+
prof_method_t - Profiling information for each method
|
35
|
+
prof_call_info_t - Keeps track a method's callers and callees.
|
36
|
+
|
37
|
+
The final resulut is a hash table of thread_data_t, keyed on the thread
|
38
|
+
id. Each thread has an hash a table of prof_method_t, keyed on the
|
39
|
+
method id. A hash table is used for quick look up when doing a profile.
|
40
|
+
However, it is exposed to Ruby as an array.
|
41
|
+
|
42
|
+
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 RCLASS_SUPER
|
87
|
+
VALUE super = rb_class_real(RCLASS_SUPER(klass));
|
88
|
+
#else
|
89
|
+
VALUE super = rb_class_real(RCLASS(klass)->super);
|
90
|
+
#endif
|
91
|
+
result = rb_str_new2("<Object::");
|
92
|
+
rb_str_append(result, rb_inspect(super));
|
93
|
+
rb_str_cat2(result, ">");
|
94
|
+
}
|
95
|
+
|
96
|
+
/* Ok, this could be other things like an array made put onto
|
97
|
+
a singleton object (yeah, it happens, see the singleton
|
98
|
+
objects test case). */
|
99
|
+
else
|
100
|
+
{
|
101
|
+
result = rb_inspect(klass);
|
102
|
+
}
|
103
|
+
|
104
|
+
return result;
|
105
|
+
}
|
106
|
+
|
107
|
+
static VALUE
|
108
|
+
klass_name(VALUE klass)
|
109
|
+
{
|
110
|
+
VALUE result = Qnil;
|
111
|
+
|
112
|
+
if (klass == 0 || klass == Qnil)
|
113
|
+
{
|
114
|
+
result = rb_str_new2("Global");
|
115
|
+
}
|
116
|
+
else if (BUILTIN_TYPE(klass) == T_MODULE)
|
117
|
+
{
|
118
|
+
result = rb_inspect(klass);
|
119
|
+
}
|
120
|
+
else if (BUILTIN_TYPE(klass) == T_CLASS && FL_TEST(klass, FL_SINGLETON))
|
121
|
+
{
|
122
|
+
result = figure_singleton_name(klass);
|
123
|
+
}
|
124
|
+
else if (BUILTIN_TYPE(klass) == T_CLASS)
|
125
|
+
{
|
126
|
+
result = rb_inspect(klass);
|
127
|
+
}
|
128
|
+
else
|
129
|
+
{
|
130
|
+
/* Should never happen. */
|
131
|
+
result = rb_str_new2("Unknown");
|
132
|
+
}
|
133
|
+
|
134
|
+
return result;
|
135
|
+
}
|
136
|
+
|
137
|
+
static VALUE
|
138
|
+
method_name(ID mid, int depth)
|
139
|
+
{
|
140
|
+
VALUE result;
|
141
|
+
|
142
|
+
if (mid == ID_ALLOCATOR)
|
143
|
+
result = rb_str_new2("allocate");
|
144
|
+
else if (mid == 0)
|
145
|
+
result = rb_str_new2("[No method]");
|
146
|
+
else
|
147
|
+
result = rb_String(ID2SYM(mid));
|
148
|
+
|
149
|
+
if (depth > 0)
|
150
|
+
{
|
151
|
+
char buffer[65];
|
152
|
+
sprintf(buffer, "(d%i)", depth);
|
153
|
+
rb_str_cat2(result, buffer);
|
154
|
+
}
|
155
|
+
|
156
|
+
return result;
|
157
|
+
}
|
158
|
+
|
159
|
+
static VALUE
|
160
|
+
full_name(VALUE klass, ID mid, int depth)
|
161
|
+
{
|
162
|
+
VALUE result = klass_name(klass);
|
163
|
+
rb_str_cat2(result, "#");
|
164
|
+
rb_str_append(result, method_name(mid, depth));
|
165
|
+
|
166
|
+
return result;
|
167
|
+
}
|
168
|
+
|
169
|
+
/* ================ Stack Handling =================*/
|
170
|
+
/* Creates a stack of prof_frame_t to keep track
|
171
|
+
of timings for active methods. */
|
172
|
+
static prof_stack_t *
|
173
|
+
stack_create()
|
174
|
+
{
|
175
|
+
prof_stack_t *stack = ALLOC(prof_stack_t);
|
176
|
+
stack->start = ALLOC_N(prof_frame_t, INITIAL_STACK_SIZE);
|
177
|
+
stack->ptr = stack->start;
|
178
|
+
stack->end = stack->start + INITIAL_STACK_SIZE;
|
179
|
+
return stack;
|
180
|
+
}
|
181
|
+
|
182
|
+
static void
|
183
|
+
stack_free(prof_stack_t *stack)
|
184
|
+
{
|
185
|
+
xfree(stack->start);
|
186
|
+
xfree(stack);
|
187
|
+
}
|
188
|
+
|
189
|
+
static prof_frame_t *
|
190
|
+
stack_push(prof_stack_t *stack)
|
191
|
+
{
|
192
|
+
/* Is there space on the stack? If not, double
|
193
|
+
its size. */
|
194
|
+
if (stack->ptr == stack->end)
|
195
|
+
{
|
196
|
+
size_t len = stack->ptr - stack->start;
|
197
|
+
size_t new_capacity = (stack->end - stack->start) * 2;
|
198
|
+
REALLOC_N(stack->start, prof_frame_t, new_capacity);
|
199
|
+
stack->ptr = stack->start + len;
|
200
|
+
stack->end = stack->start + new_capacity;
|
201
|
+
}
|
202
|
+
return stack->ptr++;
|
203
|
+
}
|
204
|
+
|
205
|
+
static prof_frame_t *
|
206
|
+
stack_pop(prof_stack_t *stack)
|
207
|
+
{
|
208
|
+
if (stack->ptr == stack->start)
|
209
|
+
return NULL;
|
210
|
+
else
|
211
|
+
return --stack->ptr;
|
212
|
+
}
|
213
|
+
|
214
|
+
static prof_frame_t *
|
215
|
+
stack_peek(prof_stack_t *stack)
|
216
|
+
{
|
217
|
+
if (stack->ptr == stack->start)
|
218
|
+
return NULL;
|
219
|
+
else
|
220
|
+
return stack->ptr - 1;
|
221
|
+
}
|
222
|
+
|
223
|
+
/* ================ Method Key =================*/
|
224
|
+
static int
|
225
|
+
method_table_cmp(prof_method_key_t *key1, prof_method_key_t *key2)
|
226
|
+
{
|
227
|
+
return (key1->klass != key2->klass) ||
|
228
|
+
(key1->mid != key2->mid) ||
|
229
|
+
(key1->depth != key2->depth);
|
230
|
+
}
|
231
|
+
|
232
|
+
static int
|
233
|
+
method_table_hash(prof_method_key_t *key)
|
234
|
+
{
|
235
|
+
return key->key;
|
236
|
+
}
|
237
|
+
|
238
|
+
static struct st_hash_type type_method_hash = {
|
239
|
+
method_table_cmp,
|
240
|
+
method_table_hash
|
241
|
+
};
|
242
|
+
|
243
|
+
static void
|
244
|
+
method_key(prof_method_key_t* key, VALUE klass, ID mid, int depth)
|
245
|
+
{
|
246
|
+
key->klass = klass;
|
247
|
+
key->mid = mid;
|
248
|
+
key->depth = depth;
|
249
|
+
key->key = (klass << 4) + (mid << 2) + depth;
|
250
|
+
}
|
251
|
+
|
252
|
+
|
253
|
+
/* ================ Call Info =================*/
|
254
|
+
static st_table *
|
255
|
+
call_info_table_create()
|
256
|
+
{
|
257
|
+
return st_init_table(&type_method_hash);
|
258
|
+
}
|
259
|
+
|
260
|
+
static size_t
|
261
|
+
call_info_table_insert(st_table *table, const prof_method_key_t *key, prof_call_info_t *val)
|
262
|
+
{
|
263
|
+
return st_insert(table, (st_data_t) key, (st_data_t) val);
|
264
|
+
}
|
265
|
+
|
266
|
+
static prof_call_info_t *
|
267
|
+
call_info_table_lookup(st_table *table, const prof_method_key_t *key)
|
268
|
+
{
|
269
|
+
st_data_t val;
|
270
|
+
if (st_lookup(table, (st_data_t) key, &val))
|
271
|
+
{
|
272
|
+
return (prof_call_info_t *) val;
|
273
|
+
}
|
274
|
+
else
|
275
|
+
{
|
276
|
+
return NULL;
|
277
|
+
}
|
278
|
+
}
|
279
|
+
|
280
|
+
static void
|
281
|
+
call_info_table_free(st_table *table)
|
282
|
+
{
|
283
|
+
st_free_table(table);
|
284
|
+
}
|
285
|
+
|
286
|
+
/* Document-class: RubyProf::CallInfo
|
287
|
+
RubyProf::CallInfo is a helper class used by RubyProf::MethodInfo
|
288
|
+
to keep track of which child methods were called and how long
|
289
|
+
they took to execute. */
|
290
|
+
|
291
|
+
/* :nodoc: */
|
292
|
+
static prof_call_info_t *
|
293
|
+
prof_call_info_create(prof_method_t* method, prof_call_info_t* parent)
|
294
|
+
{
|
295
|
+
prof_call_info_t *result = ALLOC(prof_call_info_t);
|
296
|
+
result->object = Qnil;
|
297
|
+
result->target = method;
|
298
|
+
result->parent = parent;
|
299
|
+
result->call_infos = call_info_table_create();
|
300
|
+
result->children = Qnil;
|
301
|
+
|
302
|
+
result->called = 0;
|
303
|
+
result->total_time = 0;
|
304
|
+
result->self_time = 0;
|
305
|
+
result->wait_time = 0;
|
306
|
+
result->line = 0;
|
307
|
+
return result;
|
308
|
+
}
|
309
|
+
|
310
|
+
static void prof_method_mark(prof_method_t *method);
|
311
|
+
|
312
|
+
static void
|
313
|
+
prof_call_info_mark(prof_call_info_t *call_info)
|
314
|
+
{
|
315
|
+
{
|
316
|
+
VALUE target = call_info->target->object;
|
317
|
+
if (NIL_P(target))
|
318
|
+
prof_method_mark(call_info->target);
|
319
|
+
else
|
320
|
+
rb_gc_mark(target);
|
321
|
+
}
|
322
|
+
rb_gc_mark(call_info->children);
|
323
|
+
if (call_info->parent) {
|
324
|
+
VALUE parent = call_info->parent->object;
|
325
|
+
if (NIL_P(parent)) {
|
326
|
+
prof_call_info_mark(call_info->parent);
|
327
|
+
}
|
328
|
+
else {
|
329
|
+
rb_gc_mark(parent);
|
330
|
+
}
|
331
|
+
}
|
332
|
+
}
|
333
|
+
|
334
|
+
static void
|
335
|
+
prof_call_info_free(prof_call_info_t *call_info)
|
336
|
+
{
|
337
|
+
call_info_table_free(call_info->call_infos);
|
338
|
+
xfree(call_info);
|
339
|
+
}
|
340
|
+
|
341
|
+
static VALUE
|
342
|
+
prof_call_info_wrap(prof_call_info_t *call_info)
|
343
|
+
{
|
344
|
+
if (call_info->object == Qnil)
|
345
|
+
{
|
346
|
+
call_info->object = Data_Wrap_Struct(cCallInfo, prof_call_info_mark, prof_call_info_free, call_info);
|
347
|
+
}
|
348
|
+
return call_info->object;
|
349
|
+
}
|
350
|
+
|
351
|
+
static prof_call_info_t *
|
352
|
+
prof_get_call_info_result(VALUE obj)
|
353
|
+
{
|
354
|
+
if (BUILTIN_TYPE(obj) != T_DATA)
|
355
|
+
{
|
356
|
+
/* Should never happen */
|
357
|
+
rb_raise(rb_eTypeError, "Not a call info object");
|
358
|
+
}
|
359
|
+
return (prof_call_info_t *) DATA_PTR(obj);
|
360
|
+
}
|
361
|
+
|
362
|
+
|
363
|
+
/* call-seq:
|
364
|
+
called -> MethodInfo
|
365
|
+
|
366
|
+
Returns the target method. */
|
367
|
+
static VALUE
|
368
|
+
prof_call_info_target(VALUE self)
|
369
|
+
{
|
370
|
+
/* Target is a pointer to a method_info - so we have to be careful
|
371
|
+
about the GC. We will wrap the method_info but provide no
|
372
|
+
free method so the underlying object is not freed twice! */
|
373
|
+
|
374
|
+
prof_call_info_t *result = prof_get_call_info_result(self);
|
375
|
+
return prof_method_wrap(result->target);
|
376
|
+
}
|
377
|
+
|
378
|
+
/* call-seq:
|
379
|
+
called -> int
|
380
|
+
|
381
|
+
Returns the total amount of times this method was called. */
|
382
|
+
static VALUE
|
383
|
+
prof_call_info_called(VALUE self)
|
384
|
+
{
|
385
|
+
prof_call_info_t *result = prof_get_call_info_result(self);
|
386
|
+
return INT2NUM(result->called);
|
387
|
+
}
|
388
|
+
|
389
|
+
/* call-seq:
|
390
|
+
line_no -> int
|
391
|
+
|
392
|
+
returns the line number of the method */
|
393
|
+
static VALUE
|
394
|
+
prof_call_info_line(VALUE self)
|
395
|
+
{
|
396
|
+
prof_call_info_t *result = prof_get_call_info_result(self);
|
397
|
+
return rb_int_new(result->line);
|
398
|
+
}
|
399
|
+
|
400
|
+
/* call-seq:
|
401
|
+
total_time -> float
|
402
|
+
|
403
|
+
Returns the total amount of time spent in this method and its children. */
|
404
|
+
static VALUE
|
405
|
+
prof_call_info_total_time(VALUE self)
|
406
|
+
{
|
407
|
+
prof_call_info_t *result = prof_get_call_info_result(self);
|
408
|
+
return rb_float_new(convert_measurement(result->total_time));
|
409
|
+
}
|
410
|
+
|
411
|
+
/* call-seq:
|
412
|
+
self_time -> float
|
413
|
+
|
414
|
+
Returns the total amount of time spent in this method. */
|
415
|
+
static VALUE
|
416
|
+
prof_call_info_self_time(VALUE self)
|
417
|
+
{
|
418
|
+
prof_call_info_t *result = prof_get_call_info_result(self);
|
419
|
+
|
420
|
+
return rb_float_new(convert_measurement(result->self_time));
|
421
|
+
}
|
422
|
+
|
423
|
+
/* call-seq:
|
424
|
+
wait_time -> float
|
425
|
+
|
426
|
+
Returns the total amount of time this method waited for other threads. */
|
427
|
+
static VALUE
|
428
|
+
prof_call_info_wait_time(VALUE self)
|
429
|
+
{
|
430
|
+
prof_call_info_t *result = prof_get_call_info_result(self);
|
431
|
+
|
432
|
+
return rb_float_new(convert_measurement(result->wait_time));
|
433
|
+
}
|
434
|
+
|
435
|
+
/* call-seq:
|
436
|
+
parent -> call_info
|
437
|
+
|
438
|
+
Returns the call_infos parent call_info object (the method that called this method).*/
|
439
|
+
static VALUE
|
440
|
+
prof_call_info_parent(VALUE self)
|
441
|
+
{
|
442
|
+
prof_call_info_t *result = prof_get_call_info_result(self);
|
443
|
+
if (result->parent)
|
444
|
+
return prof_call_info_wrap(result->parent);
|
445
|
+
else
|
446
|
+
return Qnil;
|
447
|
+
}
|
448
|
+
|
449
|
+
static int
|
450
|
+
prof_call_info_collect_children(st_data_t key, st_data_t value, st_data_t result)
|
451
|
+
{
|
452
|
+
prof_call_info_t *call_info = (prof_call_info_t *) value;
|
453
|
+
VALUE arr = (VALUE) result;
|
454
|
+
rb_ary_push(arr, prof_call_info_wrap(call_info));
|
455
|
+
return ST_CONTINUE;
|
456
|
+
}
|
457
|
+
|
458
|
+
/* call-seq:
|
459
|
+
children -> hash
|
460
|
+
|
461
|
+
Returns an array of call info objects of methods that this method
|
462
|
+
called (ie, children).*/
|
463
|
+
static VALUE
|
464
|
+
prof_call_info_children(VALUE self)
|
465
|
+
{
|
466
|
+
prof_call_info_t *call_info = prof_get_call_info_result(self);
|
467
|
+
if (call_info->children == Qnil)
|
468
|
+
{
|
469
|
+
call_info->children = rb_ary_new();
|
470
|
+
st_foreach(call_info->call_infos, prof_call_info_collect_children, call_info->children);
|
471
|
+
}
|
472
|
+
return call_info->children;
|
473
|
+
}
|
474
|
+
|
475
|
+
/* ================ Call Infos =================*/
|
476
|
+
static prof_call_infos_t*
|
477
|
+
prof_call_infos_create()
|
478
|
+
{
|
479
|
+
prof_call_infos_t *result = ALLOC(prof_call_infos_t);
|
480
|
+
result->start = ALLOC_N(prof_call_info_t*, INITIAL_CALL_INFOS_SIZE);
|
481
|
+
result->end = result->start + INITIAL_CALL_INFOS_SIZE;
|
482
|
+
result->ptr = result->start;
|
483
|
+
result->object = Qnil;
|
484
|
+
return result;
|
485
|
+
}
|
486
|
+
|
487
|
+
static void
|
488
|
+
prof_call_infos_free(prof_call_infos_t *call_infos)
|
489
|
+
{
|
490
|
+
xfree(call_infos->start);
|
491
|
+
xfree(call_infos);
|
492
|
+
}
|
493
|
+
|
494
|
+
static void
|
495
|
+
prof_add_call_info(prof_call_infos_t *call_infos, prof_call_info_t *call_info)
|
496
|
+
{
|
497
|
+
if (call_infos->ptr == call_infos->end)
|
498
|
+
{
|
499
|
+
size_t len = call_infos->ptr - call_infos->start;
|
500
|
+
size_t new_capacity = (call_infos->end - call_infos->start) * 2;
|
501
|
+
REALLOC_N(call_infos->start, prof_call_info_t*, new_capacity);
|
502
|
+
call_infos->ptr = call_infos->start + len;
|
503
|
+
call_infos->end = call_infos->start + new_capacity;
|
504
|
+
}
|
505
|
+
*call_infos->ptr = call_info;
|
506
|
+
call_infos->ptr++;
|
507
|
+
}
|
508
|
+
|
509
|
+
static VALUE
|
510
|
+
prof_call_infos_wrap(prof_call_infos_t *call_infos)
|
511
|
+
{
|
512
|
+
if (call_infos->object == Qnil)
|
513
|
+
{
|
514
|
+
prof_call_info_t **i;
|
515
|
+
call_infos->object = rb_ary_new();
|
516
|
+
for(i=call_infos->start; i<call_infos->ptr; i++)
|
517
|
+
{
|
518
|
+
VALUE call_info = prof_call_info_wrap(*i);
|
519
|
+
rb_ary_push(call_infos->object, call_info);
|
520
|
+
}
|
521
|
+
}
|
522
|
+
return call_infos->object;
|
523
|
+
}
|
524
|
+
|
525
|
+
|
526
|
+
/* ================ Method Info =================*/
|
527
|
+
/* Document-class: RubyProf::MethodInfo
|
528
|
+
The RubyProf::MethodInfo class stores profiling data for a method.
|
529
|
+
One instance of the RubyProf::MethodInfo class is created per method
|
530
|
+
called per thread. Thus, if a method is called in two different
|
531
|
+
thread then there will be two RubyProf::MethodInfo objects
|
532
|
+
created. RubyProf::MethodInfo objects can be accessed via
|
533
|
+
the RubyProf::Result object.
|
534
|
+
*/
|
535
|
+
|
536
|
+
static prof_method_t*
|
537
|
+
prof_method_create(prof_method_key_t *key, const char* source_file, int line)
|
538
|
+
{
|
539
|
+
prof_method_t *result = ALLOC(prof_method_t);
|
540
|
+
result->object = Qnil;
|
541
|
+
result->key = ALLOC(prof_method_key_t);
|
542
|
+
method_key(result->key, key->klass, key->mid, key->depth);
|
543
|
+
|
544
|
+
result->call_infos = prof_call_infos_create();
|
545
|
+
|
546
|
+
result->active = 0;
|
547
|
+
|
548
|
+
if (source_file != NULL)
|
549
|
+
{
|
550
|
+
int len = strlen(source_file) + 1;
|
551
|
+
char *buffer = ALLOC_N(char, len);
|
552
|
+
|
553
|
+
MEMCPY(buffer, source_file, char, len);
|
554
|
+
result->source_file = buffer;
|
555
|
+
}
|
556
|
+
else
|
557
|
+
{
|
558
|
+
result->source_file = source_file;
|
559
|
+
}
|
560
|
+
result->line = line;
|
561
|
+
|
562
|
+
return result;
|
563
|
+
}
|
564
|
+
|
565
|
+
static void
|
566
|
+
prof_method_mark(prof_method_t *method)
|
567
|
+
{
|
568
|
+
rb_gc_mark(method->call_infos->object);
|
569
|
+
rb_gc_mark(method->key->klass);
|
570
|
+
}
|
571
|
+
|
572
|
+
static void
|
573
|
+
prof_method_free(prof_method_t *method)
|
574
|
+
{
|
575
|
+
if (method->source_file)
|
576
|
+
{
|
577
|
+
xfree((char*)method->source_file);
|
578
|
+
}
|
579
|
+
|
580
|
+
prof_call_infos_free(method->call_infos);
|
581
|
+
xfree(method->key);
|
582
|
+
xfree(method);
|
583
|
+
}
|
584
|
+
|
585
|
+
static VALUE
|
586
|
+
prof_method_wrap(prof_method_t *result)
|
587
|
+
{
|
588
|
+
if (result->object == Qnil)
|
589
|
+
{
|
590
|
+
result->object = Data_Wrap_Struct(cMethodInfo, prof_method_mark, prof_method_free, result);
|
591
|
+
}
|
592
|
+
return result->object;
|
593
|
+
}
|
594
|
+
|
595
|
+
static prof_method_t *
|
596
|
+
get_prof_method(VALUE obj)
|
597
|
+
{
|
598
|
+
return (prof_method_t *) DATA_PTR(obj);
|
599
|
+
}
|
600
|
+
|
601
|
+
/* call-seq:
|
602
|
+
line_no -> int
|
603
|
+
|
604
|
+
returns the line number of the method */
|
605
|
+
static VALUE
|
606
|
+
prof_method_line(VALUE self)
|
607
|
+
{
|
608
|
+
return rb_int_new(get_prof_method(self)->line);
|
609
|
+
}
|
610
|
+
|
611
|
+
/* call-seq:
|
612
|
+
source_file => string
|
613
|
+
|
614
|
+
return the source file of the method
|
615
|
+
*/
|
616
|
+
static VALUE prof_method_source_file(VALUE self)
|
617
|
+
{
|
618
|
+
const char* sf = get_prof_method(self)->source_file;
|
619
|
+
if(!sf)
|
620
|
+
{
|
621
|
+
return rb_str_new2("ruby_runtime");
|
622
|
+
}
|
623
|
+
else
|
624
|
+
{
|
625
|
+
return rb_str_new2(sf);
|
626
|
+
}
|
627
|
+
}
|
628
|
+
|
629
|
+
|
630
|
+
/* call-seq:
|
631
|
+
method_class -> klass
|
632
|
+
|
633
|
+
Returns the Ruby klass that owns this method. */
|
634
|
+
static VALUE
|
635
|
+
prof_method_klass(VALUE self)
|
636
|
+
{
|
637
|
+
prof_method_t *result = get_prof_method(self);
|
638
|
+
return result->key->klass;
|
639
|
+
}
|
640
|
+
|
641
|
+
/* call-seq:
|
642
|
+
method_id -> ID
|
643
|
+
|
644
|
+
Returns the id of this method. */
|
645
|
+
static VALUE
|
646
|
+
prof_method_id(VALUE self)
|
647
|
+
{
|
648
|
+
prof_method_t *result = get_prof_method(self);
|
649
|
+
return ID2SYM(result->key->mid);
|
650
|
+
}
|
651
|
+
|
652
|
+
/* call-seq:
|
653
|
+
klass_name -> string
|
654
|
+
|
655
|
+
Returns the name of this method's class. Singleton classes
|
656
|
+
will have the form <Object::Object>. */
|
657
|
+
|
658
|
+
static VALUE
|
659
|
+
prof_klass_name(VALUE self)
|
660
|
+
{
|
661
|
+
prof_method_t *method = get_prof_method(self);
|
662
|
+
return klass_name(method->key->klass);
|
663
|
+
}
|
664
|
+
|
665
|
+
/* call-seq:
|
666
|
+
method_name -> string
|
667
|
+
|
668
|
+
Returns the name of this method in the format Object#method. Singletons
|
669
|
+
methods will be returned in the format <Object::Object>#method.*/
|
670
|
+
|
671
|
+
static VALUE
|
672
|
+
prof_method_name(VALUE self, int depth)
|
673
|
+
{
|
674
|
+
prof_method_t *method = get_prof_method(self);
|
675
|
+
return method_name(method->key->mid, depth);
|
676
|
+
}
|
677
|
+
|
678
|
+
/* call-seq:
|
679
|
+
full_name -> string
|
680
|
+
|
681
|
+
Returns the full name of this method in the format Object#method.*/
|
682
|
+
|
683
|
+
static VALUE
|
684
|
+
prof_full_name(VALUE self)
|
685
|
+
{
|
686
|
+
prof_method_t *method = get_prof_method(self);
|
687
|
+
return full_name(method->key->klass, method->key->mid, method->key->depth);
|
688
|
+
}
|
689
|
+
|
690
|
+
/* call-seq:
|
691
|
+
call_infos -> Array of call_info
|
692
|
+
|
693
|
+
Returns an array of call info objects that contain profiling information
|
694
|
+
about the current method.*/
|
695
|
+
static VALUE
|
696
|
+
prof_method_call_infos(VALUE self)
|
697
|
+
{
|
698
|
+
prof_method_t *method = get_prof_method(self);
|
699
|
+
return prof_call_infos_wrap(method->call_infos);
|
700
|
+
}
|
701
|
+
|
702
|
+
static int
|
703
|
+
collect_methods(st_data_t key, st_data_t value, st_data_t result)
|
704
|
+
{
|
705
|
+
/* Called for each method stored in a thread's method table.
|
706
|
+
We want to store the method info information into an array.*/
|
707
|
+
VALUE methods = (VALUE) result;
|
708
|
+
prof_method_t *method = (prof_method_t *) value;
|
709
|
+
rb_ary_push(methods, prof_method_wrap(method));
|
710
|
+
|
711
|
+
/* Wrap call info objects */
|
712
|
+
prof_call_infos_wrap(method->call_infos);
|
713
|
+
|
714
|
+
return ST_CONTINUE;
|
715
|
+
}
|
716
|
+
|
717
|
+
/* ================ Method Table =================*/
|
718
|
+
static st_table *
|
719
|
+
method_table_create()
|
720
|
+
{
|
721
|
+
return st_init_table(&type_method_hash);
|
722
|
+
}
|
723
|
+
|
724
|
+
static size_t
|
725
|
+
method_table_insert(st_table *table, const prof_method_key_t *key, prof_method_t *val)
|
726
|
+
{
|
727
|
+
return st_insert(table, (st_data_t) key, (st_data_t) val);
|
728
|
+
}
|
729
|
+
|
730
|
+
static prof_method_t *
|
731
|
+
method_table_lookup(st_table *table, const prof_method_key_t* key)
|
732
|
+
{
|
733
|
+
st_data_t val;
|
734
|
+
if (st_lookup(table, (st_data_t)key, &val))
|
735
|
+
{
|
736
|
+
return (prof_method_t *) val;
|
737
|
+
}
|
738
|
+
else
|
739
|
+
{
|
740
|
+
return NULL;
|
741
|
+
}
|
742
|
+
}
|
743
|
+
|
744
|
+
|
745
|
+
static void
|
746
|
+
method_table_free(st_table *table)
|
747
|
+
{
|
748
|
+
/* Don't free the contents since they are wrapped by
|
749
|
+
Ruby objects! */
|
750
|
+
st_free_table(table);
|
751
|
+
}
|
752
|
+
|
753
|
+
|
754
|
+
/* ================ Thread Handling =================*/
|
755
|
+
|
756
|
+
/* ---- Keeps track of thread's stack and methods ---- */
|
757
|
+
static thread_data_t*
|
758
|
+
thread_data_create()
|
759
|
+
{
|
760
|
+
thread_data_t* result = ALLOC(thread_data_t);
|
761
|
+
result->stack = stack_create();
|
762
|
+
result->method_table = method_table_create();
|
763
|
+
result->last_switch = get_measurement();
|
764
|
+
return result;
|
765
|
+
}
|
766
|
+
|
767
|
+
static void
|
768
|
+
thread_data_free(thread_data_t* thread_data)
|
769
|
+
{
|
770
|
+
method_table_free(thread_data->method_table);
|
771
|
+
stack_free(thread_data->stack);
|
772
|
+
xfree(thread_data);
|
773
|
+
}
|
774
|
+
|
775
|
+
/* ---- Hash, keyed on thread, that stores thread's stack
|
776
|
+
and methods---- */
|
777
|
+
|
778
|
+
static st_table *
|
779
|
+
threads_table_create()
|
780
|
+
{
|
781
|
+
return st_init_numtable();
|
782
|
+
}
|
783
|
+
|
784
|
+
static size_t
|
785
|
+
threads_table_insert(st_table *table, VALUE thread, thread_data_t *thread_data)
|
786
|
+
{
|
787
|
+
/* Its too slow to key on the real thread id so just typecast thread instead. */
|
788
|
+
return st_insert(table, (st_data_t) thread, (st_data_t) thread_data);
|
789
|
+
}
|
790
|
+
|
791
|
+
static thread_data_t *
|
792
|
+
threads_table_lookup(st_table *table, VALUE thread_id)
|
793
|
+
{
|
794
|
+
thread_data_t* result;
|
795
|
+
st_data_t val;
|
796
|
+
|
797
|
+
/* Its too slow to key on the real thread id so just typecast thread instead. */
|
798
|
+
if (st_lookup(table, (st_data_t) thread_id, &val))
|
799
|
+
{
|
800
|
+
result = (thread_data_t *) val;
|
801
|
+
}
|
802
|
+
else
|
803
|
+
{
|
804
|
+
result = thread_data_create();
|
805
|
+
result->thread_id = thread_id;
|
806
|
+
|
807
|
+
/* Insert the table */
|
808
|
+
threads_table_insert(threads_tbl, thread_id, result);
|
809
|
+
}
|
810
|
+
return result;
|
811
|
+
}
|
812
|
+
|
813
|
+
static int
|
814
|
+
free_thread_data(st_data_t key, st_data_t value, st_data_t dummy)
|
815
|
+
{
|
816
|
+
thread_data_free((thread_data_t*)value);
|
817
|
+
return ST_CONTINUE;
|
818
|
+
}
|
819
|
+
|
820
|
+
|
821
|
+
static void
|
822
|
+
threads_table_free(st_table *table)
|
823
|
+
{
|
824
|
+
st_foreach(table, free_thread_data, 0);
|
825
|
+
st_free_table(table);
|
826
|
+
}
|
827
|
+
|
828
|
+
|
829
|
+
static int
|
830
|
+
collect_threads(st_data_t key, st_data_t value, st_data_t result)
|
831
|
+
{
|
832
|
+
/* Although threads are keyed on an id, that is actually a
|
833
|
+
pointer to the VALUE object of the thread. So its bogus.
|
834
|
+
However, in thread_data is the real thread id stored
|
835
|
+
as an int. */
|
836
|
+
thread_data_t* thread_data = (thread_data_t*) value;
|
837
|
+
VALUE threads_hash = (VALUE) result;
|
838
|
+
|
839
|
+
VALUE methods = rb_ary_new();
|
840
|
+
|
841
|
+
/* Now collect an array of all the called methods */
|
842
|
+
st_table* method_table = thread_data->method_table;
|
843
|
+
st_foreach(method_table, collect_methods, methods);
|
844
|
+
|
845
|
+
/* Store the results in the threads hash keyed on the thread id. */
|
846
|
+
rb_hash_aset(threads_hash, thread_data->thread_id, methods);
|
847
|
+
|
848
|
+
return ST_CONTINUE;
|
849
|
+
}
|
850
|
+
|
851
|
+
|
852
|
+
/* ================ Profiling =================*/
|
853
|
+
/* Copied from eval.c */
|
854
|
+
#ifdef DEBUG
|
855
|
+
static char *
|
856
|
+
get_event_name(rb_event_flag_t event)
|
857
|
+
{
|
858
|
+
switch (event) {
|
859
|
+
case RUBY_EVENT_LINE:
|
860
|
+
return "line";
|
861
|
+
case RUBY_EVENT_CLASS:
|
862
|
+
return "class";
|
863
|
+
case RUBY_EVENT_END:
|
864
|
+
return "end";
|
865
|
+
case RUBY_EVENT_CALL:
|
866
|
+
return "call";
|
867
|
+
case RUBY_EVENT_RETURN:
|
868
|
+
return "return";
|
869
|
+
case RUBY_EVENT_C_CALL:
|
870
|
+
return "c-call";
|
871
|
+
case RUBY_EVENT_C_RETURN:
|
872
|
+
return "c-return";
|
873
|
+
case RUBY_EVENT_RAISE:
|
874
|
+
return "raise";
|
875
|
+
|
876
|
+
#ifdef RUBY_VM
|
877
|
+
case RUBY_EVENT_SWITCH:
|
878
|
+
return "thread-interrupt";
|
879
|
+
#endif
|
880
|
+
|
881
|
+
default:
|
882
|
+
return "unknown";
|
883
|
+
}
|
884
|
+
}
|
885
|
+
#endif
|
886
|
+
|
887
|
+
static prof_method_t*
|
888
|
+
#ifdef RUBY_VM
|
889
|
+
get_method(rb_event_flag_t event, VALUE klass, ID mid, int depth, st_table* method_table)
|
890
|
+
# else
|
891
|
+
get_method(rb_event_flag_t event, NODE *node, VALUE klass, ID mid, int depth, st_table* method_table)
|
892
|
+
#endif
|
893
|
+
{
|
894
|
+
prof_method_key_t key;
|
895
|
+
prof_method_t *method = NULL;
|
896
|
+
|
897
|
+
method_key(&key, klass, mid, depth);
|
898
|
+
method = method_table_lookup(method_table, &key);
|
899
|
+
|
900
|
+
if (!method)
|
901
|
+
{
|
902
|
+
const char* source_file = rb_sourcefile();
|
903
|
+
int line = rb_sourceline();
|
904
|
+
|
905
|
+
/* Line numbers are not accurate for c method calls */
|
906
|
+
if (event == RUBY_EVENT_C_CALL)
|
907
|
+
{
|
908
|
+
line = 0;
|
909
|
+
source_file = NULL;
|
910
|
+
}
|
911
|
+
|
912
|
+
method = prof_method_create(&key, source_file, line);
|
913
|
+
method_table_insert(method_table, method->key, method);
|
914
|
+
}
|
915
|
+
return method;
|
916
|
+
}
|
917
|
+
|
918
|
+
static void
|
919
|
+
update_result(prof_measure_t total_time,
|
920
|
+
prof_frame_t *parent_frame,
|
921
|
+
prof_frame_t *frame)
|
922
|
+
{
|
923
|
+
prof_measure_t self_time = total_time - frame->child_time - frame->wait_time;
|
924
|
+
prof_call_info_t *call_info = frame->call_info;
|
925
|
+
|
926
|
+
/* Update information about the current method */
|
927
|
+
call_info->called++;
|
928
|
+
call_info->total_time += total_time;
|
929
|
+
call_info->self_time += self_time;
|
930
|
+
call_info->wait_time += frame->wait_time;
|
931
|
+
|
932
|
+
/* Note where the current method was called from */
|
933
|
+
if (parent_frame)
|
934
|
+
call_info->line = parent_frame->line;
|
935
|
+
}
|
936
|
+
|
937
|
+
static thread_data_t *
|
938
|
+
switch_thread(VALUE thread_id, prof_measure_t now)
|
939
|
+
{
|
940
|
+
prof_frame_t *frame = NULL;
|
941
|
+
prof_measure_t wait_time = 0;
|
942
|
+
/* Get new thread information. */
|
943
|
+
thread_data_t *thread_data = threads_table_lookup(threads_tbl, thread_id);
|
944
|
+
|
945
|
+
/* How long has this thread been waiting? */
|
946
|
+
wait_time = now - thread_data->last_switch;
|
947
|
+
|
948
|
+
thread_data->last_switch = now; // XXXX a test that fails if this is 0
|
949
|
+
|
950
|
+
/* Get the frame at the top of the stack. This may represent
|
951
|
+
the current method (EVENT_LINE, EVENT_RETURN) or the
|
952
|
+
previous method (EVENT_CALL).*/
|
953
|
+
frame = stack_peek(thread_data->stack);
|
954
|
+
|
955
|
+
if (frame) {
|
956
|
+
frame->wait_time += wait_time;
|
957
|
+
}
|
958
|
+
|
959
|
+
/* Save on the last thread the time of the context switch
|
960
|
+
and reset this thread's last context switch to 0.*/
|
961
|
+
if (last_thread_data) {
|
962
|
+
last_thread_data->last_switch = now;
|
963
|
+
}
|
964
|
+
|
965
|
+
last_thread_data = thread_data;
|
966
|
+
return thread_data;
|
967
|
+
}
|
968
|
+
|
969
|
+
static prof_frame_t*
|
970
|
+
pop_frame(thread_data_t *thread_data, prof_measure_t now)
|
971
|
+
{
|
972
|
+
prof_frame_t *frame = NULL;
|
973
|
+
prof_frame_t* parent_frame = NULL;
|
974
|
+
prof_measure_t total_time;
|
975
|
+
|
976
|
+
frame = stack_pop(thread_data->stack); // only time it's called
|
977
|
+
/* Frame can be null. This can happen if RubProf.start is called from
|
978
|
+
a method that exits. And it can happen if an exception is raised
|
979
|
+
in code that is being profiled and the stack unwinds (RubyProf is
|
980
|
+
not notified of that by the ruby runtime. */
|
981
|
+
if (frame == NULL) return NULL;
|
982
|
+
|
983
|
+
/* Calculate the total time this method took */
|
984
|
+
total_time = now - frame->start_time;
|
985
|
+
|
986
|
+
/* Now deactivate the method */
|
987
|
+
frame->call_info->target->active = 0;
|
988
|
+
|
989
|
+
parent_frame = stack_peek(thread_data->stack);
|
990
|
+
if (parent_frame)
|
991
|
+
{
|
992
|
+
parent_frame->child_time += total_time;
|
993
|
+
}
|
994
|
+
|
995
|
+
update_result(total_time, parent_frame, frame); // only time it's called
|
996
|
+
return frame;
|
997
|
+
}
|
998
|
+
|
999
|
+
static int
|
1000
|
+
pop_frames(st_data_t key, st_data_t value, st_data_t now_arg)
|
1001
|
+
{
|
1002
|
+
VALUE thread_id = (VALUE)key;
|
1003
|
+
thread_data_t* thread_data = (thread_data_t *) value;
|
1004
|
+
prof_measure_t now = *(prof_measure_t *) now_arg;
|
1005
|
+
|
1006
|
+
if (!last_thread_data || last_thread_data->thread_id != thread_id)
|
1007
|
+
thread_data = switch_thread(thread_id, now);
|
1008
|
+
else
|
1009
|
+
thread_data = last_thread_data;
|
1010
|
+
|
1011
|
+
while (pop_frame(thread_data, now))
|
1012
|
+
{
|
1013
|
+
}
|
1014
|
+
|
1015
|
+
return ST_CONTINUE;
|
1016
|
+
}
|
1017
|
+
|
1018
|
+
static void
|
1019
|
+
prof_pop_threads()
|
1020
|
+
{
|
1021
|
+
/* Get current measurement */
|
1022
|
+
prof_measure_t now = get_measurement();
|
1023
|
+
st_foreach(threads_tbl, pop_frames, (st_data_t) &now);
|
1024
|
+
}
|
1025
|
+
|
1026
|
+
|
1027
|
+
#ifdef RUBY_VM
|
1028
|
+
|
1029
|
+
/* These are mostly to avoid bugs in core */
|
1030
|
+
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);
|
1031
|
+
void prof_install_hook();
|
1032
|
+
void prof_remove_hook();
|
1033
|
+
|
1034
|
+
static void
|
1035
|
+
prof_event_hook(rb_event_flag_t event, VALUE data, VALUE self, ID mid, VALUE klass)
|
1036
|
+
#else
|
1037
|
+
static void
|
1038
|
+
prof_event_hook(rb_event_flag_t event, NODE *node, VALUE self, ID mid, VALUE klass)
|
1039
|
+
#endif
|
1040
|
+
{
|
1041
|
+
VALUE thread = Qnil;
|
1042
|
+
VALUE thread_id = Qnil;
|
1043
|
+
prof_measure_t now = 0;
|
1044
|
+
thread_data_t* thread_data = NULL;
|
1045
|
+
prof_frame_t *frame = NULL;
|
1046
|
+
|
1047
|
+
#ifdef RUBY_VM
|
1048
|
+
if (event != RUBY_EVENT_C_CALL && event != RUBY_EVENT_C_RETURN) {
|
1049
|
+
// guess these are already set for C calls in 1.9, then?
|
1050
|
+
rb_frame_method_id_and_class(&mid, &klass);
|
1051
|
+
}
|
1052
|
+
#endif
|
1053
|
+
|
1054
|
+
/* Get current timestamp */
|
1055
|
+
now = get_measurement();
|
1056
|
+
|
1057
|
+
#ifdef DEBUG
|
1058
|
+
/* This code is here for debug purposes - uncomment it out
|
1059
|
+
when debugging to see a print out of exactly what the
|
1060
|
+
profiler is tracing. */
|
1061
|
+
{
|
1062
|
+
static VALUE last_thread_id = Qnil;
|
1063
|
+
|
1064
|
+
VALUE thread = rb_thread_current();
|
1065
|
+
VALUE thread_id = rb_obj_id(thread);
|
1066
|
+
const char* class_name = NULL;
|
1067
|
+
const char* method_name = rb_id2name(mid);
|
1068
|
+
const char* source_file = rb_sourcefile();
|
1069
|
+
unsigned int source_line = rb_sourceline();
|
1070
|
+
|
1071
|
+
char* event_name = get_event_name(event);
|
1072
|
+
|
1073
|
+
if (klass != 0)
|
1074
|
+
klass = (BUILTIN_TYPE(klass) == T_ICLASS ? RBASIC(klass)->klass : klass);
|
1075
|
+
|
1076
|
+
class_name = rb_class2name(klass);
|
1077
|
+
|
1078
|
+
if (last_thread_id != thread_id) {
|
1079
|
+
printf("\n");
|
1080
|
+
}
|
1081
|
+
|
1082
|
+
printf("%2u:%2ums %-8s %s:%2d %s#%s\n",
|
1083
|
+
(unsigned int) thread_id, (unsigned int) now, event_name, source_file, source_line, class_name, method_name);
|
1084
|
+
fflush(stdout);
|
1085
|
+
last_thread_id = thread_id;
|
1086
|
+
}
|
1087
|
+
#endif
|
1088
|
+
|
1089
|
+
/* Special case - skip any methods from the mProf
|
1090
|
+
module, such as Prof.stop, since they clutter
|
1091
|
+
the results but aren't important to them results. */
|
1092
|
+
if (self == mProf) return;
|
1093
|
+
|
1094
|
+
/* Get the current thread information. */
|
1095
|
+
thread = rb_thread_current();
|
1096
|
+
thread_id = rb_obj_id(thread);
|
1097
|
+
|
1098
|
+
# ifdef RUBY_VM
|
1099
|
+
/* ensure that new threads are hooked [sigh] (bug in core) */
|
1100
|
+
prof_remove_hook();
|
1101
|
+
prof_install_hook();
|
1102
|
+
# endif
|
1103
|
+
|
1104
|
+
if (exclude_threads_tbl &&
|
1105
|
+
st_lookup(exclude_threads_tbl, (st_data_t) thread_id, 0))
|
1106
|
+
{
|
1107
|
+
return;
|
1108
|
+
}
|
1109
|
+
|
1110
|
+
|
1111
|
+
/* Was there a context switch? */
|
1112
|
+
if (!last_thread_data || last_thread_data->thread_id != thread_id)
|
1113
|
+
thread_data = switch_thread(thread_id, now);
|
1114
|
+
else
|
1115
|
+
thread_data = last_thread_data;
|
1116
|
+
|
1117
|
+
|
1118
|
+
switch (event) {
|
1119
|
+
case RUBY_EVENT_LINE:
|
1120
|
+
{
|
1121
|
+
/* Keep track of the current line number in this method. When
|
1122
|
+
a new method is called, we know what line number it was
|
1123
|
+
called from. */
|
1124
|
+
|
1125
|
+
/* Get the current frame for the current thread. */
|
1126
|
+
frame = stack_peek(thread_data->stack);
|
1127
|
+
|
1128
|
+
if (frame)
|
1129
|
+
{
|
1130
|
+
frame->line = rb_sourceline();
|
1131
|
+
|
1132
|
+
# ifdef RUBY_VM
|
1133
|
+
// disabled till I figure out why it causes
|
1134
|
+
// us to lose valuable frame information...maybe mid comes in wrong sometimes?
|
1135
|
+
// walk_up_until_right_frame(frame, thread_data, mid, klass, now);
|
1136
|
+
# endif
|
1137
|
+
|
1138
|
+
break;
|
1139
|
+
}
|
1140
|
+
|
1141
|
+
/* If we get here there was no frame, which means this is
|
1142
|
+
the first method seen for this thread, so fall through
|
1143
|
+
to below to create it. */
|
1144
|
+
}
|
1145
|
+
case RUBY_EVENT_CALL:
|
1146
|
+
case RUBY_EVENT_C_CALL:
|
1147
|
+
{
|
1148
|
+
prof_call_info_t *call_info = NULL;
|
1149
|
+
prof_method_t *method = NULL;
|
1150
|
+
|
1151
|
+
/* Get the current frame for the current thread. */
|
1152
|
+
frame = stack_peek(thread_data->stack);
|
1153
|
+
|
1154
|
+
/* Is this an include for a module? If so get the actual
|
1155
|
+
module class since we want to combine all profiling
|
1156
|
+
results for that module. */
|
1157
|
+
|
1158
|
+
if (klass != 0)
|
1159
|
+
klass = (BUILTIN_TYPE(klass) == T_ICLASS ? RBASIC(klass)->klass : klass);
|
1160
|
+
|
1161
|
+
/* Assume this is the first time we have called this method. */
|
1162
|
+
#ifdef RUBY_VM
|
1163
|
+
method = get_method(event, klass, mid, 0, thread_data->method_table);
|
1164
|
+
#else
|
1165
|
+
method = get_method(event, node, klass, mid, 0, thread_data->method_table);
|
1166
|
+
#endif
|
1167
|
+
/* Check for a recursive call */
|
1168
|
+
while (method->active) // it's while because we start at 0 and then inc. to the right recursive depth
|
1169
|
+
{
|
1170
|
+
/* Yes, this method is already active somewhere up the stack */
|
1171
|
+
#ifdef RUBY_VM
|
1172
|
+
method = get_method(event, klass, mid, method->key->depth + 1, thread_data->method_table);
|
1173
|
+
#else
|
1174
|
+
method = get_method(event, node, klass, mid, method->key->depth + 1, thread_data->method_table);
|
1175
|
+
#endif
|
1176
|
+
}
|
1177
|
+
method->active = 1;
|
1178
|
+
|
1179
|
+
if (!frame)
|
1180
|
+
{
|
1181
|
+
call_info = prof_call_info_create(method, NULL);
|
1182
|
+
prof_add_call_info(method->call_infos, call_info);
|
1183
|
+
}
|
1184
|
+
else
|
1185
|
+
{
|
1186
|
+
call_info = call_info_table_lookup(frame->call_info->call_infos, method->key);
|
1187
|
+
|
1188
|
+
if (!call_info)
|
1189
|
+
{
|
1190
|
+
call_info = prof_call_info_create(method, frame->call_info);
|
1191
|
+
call_info_table_insert(frame->call_info->call_infos, method->key, call_info);
|
1192
|
+
prof_add_call_info(method->call_infos, call_info);
|
1193
|
+
}
|
1194
|
+
}
|
1195
|
+
|
1196
|
+
/* Push a new frame onto the stack for a new c-call or ruby call (into a method) */
|
1197
|
+
frame = stack_push(thread_data->stack);
|
1198
|
+
frame->call_info = call_info;
|
1199
|
+
frame->start_time = now;
|
1200
|
+
frame->wait_time = 0;
|
1201
|
+
frame->child_time = 0;
|
1202
|
+
frame->line = rb_sourceline();
|
1203
|
+
break;
|
1204
|
+
}
|
1205
|
+
case RUBY_EVENT_RETURN:
|
1206
|
+
case RUBY_EVENT_C_RETURN:
|
1207
|
+
{
|
1208
|
+
frame = pop_frame(thread_data, now);
|
1209
|
+
|
1210
|
+
# ifdef RUBY_VM
|
1211
|
+
// we need to walk up the stack to find the right one [http://redmine.ruby-lang.org/issues/show/2610] (for now)
|
1212
|
+
// sometimes frames don't have line and source somehow [like blank]
|
1213
|
+
// if we hit one there's not much we can do...I guess...
|
1214
|
+
// or maybe we don't have one because we're at the top or something.
|
1215
|
+
walk_up_until_right_frame(frame, thread_data, mid, klass, now);
|
1216
|
+
# endif
|
1217
|
+
|
1218
|
+
break;
|
1219
|
+
}
|
1220
|
+
}
|
1221
|
+
}
|
1222
|
+
|
1223
|
+
#ifdef RUBY_VM
|
1224
|
+
|
1225
|
+
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) {
|
1226
|
+
// while it doesn't match, pop on up until we have found where we belong...
|
1227
|
+
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))){
|
1228
|
+
frame = pop_frame(thread_data, now);
|
1229
|
+
}
|
1230
|
+
}
|
1231
|
+
#endif
|
1232
|
+
|
1233
|
+
/* ======== ProfResult ============== */
|
1234
|
+
|
1235
|
+
/* Document-class: RubyProf::Result
|
1236
|
+
The RubyProf::Result class is used to store the results of a
|
1237
|
+
profiling run. And instace of the class is returned from
|
1238
|
+
the methods RubyProf#stop and RubyProf#profile.
|
1239
|
+
|
1240
|
+
RubyProf::Result has one field, called threads, which is a hash
|
1241
|
+
table keyed on thread ID. For each thread id, the hash table
|
1242
|
+
stores another hash table that contains profiling information
|
1243
|
+
for each method called during the threads execution. That
|
1244
|
+
hash table is keyed on method name and contains
|
1245
|
+
RubyProf::MethodInfo objects. */
|
1246
|
+
|
1247
|
+
static void
|
1248
|
+
prof_result_mark(prof_result_t *prof_result)
|
1249
|
+
{
|
1250
|
+
VALUE threads = prof_result->threads;
|
1251
|
+
rb_gc_mark(threads);
|
1252
|
+
}
|
1253
|
+
|
1254
|
+
static void
|
1255
|
+
prof_result_free(prof_result_t *prof_result)
|
1256
|
+
{
|
1257
|
+
prof_result->threads = Qnil;
|
1258
|
+
xfree(prof_result);
|
1259
|
+
}
|
1260
|
+
|
1261
|
+
static VALUE
|
1262
|
+
prof_result_new()
|
1263
|
+
{
|
1264
|
+
prof_result_t *prof_result = ALLOC(prof_result_t);
|
1265
|
+
|
1266
|
+
/* Wrap threads in Ruby regular Ruby hash table. */
|
1267
|
+
prof_result->threads = rb_hash_new();
|
1268
|
+
st_foreach(threads_tbl, collect_threads, prof_result->threads);
|
1269
|
+
|
1270
|
+
return Data_Wrap_Struct(cResult, prof_result_mark, prof_result_free, prof_result);
|
1271
|
+
}
|
1272
|
+
|
1273
|
+
|
1274
|
+
static prof_result_t *
|
1275
|
+
get_prof_result(VALUE obj)
|
1276
|
+
{
|
1277
|
+
if (BUILTIN_TYPE(obj) != T_DATA ||
|
1278
|
+
RDATA(obj)->dfree != (RUBY_DATA_FUNC) prof_result_free)
|
1279
|
+
{
|
1280
|
+
/* Should never happen */
|
1281
|
+
rb_raise(rb_eTypeError, "wrong result object (%d %d) ", BUILTIN_TYPE(obj) != T_DATA, RDATA(obj)->dfree != (RUBY_DATA_FUNC) prof_result_free);
|
1282
|
+
}
|
1283
|
+
return (prof_result_t *) DATA_PTR(obj);
|
1284
|
+
}
|
1285
|
+
|
1286
|
+
/* call-seq:
|
1287
|
+
threads -> Hash
|
1288
|
+
|
1289
|
+
Returns a hash table keyed on thread ID. For each thread id,
|
1290
|
+
the hash table stores another hash table that contains profiling
|
1291
|
+
information for each method called during the threads execution.
|
1292
|
+
That hash table is keyed on method name and contains
|
1293
|
+
RubyProf::MethodInfo objects. */
|
1294
|
+
static VALUE
|
1295
|
+
prof_result_threads(VALUE self)
|
1296
|
+
{
|
1297
|
+
prof_result_t *prof_result = get_prof_result(self);
|
1298
|
+
return prof_result->threads;
|
1299
|
+
}
|
1300
|
+
|
1301
|
+
|
1302
|
+
|
1303
|
+
/* call-seq:
|
1304
|
+
measure_mode -> measure_mode
|
1305
|
+
|
1306
|
+
Returns what ruby-prof is measuring. Valid values include:
|
1307
|
+
|
1308
|
+
*RubyProf::PROCESS_TIME - Measure process time. This is default. It is implemented using the clock functions in the C Runtime library.
|
1309
|
+
*RubyProf::WALL_TIME - Measure wall time using gettimeofday on Linx and GetLocalTime on Windows
|
1310
|
+
*RubyProf::CPU_TIME - Measure time using the CPU clock counter. This mode is only supported on Pentium or PowerPC platforms.
|
1311
|
+
*RubyProf::ALLOCATIONS - Measure object allocations. This requires a patched Ruby interpreter.
|
1312
|
+
*RubyProf::MEMORY - Measure memory size. This requires a patched Ruby interpreter.
|
1313
|
+
*RubyProf::GC_RUNS - Measure number of garbage collections. This requires a patched Ruby interpreter.
|
1314
|
+
*RubyProf::GC_TIME - Measure time spent doing garbage collection. This requires a patched Ruby interpreter.*/
|
1315
|
+
static VALUE
|
1316
|
+
prof_get_measure_mode(VALUE self)
|
1317
|
+
{
|
1318
|
+
return INT2NUM(measure_mode);
|
1319
|
+
}
|
1320
|
+
|
1321
|
+
/* call-seq:
|
1322
|
+
measure_mode=value -> void
|
1323
|
+
|
1324
|
+
Specifies what ruby-prof should measure. Valid values include:
|
1325
|
+
|
1326
|
+
*RubyProf::PROCESS_TIME - Measure process time. This is default. It is implemented using the clock functions in the C Runtime library.
|
1327
|
+
*RubyProf::WALL_TIME - Measure wall time using gettimeofday on Linx and GetLocalTime on Windows
|
1328
|
+
*RubyProf::CPU_TIME - Measure time using the CPU clock counter. This mode is only supported on Pentium or PowerPC platforms.
|
1329
|
+
*RubyProf::ALLOCATIONS - Measure object allocations. This requires a patched Ruby interpreter.
|
1330
|
+
*RubyProf::MEMORY - Measure memory size. This requires a patched Ruby interpreter.
|
1331
|
+
*RubyProf::GC_RUNS - Measure number of garbage collections. This requires a patched Ruby interpreter.
|
1332
|
+
*RubyProf::GC_TIME - Measure time spent doing garbage collection. This requires a patched Ruby interpreter.*/
|
1333
|
+
static VALUE
|
1334
|
+
prof_set_measure_mode(VALUE self, VALUE val)
|
1335
|
+
{
|
1336
|
+
long mode = NUM2LONG(val);
|
1337
|
+
|
1338
|
+
if (threads_tbl)
|
1339
|
+
{
|
1340
|
+
rb_raise(rb_eRuntimeError, "can't set measure_mode while profiling");
|
1341
|
+
}
|
1342
|
+
|
1343
|
+
switch (mode) {
|
1344
|
+
case MEASURE_PROCESS_TIME:
|
1345
|
+
get_measurement = measure_process_time;
|
1346
|
+
convert_measurement = convert_process_time;
|
1347
|
+
break;
|
1348
|
+
|
1349
|
+
case MEASURE_WALL_TIME:
|
1350
|
+
get_measurement = measure_wall_time;
|
1351
|
+
convert_measurement = convert_wall_time;
|
1352
|
+
break;
|
1353
|
+
|
1354
|
+
#if defined(MEASURE_CPU_TIME)
|
1355
|
+
case MEASURE_CPU_TIME:
|
1356
|
+
if (cpu_frequency == 0)
|
1357
|
+
cpu_frequency = get_cpu_frequency();
|
1358
|
+
get_measurement = measure_cpu_time;
|
1359
|
+
convert_measurement = convert_cpu_time;
|
1360
|
+
break;
|
1361
|
+
#endif
|
1362
|
+
|
1363
|
+
#if defined(MEASURE_ALLOCATIONS)
|
1364
|
+
case MEASURE_ALLOCATIONS:
|
1365
|
+
get_measurement = measure_allocations;
|
1366
|
+
convert_measurement = convert_allocations;
|
1367
|
+
break;
|
1368
|
+
#endif
|
1369
|
+
|
1370
|
+
#if defined(MEASURE_MEMORY)
|
1371
|
+
case MEASURE_MEMORY:
|
1372
|
+
get_measurement = measure_memory;
|
1373
|
+
convert_measurement = convert_memory;
|
1374
|
+
break;
|
1375
|
+
#endif
|
1376
|
+
|
1377
|
+
#if defined(MEASURE_GC_RUNS)
|
1378
|
+
case MEASURE_GC_RUNS:
|
1379
|
+
get_measurement = measure_gc_runs;
|
1380
|
+
convert_measurement = convert_gc_runs;
|
1381
|
+
break;
|
1382
|
+
#endif
|
1383
|
+
|
1384
|
+
#if defined(MEASURE_GC_TIME)
|
1385
|
+
case MEASURE_GC_TIME:
|
1386
|
+
get_measurement = measure_gc_time;
|
1387
|
+
convert_measurement = convert_gc_time;
|
1388
|
+
break;
|
1389
|
+
#endif
|
1390
|
+
|
1391
|
+
default:
|
1392
|
+
rb_raise(rb_eArgError, "invalid mode: %ld", mode);
|
1393
|
+
break;
|
1394
|
+
}
|
1395
|
+
|
1396
|
+
measure_mode = mode;
|
1397
|
+
return val;
|
1398
|
+
}
|
1399
|
+
|
1400
|
+
/* call-seq:
|
1401
|
+
exclude_threads= -> void
|
1402
|
+
|
1403
|
+
Specifies what threads ruby-prof should exclude from profiling */
|
1404
|
+
static VALUE
|
1405
|
+
prof_set_exclude_threads(VALUE self, VALUE threads)
|
1406
|
+
{
|
1407
|
+
int i;
|
1408
|
+
|
1409
|
+
if (threads_tbl != NULL)
|
1410
|
+
{
|
1411
|
+
rb_raise(rb_eRuntimeError, "can't set exclude_threads while profiling");
|
1412
|
+
}
|
1413
|
+
|
1414
|
+
/* Stay simple, first free the old hash table */
|
1415
|
+
if (exclude_threads_tbl)
|
1416
|
+
{
|
1417
|
+
st_free_table(exclude_threads_tbl);
|
1418
|
+
exclude_threads_tbl = NULL;
|
1419
|
+
}
|
1420
|
+
|
1421
|
+
/* Now create a new one if the user passed in any threads */
|
1422
|
+
if (threads != Qnil)
|
1423
|
+
{
|
1424
|
+
Check_Type(threads, T_ARRAY);
|
1425
|
+
exclude_threads_tbl = st_init_numtable();
|
1426
|
+
|
1427
|
+
for (i=0; i < RARRAY_LEN(threads); ++i)
|
1428
|
+
{
|
1429
|
+
VALUE thread = rb_ary_entry(threads, i);
|
1430
|
+
st_insert(exclude_threads_tbl, (st_data_t) rb_obj_id(thread), 0);
|
1431
|
+
}
|
1432
|
+
}
|
1433
|
+
return threads;
|
1434
|
+
}
|
1435
|
+
|
1436
|
+
|
1437
|
+
/* ========= Profiling ============= */
|
1438
|
+
void
|
1439
|
+
prof_install_hook()
|
1440
|
+
{
|
1441
|
+
#ifdef RUBY_VM
|
1442
|
+
rb_add_event_hook(prof_event_hook,
|
1443
|
+
RUBY_EVENT_CALL | RUBY_EVENT_RETURN |
|
1444
|
+
RUBY_EVENT_C_CALL | RUBY_EVENT_C_RETURN
|
1445
|
+
| RUBY_EVENT_LINE, Qnil); // RUBY_EVENT_SWITCH
|
1446
|
+
#else
|
1447
|
+
rb_add_event_hook(prof_event_hook,
|
1448
|
+
RUBY_EVENT_CALL | RUBY_EVENT_RETURN |
|
1449
|
+
RUBY_EVENT_C_CALL | RUBY_EVENT_C_RETURN
|
1450
|
+
| RUBY_EVENT_LINE);
|
1451
|
+
#endif
|
1452
|
+
|
1453
|
+
#if defined(TOGGLE_GC_STATS)
|
1454
|
+
rb_gc_enable_stats();
|
1455
|
+
#endif
|
1456
|
+
}
|
1457
|
+
|
1458
|
+
void
|
1459
|
+
prof_remove_hook()
|
1460
|
+
{
|
1461
|
+
#if defined(TOGGLE_GC_STATS)
|
1462
|
+
rb_gc_disable_stats();
|
1463
|
+
#endif
|
1464
|
+
|
1465
|
+
/* Now unregister from event */
|
1466
|
+
rb_remove_event_hook(prof_event_hook);
|
1467
|
+
}
|
1468
|
+
|
1469
|
+
|
1470
|
+
|
1471
|
+
/* call-seq:
|
1472
|
+
running? -> boolean
|
1473
|
+
|
1474
|
+
Returns whether a profile is currently running.*/
|
1475
|
+
static VALUE
|
1476
|
+
prof_running(VALUE self)
|
1477
|
+
{
|
1478
|
+
if (threads_tbl != NULL)
|
1479
|
+
return Qtrue;
|
1480
|
+
else
|
1481
|
+
return Qfalse;
|
1482
|
+
}
|
1483
|
+
|
1484
|
+
/* call-seq:
|
1485
|
+
start -> RubyProf
|
1486
|
+
|
1487
|
+
Starts recording profile data.*/
|
1488
|
+
static VALUE
|
1489
|
+
prof_start(VALUE self)
|
1490
|
+
{
|
1491
|
+
if (threads_tbl != NULL)
|
1492
|
+
{
|
1493
|
+
rb_raise(rb_eRuntimeError, "RubyProf.start was already called");
|
1494
|
+
}
|
1495
|
+
|
1496
|
+
/* Setup globals */
|
1497
|
+
last_thread_data = NULL;
|
1498
|
+
threads_tbl = threads_table_create();
|
1499
|
+
|
1500
|
+
prof_install_hook();
|
1501
|
+
return self;
|
1502
|
+
}
|
1503
|
+
|
1504
|
+
/* call-seq:
|
1505
|
+
pause -> RubyProf
|
1506
|
+
|
1507
|
+
Pauses collecting profile data. */
|
1508
|
+
static VALUE
|
1509
|
+
prof_pause(VALUE self)
|
1510
|
+
{
|
1511
|
+
if (threads_tbl == NULL)
|
1512
|
+
{
|
1513
|
+
rb_raise(rb_eRuntimeError, "RubyProf is not running.");
|
1514
|
+
}
|
1515
|
+
|
1516
|
+
prof_remove_hook();
|
1517
|
+
return self;
|
1518
|
+
}
|
1519
|
+
|
1520
|
+
/* call-seq:
|
1521
|
+
resume {block} -> RubyProf
|
1522
|
+
|
1523
|
+
Resumes recording profile data.*/
|
1524
|
+
static VALUE
|
1525
|
+
prof_resume(VALUE self)
|
1526
|
+
{
|
1527
|
+
if (threads_tbl == NULL)
|
1528
|
+
{
|
1529
|
+
prof_start(self);
|
1530
|
+
}
|
1531
|
+
else
|
1532
|
+
{
|
1533
|
+
prof_install_hook();
|
1534
|
+
}
|
1535
|
+
|
1536
|
+
if (rb_block_given_p())
|
1537
|
+
{
|
1538
|
+
rb_ensure(rb_yield, self, prof_pause, self);
|
1539
|
+
}
|
1540
|
+
|
1541
|
+
return self;
|
1542
|
+
}
|
1543
|
+
|
1544
|
+
/* call-seq:
|
1545
|
+
stop -> RubyProf::Result
|
1546
|
+
|
1547
|
+
Stops collecting profile data and returns a RubyProf::Result object. */
|
1548
|
+
static VALUE
|
1549
|
+
prof_stop(VALUE self)
|
1550
|
+
{
|
1551
|
+
VALUE result = Qnil;
|
1552
|
+
|
1553
|
+
prof_remove_hook();
|
1554
|
+
|
1555
|
+
prof_pop_threads();
|
1556
|
+
|
1557
|
+
/* Create the result */
|
1558
|
+
result = prof_result_new();
|
1559
|
+
|
1560
|
+
/* Unset the last_thread_data (very important!)
|
1561
|
+
and the threads table */
|
1562
|
+
last_thread_data = NULL;
|
1563
|
+
threads_table_free(threads_tbl);
|
1564
|
+
threads_tbl = NULL;
|
1565
|
+
|
1566
|
+
return result;
|
1567
|
+
}
|
1568
|
+
|
1569
|
+
/* call-seq:
|
1570
|
+
profile {block} -> RubyProf::Result
|
1571
|
+
|
1572
|
+
Profiles the specified block and returns a RubyProf::Result object. */
|
1573
|
+
static VALUE
|
1574
|
+
prof_profile(VALUE self)
|
1575
|
+
{
|
1576
|
+
int result;
|
1577
|
+
|
1578
|
+
if (!rb_block_given_p())
|
1579
|
+
{
|
1580
|
+
rb_raise(rb_eArgError, "A block must be provided to the profile method.");
|
1581
|
+
}
|
1582
|
+
|
1583
|
+
prof_start(self);
|
1584
|
+
rb_protect(rb_yield, self, &result);
|
1585
|
+
return prof_stop(self);
|
1586
|
+
}
|
1587
|
+
|
1588
|
+
/* Get arround annoying limitations in RDOC */
|
1589
|
+
|
1590
|
+
/* Document-method: measure_process_time
|
1591
|
+
call-seq:
|
1592
|
+
measure_process_time -> float
|
1593
|
+
|
1594
|
+
Returns the process time.*/
|
1595
|
+
|
1596
|
+
/* Document-method: measure_wall_time
|
1597
|
+
call-seq:
|
1598
|
+
measure_wall_time -> float
|
1599
|
+
|
1600
|
+
Returns the wall time.*/
|
1601
|
+
|
1602
|
+
/* Document-method: measure_cpu_time
|
1603
|
+
call-seq:
|
1604
|
+
measure_cpu_time -> float
|
1605
|
+
|
1606
|
+
Returns the cpu time.*/
|
1607
|
+
|
1608
|
+
/* Document-method: get_cpu_frequency
|
1609
|
+
call-seq:
|
1610
|
+
cpu_frequency -> int
|
1611
|
+
|
1612
|
+
Returns the cpu's frequency. This value is needed when
|
1613
|
+
RubyProf::measure_mode is set to CPU_TIME. */
|
1614
|
+
|
1615
|
+
/* Document-method: cpu_frequency
|
1616
|
+
call-seq:
|
1617
|
+
cpu_frequency -> int
|
1618
|
+
|
1619
|
+
Returns the cpu's frequency. This value is needed when
|
1620
|
+
RubyProf::measure_mode is set to CPU_TIME. */
|
1621
|
+
|
1622
|
+
/* Document-method: cpu_frequency=
|
1623
|
+
call-seq:
|
1624
|
+
cpu_frequency = frequency
|
1625
|
+
|
1626
|
+
Sets the cpu's frequency. This value is needed when
|
1627
|
+
RubyProf::measure_mode is set to CPU_TIME. */
|
1628
|
+
|
1629
|
+
/* Document-method: measure_allocations
|
1630
|
+
call-seq:
|
1631
|
+
measure_allocations -> int
|
1632
|
+
|
1633
|
+
Returns the total number of object allocations since Ruby started.*/
|
1634
|
+
|
1635
|
+
/* Document-method: measure_memory
|
1636
|
+
call-seq:
|
1637
|
+
measure_memory -> int
|
1638
|
+
|
1639
|
+
Returns total allocated memory in bytes.*/
|
1640
|
+
|
1641
|
+
/* Document-method: measure_gc_runs
|
1642
|
+
call-seq:
|
1643
|
+
gc_runs -> Integer
|
1644
|
+
|
1645
|
+
Returns the total number of garbage collections.*/
|
1646
|
+
|
1647
|
+
/* Document-method: measure_gc_time
|
1648
|
+
call-seq:
|
1649
|
+
gc_time -> Integer
|
1650
|
+
|
1651
|
+
Returns the time spent doing garbage collections in microseconds.*/
|
1652
|
+
|
1653
|
+
|
1654
|
+
#if defined(_WIN32)
|
1655
|
+
__declspec(dllexport)
|
1656
|
+
#endif
|
1657
|
+
void
|
1658
|
+
|
1659
|
+
Init_ruby_prof()
|
1660
|
+
{
|
1661
|
+
mProf = rb_define_module("RubyProf");
|
1662
|
+
rb_define_const(mProf, "VERSION", rb_str_new2(RUBY_PROF_VERSION));
|
1663
|
+
rb_define_module_function(mProf, "start", prof_start, 0);
|
1664
|
+
rb_define_module_function(mProf, "stop", prof_stop, 0);
|
1665
|
+
rb_define_module_function(mProf, "resume", prof_resume, 0);
|
1666
|
+
rb_define_module_function(mProf, "pause", prof_pause, 0);
|
1667
|
+
rb_define_module_function(mProf, "running?", prof_running, 0);
|
1668
|
+
rb_define_module_function(mProf, "profile", prof_profile, 0);
|
1669
|
+
|
1670
|
+
rb_define_singleton_method(mProf, "exclude_threads=", prof_set_exclude_threads, 1);
|
1671
|
+
rb_define_singleton_method(mProf, "measure_mode", prof_get_measure_mode, 0);
|
1672
|
+
rb_define_singleton_method(mProf, "measure_mode=", prof_set_measure_mode, 1);
|
1673
|
+
|
1674
|
+
rb_define_const(mProf, "CLOCKS_PER_SEC", INT2NUM(CLOCKS_PER_SEC));
|
1675
|
+
rb_define_const(mProf, "PROCESS_TIME", INT2NUM(MEASURE_PROCESS_TIME));
|
1676
|
+
rb_define_singleton_method(mProf, "measure_process_time", prof_measure_process_time, 0); /* in measure_process_time.h */
|
1677
|
+
rb_define_const(mProf, "WALL_TIME", INT2NUM(MEASURE_WALL_TIME));
|
1678
|
+
rb_define_singleton_method(mProf, "measure_wall_time", prof_measure_wall_time, 0); /* in measure_wall_time.h */
|
1679
|
+
|
1680
|
+
#ifndef MEASURE_CPU_TIME
|
1681
|
+
rb_define_const(mProf, "CPU_TIME", Qnil);
|
1682
|
+
#else
|
1683
|
+
rb_define_const(mProf, "CPU_TIME", INT2NUM(MEASURE_CPU_TIME));
|
1684
|
+
rb_define_singleton_method(mProf, "measure_cpu_time", prof_measure_cpu_time, 0); /* in measure_cpu_time.h */
|
1685
|
+
rb_define_singleton_method(mProf, "cpu_frequency", prof_get_cpu_frequency, 0); /* in measure_cpu_time.h */
|
1686
|
+
rb_define_singleton_method(mProf, "cpu_frequency=", prof_set_cpu_frequency, 1); /* in measure_cpu_time.h */
|
1687
|
+
#endif
|
1688
|
+
|
1689
|
+
#ifndef MEASURE_ALLOCATIONS
|
1690
|
+
rb_define_const(mProf, "ALLOCATIONS", Qnil);
|
1691
|
+
#else
|
1692
|
+
rb_define_const(mProf, "ALLOCATIONS", INT2NUM(MEASURE_ALLOCATIONS));
|
1693
|
+
rb_define_singleton_method(mProf, "measure_allocations", prof_measure_allocations, 0); /* in measure_allocations.h */
|
1694
|
+
#endif
|
1695
|
+
|
1696
|
+
#ifndef MEASURE_MEMORY
|
1697
|
+
rb_define_const(mProf, "MEMORY", Qnil);
|
1698
|
+
#else
|
1699
|
+
rb_define_const(mProf, "MEMORY", INT2NUM(MEASURE_MEMORY));
|
1700
|
+
rb_define_singleton_method(mProf, "measure_memory", prof_measure_memory, 0); /* in measure_memory.h */
|
1701
|
+
#endif
|
1702
|
+
|
1703
|
+
#ifndef MEASURE_GC_RUNS
|
1704
|
+
rb_define_const(mProf, "GC_RUNS", Qnil);
|
1705
|
+
#else
|
1706
|
+
rb_define_const(mProf, "GC_RUNS", INT2NUM(MEASURE_GC_RUNS));
|
1707
|
+
rb_define_singleton_method(mProf, "measure_gc_runs", prof_measure_gc_runs, 0); /* in measure_gc_runs.h */
|
1708
|
+
#endif
|
1709
|
+
|
1710
|
+
#ifndef MEASURE_GC_TIME
|
1711
|
+
rb_define_const(mProf, "GC_TIME", Qnil);
|
1712
|
+
#else
|
1713
|
+
rb_define_const(mProf, "GC_TIME", INT2NUM(MEASURE_GC_TIME));
|
1714
|
+
rb_define_singleton_method(mProf, "measure_gc_time", prof_measure_gc_time, 0); /* in measure_gc_time.h */
|
1715
|
+
#endif
|
1716
|
+
|
1717
|
+
cResult = rb_define_class_under(mProf, "Result", rb_cObject);
|
1718
|
+
rb_undef_method(CLASS_OF(cMethodInfo), "new");
|
1719
|
+
rb_define_method(cResult, "threads", prof_result_threads, 0);
|
1720
|
+
|
1721
|
+
/* MethodInfo */
|
1722
|
+
cMethodInfo = rb_define_class_under(mProf, "MethodInfo", rb_cObject);
|
1723
|
+
rb_undef_method(CLASS_OF(cMethodInfo), "new");
|
1724
|
+
|
1725
|
+
rb_define_method(cMethodInfo, "klass", prof_method_klass, 0);
|
1726
|
+
rb_define_method(cMethodInfo, "klass_name", prof_klass_name, 0);
|
1727
|
+
rb_define_method(cMethodInfo, "method_name", prof_method_name, 0);
|
1728
|
+
rb_define_method(cMethodInfo, "full_name", prof_full_name, 0);
|
1729
|
+
rb_define_method(cMethodInfo, "method_id", prof_method_id, 0);
|
1730
|
+
|
1731
|
+
rb_define_method(cMethodInfo, "source_file", prof_method_source_file,0);
|
1732
|
+
rb_define_method(cMethodInfo, "line", prof_method_line, 0);
|
1733
|
+
|
1734
|
+
rb_define_method(cMethodInfo, "call_infos", prof_method_call_infos, 0);
|
1735
|
+
|
1736
|
+
/* CallInfo */
|
1737
|
+
cCallInfo = rb_define_class_under(mProf, "CallInfo", rb_cObject);
|
1738
|
+
rb_undef_method(CLASS_OF(cCallInfo), "new");
|
1739
|
+
rb_define_method(cCallInfo, "parent", prof_call_info_parent, 0);
|
1740
|
+
rb_define_method(cCallInfo, "children", prof_call_info_children, 0);
|
1741
|
+
rb_define_method(cCallInfo, "target", prof_call_info_target, 0);
|
1742
|
+
rb_define_method(cCallInfo, "called", prof_call_info_called, 0);
|
1743
|
+
rb_define_method(cCallInfo, "total_time", prof_call_info_total_time, 0);
|
1744
|
+
rb_define_method(cCallInfo, "self_time", prof_call_info_self_time, 0);
|
1745
|
+
rb_define_method(cCallInfo, "wait_time", prof_call_info_wait_time, 0);
|
1746
|
+
rb_define_method(cCallInfo, "line", prof_call_info_line, 0);
|
1747
|
+
}
|