ruby-prof 2.0.3 → 2.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +615 -607
- data/bin/ruby-prof +336 -336
- data/lib/ruby-prof/printers/abstract_printer.rb +143 -143
- data/lib/ruby-prof/printers/flame_graph_printer.rb +80 -78
- data/lib/ruby-prof/version.rb +3 -3
- metadata +4 -85
- data/Rakefile +0 -98
- data/docs/advanced-usage.md +0 -132
- data/docs/alternatives.md +0 -98
- data/docs/architecture.md +0 -304
- data/docs/best-practices.md +0 -27
- data/docs/getting-started.md +0 -130
- data/docs/history.md +0 -11
- data/docs/index.md +0 -45
- data/docs/profiling-rails.md +0 -64
- data/docs/public/examples/example.rb +0 -33
- data/docs/public/examples/generate_reports.rb +0 -92
- data/docs/public/examples/reports/call_info.txt +0 -27
- data/docs/public/examples/reports/call_stack.html +0 -835
- data/docs/public/examples/reports/callgrind.out +0 -150
- data/docs/public/examples/reports/flame_graph.html +0 -408
- data/docs/public/examples/reports/flat.txt +0 -45
- data/docs/public/examples/reports/graph.dot +0 -129
- data/docs/public/examples/reports/graph.html +0 -1319
- data/docs/public/examples/reports/graph.txt +0 -100
- data/docs/public/examples/reports/graphviz_viewer.html +0 -1
- data/docs/public/images/call_stack.png +0 -0
- data/docs/public/images/class_diagram.png +0 -0
- data/docs/public/images/dot_printer.png +0 -0
- data/docs/public/images/flame_graph.png +0 -0
- data/docs/public/images/flat.png +0 -0
- data/docs/public/images/graph.png +0 -0
- data/docs/public/images/graph_html.png +0 -0
- data/docs/public/images/ruby-prof-logo.svg +0 -1
- data/docs/reports.md +0 -151
- data/docs/stylesheets/extra.css +0 -80
- data/ruby-prof.gemspec +0 -66
- data/test/abstract_printer_test.rb +0 -25
- data/test/alias_test.rb +0 -203
- data/test/call_tree_builder.rb +0 -126
- data/test/call_tree_test.rb +0 -94
- data/test/call_tree_visitor_test.rb +0 -27
- data/test/call_trees_test.rb +0 -66
- data/test/duplicate_names_test.rb +0 -32
- data/test/dynamic_method_test.rb +0 -50
- data/test/enumerable_test.rb +0 -23
- data/test/exceptions_test.rb +0 -24
- data/test/exclude_methods_test.rb +0 -363
- data/test/exclude_threads_test.rb +0 -48
- data/test/fiber_test.rb +0 -195
- data/test/gc_test.rb +0 -104
- data/test/inverse_call_tree_test.rb +0 -174
- data/test/line_number_test.rb +0 -563
- data/test/marshal_test.rb +0 -144
- data/test/measure_allocations.rb +0 -26
- data/test/measure_allocations_test.rb +0 -1511
- data/test/measure_process_time_test.rb +0 -3286
- data/test/measure_times.rb +0 -56
- data/test/measure_wall_time_test.rb +0 -774
- data/test/measurement_test.rb +0 -82
- data/test/merge_test.rb +0 -146
- data/test/method_info_test.rb +0 -100
- data/test/multi_printer_test.rb +0 -52
- data/test/no_method_class_test.rb +0 -15
- data/test/pause_resume_test.rb +0 -171
- data/test/prime.rb +0 -54
- data/test/prime_script.rb +0 -6
- data/test/printer_call_stack_test.rb +0 -28
- data/test/printer_call_tree_test.rb +0 -30
- data/test/printer_flame_graph_test.rb +0 -82
- data/test/printer_flat_test.rb +0 -99
- data/test/printer_graph_html_test.rb +0 -62
- data/test/printer_graph_test.rb +0 -42
- data/test/printers_test.rb +0 -162
- data/test/printing_recursive_graph_test.rb +0 -81
- data/test/profile_test.rb +0 -101
- data/test/rack_test.rb +0 -103
- data/test/recursive_test.rb +0 -796
- data/test/scheduler.rb +0 -367
- data/test/singleton_test.rb +0 -39
- data/test/stack_printer_test.rb +0 -61
- data/test/start_stop_test.rb +0 -106
- data/test/test_helper.rb +0 -24
- data/test/thread_test.rb +0 -229
- data/test/unique_call_path_test.rb +0 -123
- data/test/yarv_test.rb +0 -56
data/docs/advanced-usage.md
DELETED
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
# Advanced Usage
|
|
2
|
-
|
|
3
|
-
This section describes advanced usage of ruby-prof. Additional documentation for every class is also [available](index.md#api-documentation). For workflow guidance, see [Best Practices](best-practices.md).
|
|
4
|
-
|
|
5
|
-
## Profiling Options
|
|
6
|
-
|
|
7
|
-
ruby-prof understands the following options when profiling code:
|
|
8
|
-
|
|
9
|
-
**measure_mode** - What ruby-prof should measure. For more information see the [Measurement Mode](#measurement-mode) section.
|
|
10
|
-
|
|
11
|
-
**track_allocations** - Tracks each object location, including the object class and source file location. For more information see the [Allocation Tracking](#allocation-tracking) section.
|
|
12
|
-
|
|
13
|
-
**exclude_threads** - Array of threads which should not be profiled. For more information see the [Thread Inclusion/Exclusion](#thread-inclusionexclusion) section.
|
|
14
|
-
|
|
15
|
-
**include_threads** - Array of threads which should be profiled. All other threads will be ignored. For more information see the [Thread Inclusion/Exclusion](#thread-inclusionexclusion) section.
|
|
16
|
-
|
|
17
|
-
**allow_exceptions** - Whether to raise exceptions encountered during profiling, or to suppress them. Defaults to false.
|
|
18
|
-
|
|
19
|
-
**exclude_common** - Automatically calls `exclude_common_methods!` to exclude commonly cluttering methods. Defaults to false. For more information see the [Method Exclusion](#method-exclusion) section.
|
|
20
|
-
|
|
21
|
-
## Measurement Mode
|
|
22
|
-
|
|
23
|
-
The measurement mode determines what ruby-prof measures when profiling code. Supported measurements are:
|
|
24
|
-
|
|
25
|
-
### Wall Time
|
|
26
|
-
|
|
27
|
-
Wall time measures the real-world time elapsed between any two moments in seconds. If there are other processes concurrently running on the system that use significant CPU or disk time during a profiling run then the reported results will be larger than expected. On Windows, wall time is measured using `QueryPerformanceCounter` and on other platforms by `clock_gettime(CLOCK_MONOTONIC)`. Use `RubyProf::WALL_TIME` to select this mode.
|
|
28
|
-
|
|
29
|
-
### Process Time
|
|
30
|
-
|
|
31
|
-
Process time measures the time used by a process between any two moments in seconds. It is unaffected by other processes concurrently running on the system. Remember with process time that calls to methods like sleep will not be included in profiling results. On Windows, process time is measured using `GetProcessTimes` and on other platforms by `clock_gettime`. Use `RubyProf::PROCESS_TIME` to select this mode.
|
|
32
|
-
|
|
33
|
-
### Object Allocations
|
|
34
|
-
|
|
35
|
-
Object allocations measures how many objects each method in a program allocates. Measurements are done via Ruby's `RUBY_INTERNAL_EVENT_NEWOBJ` trace event, counting each new object created (excluding internal `T_IMEMO` objects). Use `RubyProf::ALLOCATIONS` to select this mode.
|
|
36
|
-
|
|
37
|
-
To set the measurement mode:
|
|
38
|
-
|
|
39
|
-
```ruby
|
|
40
|
-
profile = RubyProf::Profile.new(measure_mode: RubyProf::WALL_TIME)
|
|
41
|
-
profile = RubyProf::Profile.new(measure_mode: RubyProf::PROCESS_TIME)
|
|
42
|
-
profile = RubyProf::Profile.new(measure_mode: RubyProf::ALLOCATIONS)
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
The default value is `RubyProf::WALL_TIME`. You may also specify the measure mode by using the `RUBY_PROF_MEASURE_MODE` environment variable:
|
|
46
|
-
|
|
47
|
-
```
|
|
48
|
-
export RUBY_PROF_MEASURE_MODE=wall
|
|
49
|
-
export RUBY_PROF_MEASURE_MODE=process
|
|
50
|
-
export RUBY_PROF_MEASURE_MODE=allocations
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
## Allocation Tracking
|
|
54
|
-
|
|
55
|
-
ruby-prof also has the ability to track object allocations. This functionality can be turned on via the track_allocations option:
|
|
56
|
-
|
|
57
|
-
```ruby
|
|
58
|
-
require 'ruby-prof'
|
|
59
|
-
|
|
60
|
-
RubyProf::Profile.profile(track_allocations: true) do
|
|
61
|
-
...
|
|
62
|
-
end
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
Note the `RubyProf::ALLOCATIONS` measure mode is slightly different than tracking allocations. The measurement mode provides high level information about the number of allocations performed in each method. In contrast, tracking allocations provides detailed information about allocation type, count, and source location. Currently, to see allocations results you must use the `RubyProf::GraphHtmlPrinter`.
|
|
66
|
-
|
|
67
|
-
## Thread Inclusion/Exclusion
|
|
68
|
-
|
|
69
|
-
ruby-prof can profile multiple threads. Sometimes this can be overwhelming. For example, assume you want to determine why your tests are running slowly. If you are using minitest, it can run tests in parallel by spawning worker threads (to force a single worker, set `N=0` when running tests). Thus, ruby-prof provides two options to specify which threads should be profiled:
|
|
70
|
-
|
|
71
|
-
**exclude_threads** - Array of threads which should not be profiled.
|
|
72
|
-
|
|
73
|
-
**include_threads** - Array of threads which should be profiled. All other threads will be ignored.
|
|
74
|
-
|
|
75
|
-
## Method Exclusion
|
|
76
|
-
|
|
77
|
-
ruby-prof supports excluding specific methods and threads from profiling results. This is useful for reducing connectivity in the call graph, making it easier to identify the source of performance problems when using a graph printer. For example, consider `Integer#times`: it's hardly ever useful to know how much time is spent in the method itself. We are more interested in how much the passed in block contributes to the time spent in the method which contains the `Integer#times` call. The effect on collected metrics are identical to eliminating methods from the profiling result in a post process step.
|
|
78
|
-
|
|
79
|
-
```ruby
|
|
80
|
-
profile = RubyProf::Profile.new(...)
|
|
81
|
-
profile.exclude_methods!(Integer, :times, ...)
|
|
82
|
-
profile.start
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
A convenience method is provided to exclude a large number of methods which usually clutter up profiles:
|
|
86
|
-
|
|
87
|
-
```ruby
|
|
88
|
-
profile.exclude_common_methods!
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
However, this is a somewhat opinionated method collection. It's usually better to view it as an inspiration instead of using it directly (see [exclude_common_methods.rb](https://github.com/ruby-prof/ruby-prof/blob/e087b7d7ca11eecf1717d95a5c5fea1e36ea3136/lib/ruby-prof/profile/exclude_common_methods.rb)).
|
|
92
|
-
|
|
93
|
-
## Merging Threads and Fibers
|
|
94
|
-
|
|
95
|
-
ruby-prof profiles each thread and fiber separately. A common design pattern is to have a main thread delegate work to background threads or fibers. Examples include web servers such as Puma and Falcon, as well as code that uses `Enumerator`, `Fiber.new`, or async libraries.
|
|
96
|
-
|
|
97
|
-
Understanding profiling results can be very difficult when there are many threads or fibers because each one appears as a separate entry in the output. To help with this, ruby-prof includes the ability to merge results for threads and fibers that start with the same root method. In the best case, this can collapse results into just two entries - one for the parent thread and one for all workers.
|
|
98
|
-
|
|
99
|
-
Note the collapsed results show the sum of times for all merged threads/fibers. For example, assume there are 10 worker fibers that each took 5 seconds to run. The single merged entry will show a total time of 50 seconds.
|
|
100
|
-
|
|
101
|
-
To merge threads and fibers:
|
|
102
|
-
|
|
103
|
-
```ruby
|
|
104
|
-
profile = RubyProf::Profile.profile do
|
|
105
|
-
...
|
|
106
|
-
end
|
|
107
|
-
profile.merge!
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
This is also supported in the Rack adapter via the `merge_fibers` option:
|
|
111
|
-
|
|
112
|
-
```ruby
|
|
113
|
-
config.middleware.use Rack::RubyProf, path: Rails.root.join("tmp/profile"), merge_fibers: true
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
## Saving Results
|
|
117
|
-
|
|
118
|
-
It can be helpful to save the results of a profiling run for later analysis. Results can be saved using Ruby's [marshal](https://docs.ruby-lang.org/en/master/Marshal.html) library.
|
|
119
|
-
|
|
120
|
-
```ruby
|
|
121
|
-
profile_1 = RubyProf::Profile.profile do
|
|
122
|
-
...
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
# Save the results
|
|
126
|
-
data = Marshal.dump(profile_1)
|
|
127
|
-
|
|
128
|
-
# Sometime later load the results
|
|
129
|
-
profile_2 = Marshal.load(data)
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
**!!!WARNING!!!** - Only load ruby-prof profiles that you know are safe. Demarshaling data can lead to arbitrary code execution and thus can be [dangerous](https://docs.ruby-lang.org/en/master/Marshal.html#module-Marshal-label-Security+considerations).
|
data/docs/alternatives.md
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
# Comparison with Other Profilers
|
|
2
|
-
|
|
3
|
-
Ruby has several excellent profiling tools, each with different strengths. This page compares ruby-prof with three popular alternatives to help you choose the right tool for your needs.
|
|
4
|
-
|
|
5
|
-
## Tracing vs Sampling
|
|
6
|
-
|
|
7
|
-
The most important distinction between profilers is **tracing** vs **sampling**:
|
|
8
|
-
|
|
9
|
-
- **Tracing profilers** (ruby-prof) instrument every method call and return. This provides exact call counts and complete call graphs, but adds overhead to every method invocation.
|
|
10
|
-
- **Sampling profilers** (stackprof, rbspy, vernier) periodically capture stack snapshots. This has much lower overhead but may miss short-lived method calls.
|
|
11
|
-
|
|
12
|
-
## Overview
|
|
13
|
-
|
|
14
|
-
The table below compares ruby-prof with [stackprof](https://github.com/tmm1/stackprof), [rbspy](https://github.com/rbspy/rbspy), and [vernier](https://github.com/jhawthorn/vernier) — the three most popular sampling profilers for Ruby.
|
|
15
|
-
|
|
16
|
-
| | ruby-prof | stackprof | rbspy | vernier |
|
|
17
|
-
|---|---|---|---|---|
|
|
18
|
-
| **Type** | Tracing | Sampling | Sampling | Sampling |
|
|
19
|
-
| **Implementation** | C extension (TracePoint API) | C extension (signals) | External Rust binary | C extension (signals) |
|
|
20
|
-
| **Code changes** | None ([CLI](getting-started.md#command-line)) or minimal | Minimal | None | Minimal |
|
|
21
|
-
| **Ruby versions** | All, since 2006 (currently 3.2+) | 2.2+ | 1.9.3+ | 3.2.1+ |
|
|
22
|
-
| **OS support** | Linux, macOS, Windows | Linux | Linux, macOS, Windows, FreeBSD | Linux, macOS |
|
|
23
|
-
|
|
24
|
-
## Measurement Capabilities
|
|
25
|
-
|
|
26
|
-
| | ruby-prof | stackprof | rbspy | vernier |
|
|
27
|
-
|---|---|---|---|---|
|
|
28
|
-
| **Wall time** | Yes | Yes | Yes | Yes |
|
|
29
|
-
| **CPU/Process time** | Yes | Yes | No | No |
|
|
30
|
-
| **Allocations** | Yes | Yes | No | Yes |
|
|
31
|
-
| **GVL visibility** | No | No | No | Yes |
|
|
32
|
-
| **GC pauses** | No | No | No | Yes |
|
|
33
|
-
| **Retained memory** | No | No | No | Yes |
|
|
34
|
-
| **Multi-thread** | Yes | No | No | Yes |
|
|
35
|
-
| **Fibers** | Yes | No | No | No |
|
|
36
|
-
|
|
37
|
-
## Report Formats
|
|
38
|
-
|
|
39
|
-
| | ruby-prof | stackprof | rbspy | vernier |
|
|
40
|
-
|---|---|---|---|---|
|
|
41
|
-
| **Flat/Summary** | Yes | Yes | Yes | No |
|
|
42
|
-
| **Call graph** | Yes (text + HTML) | No | No | No |
|
|
43
|
-
| **Flame graph** | Yes (HTML) | Yes | Yes (SVG) | Yes (Firefox Profiler) |
|
|
44
|
-
| **Call stack** | Yes (HTML) | No | No | No |
|
|
45
|
-
| **Callgrind** | Yes | No | Yes | No |
|
|
46
|
-
| **Graphviz dot** | Yes | Yes | No | No |
|
|
47
|
-
|
|
48
|
-
## When to Use Each
|
|
49
|
-
|
|
50
|
-
### ruby-prof
|
|
51
|
-
|
|
52
|
-
ruby-prof is the longest-standing Ruby profiler, with its [first](./history.md) release in 2005. It has been continuously maintained for nearly two decades, evolving alongside Ruby itself from 1.8 through 4.0. Over that time it has supported every major Ruby version and platform, including Windows — a rarity among Ruby C extensions.
|
|
53
|
-
|
|
54
|
-
Being a tracing profiler, ruby-prof provides *exact* information about your program. It tracks every thread, every fiber and every method call. It shines with its support for multiple measurements modes and excellent reporting capabilities.
|
|
55
|
-
|
|
56
|
-
ruby-prof can be used from the [command line](getting-started.md#command-line) with no code changes, or via an API for more control.
|
|
57
|
-
|
|
58
|
-
The biggest downsides of ruby-prof are:
|
|
59
|
-
|
|
60
|
-
* It adds significant overhead for running programs, so is not suitable for production use
|
|
61
|
-
* It must start a Ruby program, it cannot attach to an already running program
|
|
62
|
-
|
|
63
|
-
### stackprof
|
|
64
|
-
|
|
65
|
-
[stackprof](https://github.com/tmm1/stackprof) is a low-overhead, sampling profiler that is good for development. It adds minimal overhead while still providing useful flame graphs and per-line hit counts. A good choice when you want something lightweight and well-established.
|
|
66
|
-
|
|
67
|
-
The biggest downsides of stackprof are:
|
|
68
|
-
|
|
69
|
-
* Single-thread only
|
|
70
|
-
* Linux only for time-based modes
|
|
71
|
-
|
|
72
|
-
### rbspy
|
|
73
|
-
|
|
74
|
-
[rbspy](https://github.com/rbspy/rbspy) is a sampling profiler best for profiling in production or when you cannot modify the application code. As an external process, it attaches to a running Ruby process by PID with zero code changes. It is particularly useful for profiling third-party Ruby applications (Chef, Puppet, etc.), investigating slow test runs, or quick profiling of scripts via `rbspy record ruby my-script.rb`. Supports the widest range of Ruby versions.
|
|
75
|
-
|
|
76
|
-
The biggest downsides of rbspy are:
|
|
77
|
-
|
|
78
|
-
* No allocation profiling
|
|
79
|
-
* No call graph or caller/callee data
|
|
80
|
-
|
|
81
|
-
### vernier
|
|
82
|
-
|
|
83
|
-
[vernier](https://github.com/jhawthorn/vernier) is a sampling profiler best for diagnosing concurrency issues and understanding GVL contention. It is the only Ruby profiler that reports GVL state, GC pauses and idle time. Its Firefox Profiler integration provides rich interactive visualizations with per-thread timelines.
|
|
84
|
-
|
|
85
|
-
The biggest downsides of vernier are:
|
|
86
|
-
|
|
87
|
-
* Requires Ruby 3.2.1+
|
|
88
|
-
* No Windows support
|
|
89
|
-
|
|
90
|
-
### rack-mini-profiler
|
|
91
|
-
|
|
92
|
-
[rack-mini-profiler](https://github.com/MiniProfiler/rack-mini-profiler) is a "batteries-included" profiling tool for Rails and Rack applications. It uses stackprof under the hood for CPU profiling while also supporting memory profiling. It is a good choice if you want an integrated profiling solution that works directly in the browser during development.
|
|
93
|
-
|
|
94
|
-
## Memory Profiling
|
|
95
|
-
|
|
96
|
-
[memory_profiler](https://github.com/SamSaffron/memory_profiler) is another profiler, but it focuses exclusively on memory usage. It uses Ruby's `ObjectSpace` API to track every object allocation during a block of code, recording the source file, line number, object type, and size via `ObjectSpace.memsize_of`. By snapshotting the GC generation before and after, it distinguishes between allocated objects (created during the block) and retained objects (still alive after GC). This makes it useful for finding memory leaks and identifying allocation-heavy code. It's pure Ruby with no C extension, so it works across Ruby versions and platforms.
|
|
97
|
-
|
|
98
|
-
ruby-prof can track allocation counts via its `RubyProf::ALLOCATIONS` mode, but memory_profiler gives deeper insight into memory specifically — object sizes, retained vs allocated, and per-gem breakdowns.
|
data/docs/architecture.md
DELETED
|
@@ -1,304 +0,0 @@
|
|
|
1
|
-
# Architecture
|
|
2
|
-
|
|
3
|
-
## Overview
|
|
4
|
-
|
|
5
|
-
ruby-prof is a C extension that uses Ruby's [TracePoint](https://docs.ruby-lang.org/en/master/TracePoint.html) API to intercept method calls and returns. Every time a method is entered or exited, ruby-prof records timing and (optionally) allocation data. This tracing approach means ruby-prof captures every method invocation, giving exact call counts and complete call graphs.
|
|
6
|
-
|
|
7
|
-
The diagram below shows the main classes that make up ruby-prof:
|
|
8
|
-
|
|
9
|
-
```mermaid
|
|
10
|
-
classDiagram
|
|
11
|
-
Profile "1" *-- "1" Measurer
|
|
12
|
-
Profile "1" *-- "*" Thread
|
|
13
|
-
Thread "1" *-- "1" Stack
|
|
14
|
-
Thread "1" *-- "*" MethodInfo
|
|
15
|
-
Thread "1" *-- "1" CallTree
|
|
16
|
-
Stack "1" o-- "*" Frame
|
|
17
|
-
Frame --> CallTree
|
|
18
|
-
CallTree "1" *-- "1" Measurement
|
|
19
|
-
CallTree --> MethodInfo : target
|
|
20
|
-
MethodInfo "1" *-- "1" CallTrees
|
|
21
|
-
MethodInfo "1" *-- "1" Measurement
|
|
22
|
-
MethodInfo "1" *-- "*" Allocation
|
|
23
|
-
CallTrees o-- "*" CallTree
|
|
24
|
-
|
|
25
|
-
class Profile {
|
|
26
|
-
+threads: Hash
|
|
27
|
-
+measurer: Measurer
|
|
28
|
-
}
|
|
29
|
-
class Measurer {
|
|
30
|
-
+mode: MeasurerMode
|
|
31
|
-
+track_allocations: boolean
|
|
32
|
-
+multiplier: double
|
|
33
|
-
+measure: function pointer
|
|
34
|
-
}
|
|
35
|
-
class Thread {
|
|
36
|
-
+methods: Hash
|
|
37
|
-
+stack: Stack
|
|
38
|
-
+callTree: CallTree
|
|
39
|
-
}
|
|
40
|
-
class Stack {
|
|
41
|
-
+frames: Array
|
|
42
|
-
}
|
|
43
|
-
class Frame {
|
|
44
|
-
+callTree: CallTree
|
|
45
|
-
}
|
|
46
|
-
class CallTree {
|
|
47
|
-
+parent: CallTree
|
|
48
|
-
+children: Hash
|
|
49
|
-
+target: MethodInfo
|
|
50
|
-
+measurement: Measurement
|
|
51
|
-
}
|
|
52
|
-
class MethodInfo {
|
|
53
|
-
+allocations: Hash
|
|
54
|
-
+callTrees: CallTrees
|
|
55
|
-
+measurement: Measurement
|
|
56
|
-
}
|
|
57
|
-
class Measurement {
|
|
58
|
-
+total_time: double
|
|
59
|
-
+self_time: double
|
|
60
|
-
+wait_time: double
|
|
61
|
-
+called: integer
|
|
62
|
-
}
|
|
63
|
-
class Allocation {
|
|
64
|
-
+count: integer
|
|
65
|
-
+source_file: string
|
|
66
|
-
+source_line: int
|
|
67
|
-
+klass: VALUE
|
|
68
|
-
}
|
|
69
|
-
class CallTrees {
|
|
70
|
-
+callTrees: Array
|
|
71
|
-
}
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
## Profile
|
|
75
|
-
|
|
76
|
-
Profile is the top-level object returned by a profiling run:
|
|
77
|
-
|
|
78
|
-
```ruby
|
|
79
|
-
profile = RubyProf::Profile.profile do
|
|
80
|
-
...
|
|
81
|
-
end
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
A Profile owns a Measurer that determines what is being measured, and a collection of Threads representing each thread (or fiber) that was active during profiling.
|
|
85
|
-
|
|
86
|
-
## Measurer and Measurement
|
|
87
|
-
|
|
88
|
-
The **Measurer** controls what ruby-prof measures. It holds a function pointer that is called on every method entry and exit to take a measurement. The three modes are:
|
|
89
|
-
|
|
90
|
-
- **Wall time** — elapsed real time
|
|
91
|
-
- **Process time** — CPU time consumed by the process (excludes time spent in sleep or I/O)
|
|
92
|
-
- **Allocations** — number of objects allocated
|
|
93
|
-
|
|
94
|
-
Each CallTree and MethodInfo holds a **Measurement** that accumulates the results: total time, self time (excluding children), wait time (time spent waiting on other threads), and call count.
|
|
95
|
-
|
|
96
|
-
## Thread
|
|
97
|
-
|
|
98
|
-
Each Thread tracks the methods called on that thread and owns the root of a call tree. It also maintains an internal Stack of Frames used during profiling to track the current call depth.
|
|
99
|
-
|
|
100
|
-
**Stack** and **Frame** are transient — they exist only while profiling is active. A Frame records timing data for a single method invocation on the stack, including start time and time spent in child calls. When a method returns, its Frame is popped and the accumulated timing is transferred to the corresponding CallTree node.
|
|
101
|
-
|
|
102
|
-
## CallTree and MethodInfo
|
|
103
|
-
|
|
104
|
-
These two classes are central to ruby-prof and represent two different views of the same profiling data:
|
|
105
|
-
|
|
106
|
-
- **CallTree** records the calling structure — which method called which, forming a tree. Each node has a parent, children, and a reference to its target MethodInfo. A method that is called from two different call sites will have two separate CallTree nodes, each with its own Measurement. Recursive methods are handled by creating a chain of CallTree nodes (see [Recursion](#recursion) below).
|
|
107
|
-
|
|
108
|
-
- **MethodInfo** represents a single method regardless of where it was called from. It aggregates data across all call sites. Each MethodInfo holds a CallTrees collection that links back to every CallTree node that invoked that method, providing both caller and callee information.
|
|
109
|
-
|
|
110
|
-
This separation is what allows ruby-prof to generate both call graph reports (which show calling relationships) and flat reports (which show per-method totals).
|
|
111
|
-
|
|
112
|
-
## Building the Call Tree
|
|
113
|
-
|
|
114
|
-
This section describes how the call tree is constructed during profiling.
|
|
115
|
-
|
|
116
|
-
Consider profiling this code:
|
|
117
|
-
|
|
118
|
-
```ruby
|
|
119
|
-
def process
|
|
120
|
-
validate
|
|
121
|
-
save
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
def save
|
|
125
|
-
validate
|
|
126
|
-
write
|
|
127
|
-
end
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
The resulting CallTree looks like:
|
|
131
|
-
|
|
132
|
-
```mermaid
|
|
133
|
-
graph TD
|
|
134
|
-
A{{"[global]"}} -->|child| B{{"process"}}
|
|
135
|
-
B -->|child| C{{"validate"}}
|
|
136
|
-
B -->|child| D{{"save"}}
|
|
137
|
-
D -->|child| E{{"validate"}}
|
|
138
|
-
D -->|child| F{{"write"}}
|
|
139
|
-
|
|
140
|
-
B -.->|parent| A
|
|
141
|
-
C -.->|parent| B
|
|
142
|
-
D -.->|parent| B
|
|
143
|
-
E -.->|parent| D
|
|
144
|
-
F -.->|parent| D
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
Notice that `validate` appears as two separate CallTree nodes — one under `process` and one under `save` — because it was called from two different call sites. Each has its own parent and its own Measurement. Both nodes reference the same `validate` MethodInfo, which aggregates the data across both call sites.
|
|
148
|
-
|
|
149
|
-
The following diagram shows both views together. CallTree nodes (hexagons) reference their target MethodInfo (rectangles) via dashed arrows:
|
|
150
|
-
|
|
151
|
-
```mermaid
|
|
152
|
-
graph TD
|
|
153
|
-
classDef calltree fill:#E8F4FD,stroke:#2E86C1
|
|
154
|
-
classDef methodinfo fill:#FADBD8,stroke:#E74C3C
|
|
155
|
-
|
|
156
|
-
CT1{{"[global]"}}:::calltree --> CT2{{"process"}}:::calltree
|
|
157
|
-
CT2 --> CT3{{"validate"}}:::calltree
|
|
158
|
-
CT2 --> CT4{{"save"}}:::calltree
|
|
159
|
-
CT4 --> CT5{{"validate"}}:::calltree
|
|
160
|
-
CT4 --> CT6{{"write"}}:::calltree
|
|
161
|
-
|
|
162
|
-
CT1 -.->|target| M1["[global]"]:::methodinfo
|
|
163
|
-
CT2 -.->|target| M2["process"]:::methodinfo
|
|
164
|
-
CT3 -.->|target| M3["validate"]:::methodinfo
|
|
165
|
-
CT4 -.->|target| M4["save"]:::methodinfo
|
|
166
|
-
CT5 -.->|target| M3
|
|
167
|
-
CT6 -.->|target| M5["write"]:::methodinfo
|
|
168
|
-
|
|
169
|
-
M3 -.->|call_trees| CT3
|
|
170
|
-
M3 -.->|call_trees| CT5
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
Both `validate` CallTree nodes point to the same `validate` MethodInfo via `target`. The MethodInfo points back to its CallTree nodes via `call_trees` — a flat array of every CallTree node that invoked this method. From this array, `callers` and `callees` are derived: `callers` walks each node's parent, and `callees` walks each node's children. Both are aggregated by method to produce a single entry per caller or callee method.
|
|
174
|
-
|
|
175
|
-
### Parents
|
|
176
|
-
|
|
177
|
-
Each CallTree node has exactly one parent, set at creation. When a method call event fires, the profiler determines the parent from the current frame on the stack:
|
|
178
|
-
|
|
179
|
-
```c
|
|
180
|
-
parent_call_tree = frame->call_tree;
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
The parent is the CallTree node that was active (top of the stack) when this method was called. The root CallTree node for each thread has no parent.
|
|
184
|
-
|
|
185
|
-
### Children
|
|
186
|
-
|
|
187
|
-
A CallTree node's children are stored in a hash table keyed by method. The profiler looks up the method in the current parent's children. If a child already exists for that method, the existing CallTree node is **reused** and its `called` count increments. Otherwise a new node is created:
|
|
188
|
-
|
|
189
|
-
```c
|
|
190
|
-
call_tree = call_tree_table_lookup(parent_call_tree->children, method->key);
|
|
191
|
-
|
|
192
|
-
if (!call_tree)
|
|
193
|
-
{
|
|
194
|
-
call_tree = prof_call_tree_create(method, parent_call_tree, ...);
|
|
195
|
-
prof_call_tree_add_child(parent_call_tree, call_tree);
|
|
196
|
-
}
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
This means each parent has **one** child CallTree per method. For example, if `foo` calls `bar` ten times, there is a single `bar` CallTree node under `foo` with `called: 10`.
|
|
200
|
-
|
|
201
|
-
## Allocation
|
|
202
|
-
|
|
203
|
-
When allocation tracking is enabled, each MethodInfo records the objects it allocated. An Allocation tracks the class of object created, the source location, and the count.
|
|
204
|
-
|
|
205
|
-
## Memory Management
|
|
206
|
-
|
|
207
|
-
The Profile object is responsible for managing the memory of its child objects, which are C structures. When a Profile is garbage collected, it recursively frees all its objects. In the class diagram, composition relationships (filled diamond) indicate ownership — a Profile frees its Threads, Threads free their CallTrees and MethodInfo instances, and so on.
|
|
208
|
-
|
|
209
|
-
ruby-prof keeps a Profile alive as long as there are live references to any of its MethodInfo or CallTree objects. This is done via Ruby's GC mark phase: CallTree instances mark their associated MethodInfo, and MethodInfo instances mark their owning Profile.
|
|
210
|
-
|
|
211
|
-
Starting with version 1.5, it is possible to create Thread, CallTree and MethodInfo instances from Ruby (this was added to support testing). These Ruby-created objects are owned by Ruby's garbage collector rather than the C extension. An internal ownership flag on each instance tracks who is responsible for freeing it.
|
|
212
|
-
|
|
213
|
-
## Recursion
|
|
214
|
-
|
|
215
|
-
The call tree handles recursion naturally — each recursive call has a different parent, so new nodes are created at each level just like any other method call. The only special handling is in timing calculation, where care is needed to avoid double-counting.
|
|
216
|
-
|
|
217
|
-
### How Recursive Calls Create New Nodes
|
|
218
|
-
|
|
219
|
-
Consider a simple recursive method:
|
|
220
|
-
|
|
221
|
-
```ruby
|
|
222
|
-
def simple(n)
|
|
223
|
-
sleep(1)
|
|
224
|
-
return if n == 0
|
|
225
|
-
simple(n - 1)
|
|
226
|
-
end
|
|
227
|
-
|
|
228
|
-
simple(2)
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
Each recursive call to `simple` has a different parent CallTree node, so the lookup in the parent's children misses and a new node is created at each level:
|
|
232
|
-
|
|
233
|
-
```mermaid
|
|
234
|
-
graph TD
|
|
235
|
-
classDef calltree fill:#E8F4FD,stroke:#2E86C1
|
|
236
|
-
classDef methodinfo fill:#FADBD8,stroke:#E74C3C
|
|
237
|
-
|
|
238
|
-
A{{"[global]"}}:::calltree --> B{{"simple"}}:::calltree
|
|
239
|
-
B --> C{{"sleep"}}:::calltree
|
|
240
|
-
B --> D{{"simple"}}:::calltree
|
|
241
|
-
D --> E{{"sleep"}}:::calltree
|
|
242
|
-
D --> F{{"simple"}}:::calltree
|
|
243
|
-
F --> G{{"sleep"}}:::calltree
|
|
244
|
-
|
|
245
|
-
B -.-> A
|
|
246
|
-
C -.-> B
|
|
247
|
-
D -.-> B
|
|
248
|
-
E -.-> D
|
|
249
|
-
F -.-> D
|
|
250
|
-
G -.-> F
|
|
251
|
-
|
|
252
|
-
B -.-> M["simple"]:::methodinfo
|
|
253
|
-
D -.-> M
|
|
254
|
-
F -.-> M
|
|
255
|
-
```
|
|
256
|
-
|
|
257
|
-
The CallTree is always acyclic — each recursive call creates a new node at a deeper level. However, there is a single `simple` MethodInfo (red rectangle at the bottom of the diagram), and each CallTree node points to it.
|
|
258
|
-
|
|
259
|
-
### The Visits Counter
|
|
260
|
-
|
|
261
|
-
Both CallTree and MethodInfo have a `visits` field that tracks how many times that node or method is currently on the stack. This counter is incremented on method entry and decremented on method exit:
|
|
262
|
-
|
|
263
|
-
```c
|
|
264
|
-
// Method entry (prof_frame_push):
|
|
265
|
-
call_tree->visits++;
|
|
266
|
-
if (call_tree->method->visits > 0)
|
|
267
|
-
call_tree->method->recursive = true;
|
|
268
|
-
call_tree->method->visits++;
|
|
269
|
-
|
|
270
|
-
// Method exit (prof_frame_pop):
|
|
271
|
-
call_tree->visits--;
|
|
272
|
-
call_tree->method->visits--;
|
|
273
|
-
```
|
|
274
|
-
|
|
275
|
-
The MethodInfo `visits` counter serves two purposes:
|
|
276
|
-
|
|
277
|
-
1. Detecting recursion — if `method->visits > 0` when a method is entered, the method is currently an ancestor of itself in the call stack and is marked recursive.
|
|
278
|
-
|
|
279
|
-
2. Correct total_time accounting — total time is only added to the Measurement when a node's `visits` drops back to 1, meaning it is the outermost invocation:
|
|
280
|
-
|
|
281
|
-
```c
|
|
282
|
-
// Only accumulate total_time at the outermost visit
|
|
283
|
-
if (call_tree->visits == 1)
|
|
284
|
-
call_tree->measurement->total_time += total_time;
|
|
285
|
-
|
|
286
|
-
if (call_tree->method->visits == 1)
|
|
287
|
-
call_tree->method->measurement->total_time += total_time;
|
|
288
|
-
```
|
|
289
|
-
|
|
290
|
-
Without this guard, total time would be double-counted. Consider `simple(2)` with 1-second sleeps. The outermost call takes ~3 seconds total, the middle call ~2 seconds, and the innermost ~1 second. Naively summing all three would give 6 seconds, but the actual elapsed time is only 3 seconds. By only recording total_time at the outermost visit, the MethodInfo correctly reports 3 seconds.
|
|
291
|
-
|
|
292
|
-
### Recursion at the MethodInfo Level
|
|
293
|
-
|
|
294
|
-
At the MethodInfo level, recursive methods create cycles. A recursive `simple` method has itself as both a caller and a callee:
|
|
295
|
-
|
|
296
|
-
```mermaid
|
|
297
|
-
graph TD
|
|
298
|
-
classDef methodinfo fill:#FADBD8,stroke:#E74C3C
|
|
299
|
-
A["global"]:::methodinfo -->|"calls"| B["simple"]:::methodinfo
|
|
300
|
-
B -->|"calls"| C["sleep"]:::methodinfo
|
|
301
|
-
B -->|"calls"| B
|
|
302
|
-
```
|
|
303
|
-
|
|
304
|
-
This is why MethodInfo has a `recursive?` flag — printers that operate on MethodInfo (such as the graph printer) need to be aware of these cycles. However, the underlying CallTree structure is always a tree with no structural cycles.
|
data/docs/best-practices.md
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
# Best Practices
|
|
2
|
-
|
|
3
|
-
Profiling gives you amazing insight into your program. What you think is slow is almost never what is actually slow. Below are some best practices to help unlock this power.
|
|
4
|
-
|
|
5
|
-
## Start With Realistic Runs
|
|
6
|
-
|
|
7
|
-
When profiling data-heavy work, start with a smaller sample of the data instead of the full dataset. Profile a portion first (for example 1% or 10%). It is faster, easier to understand, and often enough to find the main bottleneck. Once you have a likely fix, validate it with a larger and more realistic workload so you know the result still holds in context. Run the same profile more than once and warm up before you measure so one-time startup work does not dominate the report.
|
|
8
|
-
|
|
9
|
-
## Choose The Right Measurement Mode
|
|
10
|
-
|
|
11
|
-
Pick the measurement mode based on the question you are asking. Use `WALL_TIME` for end-to-end latency, `PROCESS_TIME` for CPU-focused work, and `ALLOCATIONS` when object churn is the concern. See [Measurement Mode](advanced-usage.md#measurement-mode) for details.
|
|
12
|
-
|
|
13
|
-
## Reduce Noise Before Deep Analysis
|
|
14
|
-
|
|
15
|
-
When framework internals or concurrency noise dominate output, narrow the scope first. Use `exclude_common` or explicit method exclusions, and use thread filtering (`include_threads` / `exclude_threads`) when needed. For highly concurrent workloads, merging worker results (`merge!` or Rack `merge_fibers: true`) can make trends much easier to read. See [Profiling Options](advanced-usage.md#profiling-options), [Method Exclusion](advanced-usage.md#method-exclusion), and [Merging Threads and Fibers](advanced-usage.md#merging-threads-and-fibers).
|
|
16
|
-
|
|
17
|
-
## Use Reports In A Sequence
|
|
18
|
-
|
|
19
|
-
Start with a quick summary, then drill down. In practice, this usually means using `FlatPrinter` to find hotspots, `GraphHtmlPrinter` (or `GraphPrinter`) to understand caller/callee relationships, and `FlameGraphPrinter` to validate dominant paths visually. See [Reports](reports.md), especially [Creating Reports](reports.md#creating-reports) and [Report Types](reports.md#report-types).
|
|
20
|
-
|
|
21
|
-
## Use Threshold Filters Early
|
|
22
|
-
|
|
23
|
-
Threshold filters are one of the fastest ways to make a large profile readable. Start with `min_percent` to hide low-impact methods in most printers. For `GraphHtmlPrinter`, use `min_time` when you want to drop methods below an absolute time cutoff. These filters help you focus on the code that actually moves total runtime.
|
|
24
|
-
|
|
25
|
-
## Compare Trends, Not Single Snapshots
|
|
26
|
-
|
|
27
|
-
Do not optimize based on one run unless the signal is overwhelming. Compare before/after profiles under the same workload, then prioritize repeated hot paths over one-off spikes.
|