fiber-profiler 0.6.1 → 0.7.1
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
- checksums.yaml.gz.sig +0 -0
- data/context/capture-mode.md +80 -0
- data/context/getting-started.md +20 -49
- data/context/index.yaml +10 -1
- data/context/watchdog-mode.md +100 -0
- data/ext/fiber/profiler/capture.c +4 -2
- data/lib/fiber/profiler/capture.rb +2 -35
- data/lib/fiber/profiler/thread_local.rb +34 -0
- data/lib/fiber/profiler/version.rb +1 -1
- data/lib/fiber/profiler/watchdog.rb +185 -0
- data/lib/fiber/profiler.rb +17 -3
- data/readme.md +13 -3
- data/release.cert +24 -0
- data/releases.md +4 -0
- data.tar.gz.sig +0 -0
- metadata +44 -29
- metadata.gz.sig +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e3d1446f213526360f5368bfb8a08884033484f4ab868160340709652349c25c
|
|
4
|
+
data.tar.gz: 844c5319c20f8b0929342978790efe2d1aeb4dd37ae260cd073ff36c82c730fd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '08e564d3dc9c9abfba57056f3ff5a7e0fce516a5969ee014820ff2dac72b60fd51603c258c21cbee80c46d686f9ca9bbd2376a11b1202635371d0201b8a3b372'
|
|
7
|
+
data.tar.gz: 88d96fba98c5b9578683ad7f13cb898d70e8da83ce11a3b756b15d29b4cbc29649a61c7ec2d589cb9a1185e9646bee4ed018bbcbb1946defdd2e7163ae951aa8
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Capture Mode
|
|
2
|
+
|
|
3
|
+
This guide explains how to trace fiber execution and analyze call timings with the capture profiler.
|
|
4
|
+
|
|
5
|
+
Use capture mode when you need to investigate the calls made during a stall. It records call timings for sampled fiber executions and reports after the fiber switches away. For sampled backtraces while a stall is still in progress, see [Watchdog Mode](../watchdog-mode/index).
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
Add `fiber-profiler` to your application's bundle as described in [Getting Started](../getting-started/index), then select capture mode when starting Async or Falcon:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
FIBER_PROFILER=capture bundle exec falcon serve
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Async starts and stops capture automatically. The legacy `FIBER_PROFILER_CAPTURE=true` setting also enables capture when `FIBER_PROFILER` is unset. An explicit `FIBER_PROFILER` value takes precedence.
|
|
16
|
+
|
|
17
|
+
### Manual Instrumentation
|
|
18
|
+
|
|
19
|
+
To reproduce a stall without a scheduler, save this as `capture.rb`. It starts a {ruby Fiber::Profiler::Capture} and simulates a blocking operation inside a fiber:
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
require "fiber/profiler"
|
|
23
|
+
|
|
24
|
+
profiler = Fiber::Profiler::Capture.new
|
|
25
|
+
|
|
26
|
+
begin
|
|
27
|
+
profiler.start
|
|
28
|
+
|
|
29
|
+
# Simulate a blocking operation without a scheduler:
|
|
30
|
+
Fiber.new(blocking: false) do
|
|
31
|
+
sleep 0.1
|
|
32
|
+
end.resume
|
|
33
|
+
ensure
|
|
34
|
+
profiler.stop
|
|
35
|
+
end
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
bundle exec ruby capture.rb
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
This example starts the profiler explicitly, so it does not need `FIBER_PROFILER=capture`. The report should include `Kernel#sleep` and its elapsed duration. Start and stop profiling on the same thread.
|
|
43
|
+
|
|
44
|
+
## Configuration
|
|
45
|
+
|
|
46
|
+
Set these environment variables before loading `fiber-profiler`; the native extension reads them when it loads:
|
|
47
|
+
|
|
48
|
+
| Variable | Default | Meaning |
|
|
49
|
+
| --- | --- | --- |
|
|
50
|
+
| `FIBER_PROFILER_CAPTURE_STALL_THRESHOLD` | `0.01` | Minimum execution duration in seconds to exceed before reporting a stall. |
|
|
51
|
+
| `FIBER_PROFILER_CAPTURE_FILTER_THRESHOLD` | 10% of the stall threshold | Filter calls shorter than this duration in seconds. |
|
|
52
|
+
| `FIBER_PROFILER_CAPTURE_TRACK_CALLS` | `true` | Record call timings. Set to `false` to report stall durations without tracing calls. |
|
|
53
|
+
| `FIBER_PROFILER_CAPTURE_SAMPLE_RATE` | `1.0` | Fraction of eligible fiber executions to sample: `1.0` samples all, `0.1` samples approximately 10%. |
|
|
54
|
+
|
|
55
|
+
These settings apply to capture mode only. The sample rate controls selection at fiber switches; it is not a time interval between backtrace samples.
|
|
56
|
+
|
|
57
|
+
For explicit instrumentation, override the defaults with `Fiber::Profiler::Capture.new(stall_threshold: 0.05, filter_threshold: 0.005, track_calls: true, sample_rate: 0.1)`. Pass `output:` to select a writable IO; the default is standard error.
|
|
58
|
+
|
|
59
|
+
## Reading Reports
|
|
60
|
+
|
|
61
|
+
When standard error is a terminal, capture prints a readable call log. Redirected output uses one JSON object per stall, including the execution `duration` and a `calls` array. Each retained call includes its source location, class, method, duration, and nesting information.
|
|
62
|
+
|
|
63
|
+
To collect the example's reports and summarize call timings:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
bundle exec ruby capture.rb 2> capture.ndjson
|
|
67
|
+
bundle exec bake input --file capture.ndjson fiber:profiler:analyze output
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The analyzer aggregates call durations by source location and sorts the summary by total duration. Feed it capture reports; unrelated application output on standard error must be separated from the JSON first. With `track_calls: false`, reports contain no call timings to aggregate.
|
|
71
|
+
|
|
72
|
+
Call durations can include time spent in nested calls, so adding durations across different locations does not give total application runtime. Short or uninformative calls may be filtered from the report.
|
|
73
|
+
|
|
74
|
+
## Interpretation and Limits
|
|
75
|
+
|
|
76
|
+
Capture samples non-blocking fibers and excludes blocking fibers, including the event-loop fiber. It measures wall time between fiber switches, so an ordinary scheduler-aware wait that yields does not count its entire wait time as a stall. A blocking operation that keeps the same fiber executing can count toward a stall.
|
|
77
|
+
|
|
78
|
+
Reports are emitted when the fiber switches away. An operation that never yields or returns can therefore prevent its capture report from appearing. Use [Watchdog Mode](../watchdog-mode/index) to investigate an ongoing stall when Ruby thread scheduling is still possible.
|
|
79
|
+
|
|
80
|
+
Call tracing can substantially affect performance. Reduce the sample rate to trace fewer executions, or disable call tracking if you only need stall durations. Use the [Falcon overhead benchmark](https://github.com/socketry/fiber-profiler/tree/main/benchmark/falcon) to compare modes, and measure with your application's Ruby/JIT configuration and workload.
|
data/context/getting-started.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Getting Started
|
|
2
2
|
|
|
3
|
-
This guide explains how to
|
|
3
|
+
This guide explains how to install the fiber profiler and choose a mode for diagnosing event-loop stalls.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -10,63 +10,34 @@ Add the gem to your project:
|
|
|
10
10
|
$ bundle add fiber-profiler
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
##
|
|
13
|
+
## Choose a Mode
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
A fiber that runs for too long without yielding prevents other work on the same event loop from progressing. The profiler offers two ways to investigate it:
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
| Mode | Use it to | Reports |
|
|
18
|
+
| --- | --- | --- |
|
|
19
|
+
| [Watchdog](../watchdog-mode/index) | Find where a fiber is spending time during an ongoing stall. | Periodically sampled backtraces while the fiber is still executing. |
|
|
20
|
+
| [Capture](../capture-mode/index) | Investigate individual call timings during a fiber's execution. | Traced calls and their durations after the fiber switches away. |
|
|
19
21
|
|
|
20
|
-
|
|
22
|
+
Watchdog avoids method-call tracing and is a useful starting point for observing sustained stalls. Capture provides more detail at a higher instrumentation cost. Both affect the application being measured; see the [Falcon overhead benchmark](https://github.com/socketry/fiber-profiler/tree/main/benchmark/falcon) and measure with your own workload.
|
|
21
23
|
|
|
22
|
-
|
|
24
|
+
## Integration with Async and Falcon
|
|
23
25
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
# Your application code:
|
|
27
|
-
Fiber.new do
|
|
28
|
-
sleep 0.1
|
|
29
|
-
end.resume
|
|
30
|
-
|
|
31
|
-
profiler&.stop
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
Running this program will output the following:
|
|
26
|
+
Select the profiling mode when starting your application:
|
|
35
27
|
|
|
36
28
|
```bash
|
|
37
|
-
|
|
38
|
-
Fiber stalled for 0.105 seconds
|
|
39
|
-
/Users/samuel/Developer/socketry/fiber-profiler/test.rb:11 in c-call 'Kernel#sleep' (0.105s)
|
|
29
|
+
FIBER_PROFILER=watchdog bundle exec falcon serve
|
|
40
30
|
```
|
|
41
31
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
The fiber profiler is optionally supported by `Async`. Simply enable the profiler using `FIBER_PROFILER_CAPTURE=true` to capture and report stalls.
|
|
45
|
-
|
|
46
|
-
## Default Environment Variables
|
|
47
|
-
|
|
48
|
-
### `FIBER_PROFILER_CAPTURE`
|
|
49
|
-
|
|
50
|
-
Set to `true` to enable capturing of stalled fibers.
|
|
51
|
-
|
|
52
|
-
### `FIBER_PROFILER_CAPTURE_STALL_THRESHOLD`
|
|
32
|
+
With Async, including Falcon, the scheduler starts and stops the selected profiler automatically. Include `fiber-profiler` in your application's bundle; no initializer or per-request instrumentation is needed.
|
|
53
33
|
|
|
54
|
-
|
|
34
|
+
| Setting | Behaviour |
|
|
35
|
+
| --- | --- |
|
|
36
|
+
| `FIBER_PROFILER=watchdog` | Periodically sample stacks during sustained fiber execution. |
|
|
37
|
+
| `FIBER_PROFILER=capture` | Trace calls and report fibers that exceed the capture threshold. |
|
|
38
|
+
| `FIBER_PROFILER=false` | Disable the default profiler, even if the legacy capture flag is enabled. |
|
|
39
|
+
| `FIBER_PROFILER` unset | Preserve the existing `FIBER_PROFILER_CAPTURE=true` behaviour. |
|
|
55
40
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
Set to `true` to track calls within the fiber. Default is `true`. This can be disabled to reduce overhead.
|
|
59
|
-
|
|
60
|
-
### `FIBER_PROFILER_CAPTURE_SAMPLE_RATE`
|
|
61
|
-
|
|
62
|
-
Set the sample rate of the profiler as a percentage of all context switches. The default is 1.0 (100%).
|
|
63
|
-
|
|
64
|
-
## Analyzing Logs
|
|
65
|
-
|
|
66
|
-
If you collect your logs in a file (e.g. as `ndjson`) you can analyze them using the included `bake` commands:
|
|
67
|
-
|
|
68
|
-
```bash
|
|
69
|
-
$ bundle exec bake input --file samples.ndjson fiber:profiler:analyze output
|
|
70
|
-
```
|
|
41
|
+
Unknown modes raise `ArgumentError`. Set environment variables before loading the gem: the native capture settings are read when the extension loads. The new mode selector and watchdog settings are read when `Fiber::Profiler.default` is called.
|
|
71
42
|
|
|
72
|
-
|
|
43
|
+
Each scheduler obtains its own profiler, so schedulers in separate worker processes or threads are monitored independently. After `fork`, inherited profiling is stopped in the child; a new scheduler starts a new profiler normally.
|
data/context/index.yaml
CHANGED
|
@@ -8,4 +8,13 @@ metadata:
|
|
|
8
8
|
files:
|
|
9
9
|
- path: getting-started.md
|
|
10
10
|
title: Getting Started
|
|
11
|
-
description: This guide explains how to
|
|
11
|
+
description: This guide explains how to install the fiber profiler and choose a
|
|
12
|
+
mode for diagnosing event-loop stalls.
|
|
13
|
+
- path: watchdog-mode.md
|
|
14
|
+
title: Watchdog Mode
|
|
15
|
+
description: This guide explains how to sample ongoing fiber stalls with the watchdog
|
|
16
|
+
profiler.
|
|
17
|
+
- path: capture-mode.md
|
|
18
|
+
title: Capture Mode
|
|
19
|
+
description: This guide explains how to trace fiber execution and analyze call timings
|
|
20
|
+
with the capture profiler.
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Watchdog Mode
|
|
2
|
+
|
|
3
|
+
This guide explains how to sample ongoing fiber stalls with the watchdog profiler.
|
|
4
|
+
|
|
5
|
+
Use watchdog mode to find where a fiber is spending time while it prevents other fibers from running. It reports during the stall, so you can investigate an operation that has not returned yet. For individual call timings, see [Capture Mode](../capture-mode/index).
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
Add `fiber-profiler` to your application's bundle as described in [Getting Started](../getting-started/index), then select watchdog mode when starting Async or Falcon:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
FIBER_PROFILER=watchdog bundle exec falcon serve
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Async starts and stops the watchdog automatically. Each scheduler gets its own profiler, which binds to the thread when profiling starts.
|
|
16
|
+
|
|
17
|
+
To reproduce a stall, save this as `stall.rb`:
|
|
18
|
+
|
|
19
|
+
```ruby
|
|
20
|
+
require "async"
|
|
21
|
+
|
|
22
|
+
Sync do
|
|
23
|
+
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + 2
|
|
24
|
+
while Process.clock_gettime(Process::CLOCK_MONOTONIC) < deadline
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
FIBER_PROFILER=watchdog bundle exec ruby stall.rb
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The reports should point to the busy loop. Replacing the loop with `sleep(2)` allows Async to keep scheduling fibers and should produce no reports.
|
|
34
|
+
|
|
35
|
+
## How Sampling Works
|
|
36
|
+
|
|
37
|
+
Watchdog uses a thread-specific `:fiber_switch` tracepoint to track which fiber is executing. A separate Ruby thread periodically captures the monitored thread's backtrace. It does not install method-call tracing hooks or require a heartbeat task.
|
|
38
|
+
|
|
39
|
+
By default, it samples every 100 ms, retains at most five recent stacks, and reports when the same fiber execution lasts at least 500 ms. A continued stall produces at most one report per additional threshold interval. Returning to the event loop or switching to another fiber resets the sample window. The measured duration covers one uninterrupted fiber execution.
|
|
40
|
+
|
|
41
|
+
## Configuration
|
|
42
|
+
|
|
43
|
+
Set these environment variables before starting your application. They are read when the default watchdog is constructed:
|
|
44
|
+
|
|
45
|
+
| Variable | Default | Meaning |
|
|
46
|
+
| --- | --- | --- |
|
|
47
|
+
| `FIBER_PROFILER_WATCHDOG_STALL_THRESHOLD` | `0.5` | Minimum execution duration in seconds before reporting, and minimum interval between repeated reports. |
|
|
48
|
+
| `FIBER_PROFILER_WATCHDOG_SAMPLE_INTERVAL` | `0.1` | Delay in seconds between stack samples. |
|
|
49
|
+
|
|
50
|
+
Both values must be finite and positive. Actual sampling intervals depend on Ruby and operating-system scheduling. Capture-specific settings, including its sample rate, do not configure watchdog mode.
|
|
51
|
+
|
|
52
|
+
### Manual Instrumentation
|
|
53
|
+
|
|
54
|
+
When managing fibers directly, construct a {ruby Fiber::Profiler::Watchdog} and stop it in an `ensure` block:
|
|
55
|
+
|
|
56
|
+
```ruby
|
|
57
|
+
require "fiber/profiler/watchdog"
|
|
58
|
+
|
|
59
|
+
watchdog = Fiber::Profiler::Watchdog.new(
|
|
60
|
+
stall_threshold: 0.5,
|
|
61
|
+
sample_interval: 0.1,
|
|
62
|
+
max_samples: 5,
|
|
63
|
+
output: $stderr
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
begin
|
|
67
|
+
watchdog.start
|
|
68
|
+
|
|
69
|
+
# Simulate an application fiber blocked on an operation:
|
|
70
|
+
Fiber.new do
|
|
71
|
+
sleep 1
|
|
72
|
+
end.resume
|
|
73
|
+
ensure
|
|
74
|
+
watchdog.stop
|
|
75
|
+
end
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
This example starts the profiler explicitly, so it does not need `FIBER_PROFILER=watchdog`. Start and stop profiling on the monitored thread. `max_samples` must be a positive integer; there is no environment variable for it. The output must support writes from the watchdog thread. Stop the profiler before closing its output.
|
|
79
|
+
|
|
80
|
+
When profiling starts on a blocking event-loop fiber, that fiber is excluded from monitoring, including its normal idle waits. Other fibers, including blocking application fibers, are monitored. When profiling starts inside a non-blocking application fiber, blocking fibers are excluded because the event-loop fiber is not known.
|
|
81
|
+
|
|
82
|
+
If sampling or reporting raises a `StandardError`, the watchdog disables its tracepoint and stops sampling. It retains the exception in `watchdog.error` and attempts one warning to standard error. Failure to write that warning is also contained. These errors do not escape through `stop` or replace application errors. Call `stop` normally to finish cleanup; a subsequent `start` clears the error and resumes monitoring.
|
|
83
|
+
|
|
84
|
+
## Reading Reports
|
|
85
|
+
|
|
86
|
+
Terminal output contains readable stacks. Redirected output uses one JSON object per report:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
FIBER_PROFILER=watchdog bundle exec ruby stall.rb 2> watchdog.ndjson
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Each report contains `mode: "watchdog"`, process/thread/fiber identifiers, elapsed `duration`, and a bounded `samples` array. Each sample contains its elapsed time and `backtrace`. Duration is elapsed wall time since the fiber resumed, and a report is emitted while the fiber is still executing.
|
|
93
|
+
|
|
94
|
+
Repeated frames identify code worth investigating: optimize expensive work, add cooperative yield points, or offload suitable work to a bounded thread pool. Watchdog reports contain sampled backtraces, so the capture-mode `fiber:profiler:analyze` command does not aggregate them.
|
|
95
|
+
|
|
96
|
+
## Interpretation and Limits
|
|
97
|
+
|
|
98
|
+
The watchdog requires Ruby thread scheduling. Native code that holds the GVL without allowing other threads to run can prevent sampling entirely. Short stalls can occur between samples, and normal scheduler/OS delays can extend measured execution time. A report is a diagnostic lead, not proof that every sampled frame is expensive.
|
|
99
|
+
|
|
100
|
+
Sampling still has overhead. Use the [Falcon overhead benchmark](https://github.com/socketry/fiber-profiler/tree/main/benchmark/falcon) as a starting point for measuring your own application before enabling continuous monitoring.
|
|
@@ -184,7 +184,7 @@ static void Fiber_Profiler_Capture_free(void *ptr) {
|
|
|
184
184
|
Fiber_Profiler_Stream_free(&capture->stream);
|
|
185
185
|
Fiber_Profiler_Deque_free(&capture->calls);
|
|
186
186
|
|
|
187
|
-
|
|
187
|
+
xfree(capture);
|
|
188
188
|
}
|
|
189
189
|
|
|
190
190
|
static size_t Fiber_Profiler_Capture_memsize(const void *ptr) {
|
|
@@ -271,7 +271,9 @@ VALUE Fiber_Profiler_Capture_initialize(int argc, VALUE *argv, VALUE self) {
|
|
|
271
271
|
|
|
272
272
|
VALUE arguments[5] = {0};
|
|
273
273
|
VALUE options = Qnil;
|
|
274
|
-
|
|
274
|
+
if (argc > 0) {
|
|
275
|
+
rb_scan_args(argc, argv, ":", &options);
|
|
276
|
+
}
|
|
275
277
|
rb_get_kwargs(options, Fiber_Profiler_Capture_initialize_options, 0, 5, arguments);
|
|
276
278
|
|
|
277
279
|
if (arguments[0] != Qundef) {
|
|
@@ -4,44 +4,11 @@
|
|
|
4
4
|
# Copyright, 2025-2026, by Samuel Williams.
|
|
5
5
|
|
|
6
6
|
require_relative "native"
|
|
7
|
-
require_relative "
|
|
7
|
+
require_relative "thread_local"
|
|
8
8
|
|
|
9
9
|
module Fiber::Profiler
|
|
10
|
-
# Thread-local storage for the active profiler capture.
|
|
11
|
-
::Thread.attr_accessor :fiber_profiler_capture
|
|
12
|
-
|
|
13
|
-
# Hook into Process._fork to handle fork events automatically.
|
|
14
|
-
::Process.singleton_class.prepend(ForkHandler)
|
|
15
|
-
|
|
16
|
-
# Private module that wraps start/stop to manage thread-local storage.
|
|
17
|
-
module ThreadLocalCapture
|
|
18
|
-
# Start profiling and store the capture in the current thread's thread-local storage.
|
|
19
|
-
def start
|
|
20
|
-
result = super
|
|
21
|
-
|
|
22
|
-
if result
|
|
23
|
-
Thread.current.fiber_profiler_capture = self
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
return result
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
# Stop profiling and clear the capture from the current thread's thread-local storage.
|
|
30
|
-
def stop
|
|
31
|
-
result = super
|
|
32
|
-
|
|
33
|
-
if result
|
|
34
|
-
Thread.current.fiber_profiler_capture = nil
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
return result
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
private_constant :ThreadLocalCapture
|
|
42
|
-
|
|
43
10
|
# Represents a running profiler capture.
|
|
44
11
|
class Capture
|
|
45
|
-
prepend
|
|
12
|
+
prepend ThreadLocalProfiler
|
|
46
13
|
end
|
|
47
14
|
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2025-2026, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
require_relative "fork_handler"
|
|
7
|
+
|
|
8
|
+
module Fiber::Profiler
|
|
9
|
+
# Thread-local storage for the active profiler, shared by both implementations.
|
|
10
|
+
::Thread.attr_accessor :fiber_profiler_capture
|
|
11
|
+
|
|
12
|
+
::Process.singleton_class.prepend(ForkHandler)
|
|
13
|
+
|
|
14
|
+
# Manages the active profiler so it can be stopped after a fork.
|
|
15
|
+
module ThreadLocalProfiler
|
|
16
|
+
# Start profiling on the current thread.
|
|
17
|
+
def start
|
|
18
|
+
result = super
|
|
19
|
+
Thread.current.fiber_profiler_capture = self if result
|
|
20
|
+
result
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Stop profiling and clear the current thread's reference.
|
|
24
|
+
def stop
|
|
25
|
+
super
|
|
26
|
+
ensure
|
|
27
|
+
if Thread.current.fiber_profiler_capture.equal?(self)
|
|
28
|
+
Thread.current.fiber_profiler_capture = nil
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private_constant :ThreadLocalProfiler
|
|
34
|
+
end
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2026, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
require_relative "version"
|
|
7
|
+
require_relative "thread_local"
|
|
8
|
+
require "json"
|
|
9
|
+
|
|
10
|
+
module Fiber::Profiler
|
|
11
|
+
# Samples stacks while a fiber runs without switching back to the event loop.
|
|
12
|
+
class Watchdog
|
|
13
|
+
prepend ThreadLocalProfiler
|
|
14
|
+
|
|
15
|
+
# Build a watchdog using environment configuration.
|
|
16
|
+
# @parameter env [Hash] The environment to read configuration from.
|
|
17
|
+
# @returns [Watchdog] The configured watchdog.
|
|
18
|
+
def self.default(env = ENV)
|
|
19
|
+
new(
|
|
20
|
+
stall_threshold: Float(env.fetch("FIBER_PROFILER_WATCHDOG_STALL_THRESHOLD", "0.5")),
|
|
21
|
+
sample_interval: Float(env.fetch("FIBER_PROFILER_WATCHDOG_SAMPLE_INTERVAL", "0.1"))
|
|
22
|
+
)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Initialize a watchdog. Monitoring begins when {start} is called.
|
|
26
|
+
# @parameter stall_threshold [Float] Seconds without a fiber switch before reporting, and the minimum interval between reports for the same execution.
|
|
27
|
+
# @parameter sample_interval [Float] Seconds between stack samples.
|
|
28
|
+
# @parameter max_samples [Integer] Maximum number of recent stacks retained per execution.
|
|
29
|
+
# @parameter output [IO] The destination for stall reports.
|
|
30
|
+
def initialize(stall_threshold: 0.5, sample_interval: 0.1, max_samples: 5, output: $stderr)
|
|
31
|
+
@stall_threshold = Float(stall_threshold)
|
|
32
|
+
@sample_interval = Float(sample_interval)
|
|
33
|
+
|
|
34
|
+
unless @stall_threshold.finite? && @stall_threshold.positive? && @sample_interval.finite? && @sample_interval.positive?
|
|
35
|
+
raise ArgumentError, "Watchdog intervals must be finite and positive"
|
|
36
|
+
end
|
|
37
|
+
unless max_samples.is_a?(Integer) && max_samples.positive?
|
|
38
|
+
raise ArgumentError, "max_samples must be a positive integer"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
@max_samples = max_samples
|
|
42
|
+
@output = output
|
|
43
|
+
@running = false
|
|
44
|
+
@stalls = 0
|
|
45
|
+
@error = nil
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @attribute [Float] The minimum execution duration before reporting a stall.
|
|
49
|
+
attr_reader :stall_threshold
|
|
50
|
+
|
|
51
|
+
# @attribute [Float] The delay between samples.
|
|
52
|
+
attr_reader :sample_interval
|
|
53
|
+
|
|
54
|
+
# @attribute [Integer] The number of reports written.
|
|
55
|
+
attr_reader :stalls
|
|
56
|
+
|
|
57
|
+
# @attribute [StandardError | Nil] The sampling or reporting failure, retained until monitoring starts again.
|
|
58
|
+
attr_reader :error
|
|
59
|
+
|
|
60
|
+
# Start monitoring application fibers on the calling thread.
|
|
61
|
+
# @returns [Watchdog | false] Self, or false if already running.
|
|
62
|
+
def start
|
|
63
|
+
return false if @running
|
|
64
|
+
|
|
65
|
+
@thread = Thread.current
|
|
66
|
+
@pid = Process.pid
|
|
67
|
+
@mutex = Mutex.new
|
|
68
|
+
@condition = ConditionVariable.new
|
|
69
|
+
@running = true
|
|
70
|
+
@execution = nil
|
|
71
|
+
@error = nil
|
|
72
|
+
|
|
73
|
+
# When started on the event loop, distinguish it from blocking application fibers:
|
|
74
|
+
@loop = Fiber.current if Fiber.current.blocking?
|
|
75
|
+
@tracepoint = TracePoint.new(:fiber_switch){record_execution}
|
|
76
|
+
@tracepoint.enable(target_thread: @thread)
|
|
77
|
+
record_execution
|
|
78
|
+
|
|
79
|
+
@watchdog = Thread.new{watch}
|
|
80
|
+
self
|
|
81
|
+
rescue Exception
|
|
82
|
+
stop
|
|
83
|
+
raise
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Stop monitoring and wait for the sampling thread to finish.
|
|
87
|
+
# @returns [Watchdog | false] Self, or false if already stopped.
|
|
88
|
+
def stop
|
|
89
|
+
return false unless @running
|
|
90
|
+
|
|
91
|
+
@tracepoint&.disable
|
|
92
|
+
|
|
93
|
+
if @pid == Process.pid
|
|
94
|
+
@mutex.synchronize do
|
|
95
|
+
@running = false
|
|
96
|
+
@condition.broadcast
|
|
97
|
+
end
|
|
98
|
+
@watchdog&.join
|
|
99
|
+
else
|
|
100
|
+
# The sampling thread does not survive fork; avoid inherited synchronization:
|
|
101
|
+
@running = false
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
self
|
|
105
|
+
ensure
|
|
106
|
+
@watchdog = @thread = @execution = @loop = @tracepoint = nil
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
private
|
|
110
|
+
|
|
111
|
+
def now
|
|
112
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def record_execution
|
|
116
|
+
fiber = Fiber.current
|
|
117
|
+
idle = @loop ? fiber.equal?(@loop) : fiber.blocking?
|
|
118
|
+
|
|
119
|
+
@mutex.synchronize do
|
|
120
|
+
@execution = idle ? nil : [fiber, now]
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def wait
|
|
125
|
+
@mutex.synchronize do
|
|
126
|
+
@condition.wait(@mutex, @sample_interval) if @running
|
|
127
|
+
@running
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def watch
|
|
132
|
+
execution = nil
|
|
133
|
+
samples = []
|
|
134
|
+
next_report = @stall_threshold
|
|
135
|
+
|
|
136
|
+
while wait
|
|
137
|
+
current = @mutex.synchronize{@execution}
|
|
138
|
+
unless current.equal?(execution)
|
|
139
|
+
execution = current
|
|
140
|
+
samples.clear
|
|
141
|
+
next_report = @stall_threshold
|
|
142
|
+
end
|
|
143
|
+
next unless execution
|
|
144
|
+
|
|
145
|
+
backtrace = @thread.backtrace
|
|
146
|
+
duration = now - execution[1]
|
|
147
|
+
|
|
148
|
+
# Discard samples if the target switched fibers while capturing its stack:
|
|
149
|
+
next unless backtrace && @mutex.synchronize{@execution.equal?(execution)}
|
|
150
|
+
|
|
151
|
+
samples << {elapsed: duration, backtrace: backtrace}
|
|
152
|
+
samples.shift if samples.size > @max_samples
|
|
153
|
+
|
|
154
|
+
if duration >= next_report
|
|
155
|
+
report(execution[0], duration, samples)
|
|
156
|
+
@stalls += 1
|
|
157
|
+
next_report = duration + @stall_threshold
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
rescue StandardError => error
|
|
161
|
+
# A diagnostic failure must not escape into the monitored application:
|
|
162
|
+
@error = error
|
|
163
|
+
@tracepoint.disable
|
|
164
|
+
warn_failure(error)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def warn_failure(error)
|
|
168
|
+
warn "Fiber::Profiler::Watchdog disabled: #{error.class}: #{error.message}"
|
|
169
|
+
rescue StandardError
|
|
170
|
+
# The diagnostic destination may also be unavailable; retain the original error:
|
|
171
|
+
nil
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def report(fiber, duration, samples)
|
|
175
|
+
if @output.respond_to?(:tty?) && @output.tty?
|
|
176
|
+
message = "## Fiber stalled for at least %.3f seconds (watchdog, pid=%d, thread=%d, fiber=%d)\n" % [duration, @pid, @thread.object_id, fiber.object_id]
|
|
177
|
+
message << samples.map{|sample| sample[:backtrace].join("\n")}.join("\n\n") << "\n"
|
|
178
|
+
else
|
|
179
|
+
message = JSON.generate(mode: "watchdog", pid: @pid, thread_id: @thread.object_id, fiber_id: fiber.object_id, duration: duration, samples: samples) << "\n"
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
@output.write(message)
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
data/lib/fiber/profiler.rb
CHANGED
|
@@ -9,11 +9,25 @@ require_relative "profiler/capture"
|
|
|
9
9
|
module Fiber::Profiler
|
|
10
10
|
# The default profiler to use, if any.
|
|
11
11
|
#
|
|
12
|
-
#
|
|
12
|
+
# Set `FIBER_PROFILER` to `watchdog`, `capture`, or `false`. When unset,
|
|
13
|
+
# the legacy `FIBER_PROFILER_CAPTURE=true` setting enables capture mode.
|
|
13
14
|
#
|
|
14
|
-
# @returns [Capture | Nil]
|
|
15
|
+
# @returns [Capture | Watchdog | Nil]
|
|
16
|
+
# @raises [ArgumentError] If the requested mode is unknown.
|
|
15
17
|
def self.default
|
|
16
|
-
|
|
18
|
+
case mode = ENV["FIBER_PROFILER"]
|
|
19
|
+
when nil
|
|
20
|
+
Capture.default
|
|
21
|
+
when "capture"
|
|
22
|
+
Capture.new
|
|
23
|
+
when "watchdog"
|
|
24
|
+
require_relative "profiler/watchdog"
|
|
25
|
+
Watchdog.default
|
|
26
|
+
when "false"
|
|
27
|
+
nil
|
|
28
|
+
else
|
|
29
|
+
raise ArgumentError, "Unknown FIBER_PROFILER mode: #{mode.inspect} (expected watchdog, capture, or false)"
|
|
30
|
+
end
|
|
17
31
|
end
|
|
18
32
|
|
|
19
33
|
# Execute the given block with the {default} profiler, if any.
|
data/readme.md
CHANGED
|
@@ -12,12 +12,20 @@ Migrating existing applications to the event loop can be tricky. One of the most
|
|
|
12
12
|
|
|
13
13
|
Please see the [project documentation](https://socketry.github.io/fiber-profiler/) for more details.
|
|
14
14
|
|
|
15
|
-
- [Getting Started](https://socketry.github.io/fiber-profiler/guides/getting-started/index) - This guide explains how to
|
|
15
|
+
- [Getting Started](https://socketry.github.io/fiber-profiler/guides/getting-started/index) - This guide explains how to install the fiber profiler and choose a mode for diagnosing event-loop stalls.
|
|
16
|
+
|
|
17
|
+
- [Watchdog Mode](https://socketry.github.io/fiber-profiler/guides/watchdog-mode/index) - This guide explains how to sample ongoing fiber stalls with the watchdog profiler.
|
|
18
|
+
|
|
19
|
+
- [Capture Mode](https://socketry.github.io/fiber-profiler/guides/capture-mode/index) - This guide explains how to trace fiber execution and analyze call timings with the capture profiler.
|
|
16
20
|
|
|
17
21
|
## Releases
|
|
18
22
|
|
|
19
23
|
Please see the [project releases](https://socketry.github.io/fiber-profiler/releases/index) for all releases.
|
|
20
24
|
|
|
25
|
+
### v0.7.0
|
|
26
|
+
|
|
27
|
+
- Sign releases with the Socketry Ruby Gems certificate.
|
|
28
|
+
|
|
21
29
|
### v0.6.0
|
|
22
30
|
|
|
23
31
|
- Fixed compatibility with `Process.fork`.
|
|
@@ -50,12 +58,14 @@ bundle exec sus
|
|
|
50
58
|
|
|
51
59
|
### Making Releases
|
|
52
60
|
|
|
53
|
-
To
|
|
61
|
+
To prepare a release branch and open a pull request from an up-to-date `main`:
|
|
54
62
|
|
|
55
63
|
``` shell
|
|
56
|
-
bundle exec bake gem:release:patch # or minor or major
|
|
64
|
+
bundle exec bake gem:github:release:patch # or minor or major
|
|
57
65
|
```
|
|
58
66
|
|
|
67
|
+
After the release PR is merged, GitHub Actions automatically publishes the verified gem through the `rubygems` environment. See [bake-gem-github](https://github.com/socketry/bake-gem-github) for setup, remote releases, and recovery.
|
|
68
|
+
|
|
59
69
|
### Developer Certificate of Origin
|
|
60
70
|
|
|
61
71
|
In order to protect users of this project, we require all contributors to comply with the [Developer Certificate of Origin](https://developercertificate.org/). This ensures that all contributions are properly licensed and attributed.
|
data/release.cert
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
|
2
|
+
MIIEFDCCAnygAwIBAgIBATANBgkqhkiG9w0BAQsFADAwMREwDwYDVQQKDAhTb2Nr
|
|
3
|
+
ZXRyeTEbMBkGA1UEAwwSU29ja2V0cnkgUnVieSBHZW1zMB4XDTI2MDkyMTA3NTgx
|
|
4
|
+
NFoXDTI3MDkyMTA3NTgxNFowMDERMA8GA1UECgwIU29ja2V0cnkxGzAZBgNVBAMM
|
|
5
|
+
ElNvY2tldHJ5IFJ1YnkgR2VtczCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoC
|
|
6
|
+
ggGBAM/QgjVgDzDo/xJEQoFvAzcVFP7msnswkhPJB2UDsbxXCZmers5jV512TM5s
|
|
7
|
+
NwLHfzJiC4DcI5ax9ZYKM9Q+dS21YYagNqjtg3YPyDqR6phoibEA0VoMuInUQW68
|
|
8
|
+
i5OkCJzYKGD2pYYVZnuFNqyM2ECUvXh/fBmvPoHbncAHhWPaCBH8mQJ8sRNc6+RV
|
|
9
|
+
SoZZu1Yo/aW1zQ+SpZwad7s1ZmigfDNHKhJ22KwZHk+/5Zw1QXVfcajjswV0Mm2P
|
|
10
|
+
irgjKN3fyNVkwlly63EBlR4VydT+g9QtJtqh7ee0ThhI+v4wYovf4dbLNcEBTHu1
|
|
11
|
+
x1pGbXGx/2fBAST5eVwaXIx1V8VU22AJcBk1H2o5/1F0HfbD2HQjmFOA112KUwBW
|
|
12
|
+
9TtspZD3g6rCQFX53XvL9h6j2y1ukl7s5AdEzOe/x6r1MO8tSDtgcYoF89wgF/88
|
|
13
|
+
Q/Eski0FckQ+znfESZcFcpzs2sPDoYW6SNVX4tZcNty85Y6WBHfHKio14x63PeyC
|
|
14
|
+
CG1gMwIDAQABozkwNzAJBgNVHRMEAjAAMAsGA1UdDwQEAwIEsDAdBgNVHQ4EFgQU
|
|
15
|
+
SuMc0x24Xshtaa8SQVkBr2Z5WzIwDQYJKoZIhvcNAQELBQADggGBABI62ey5lDWm
|
|
16
|
+
j8jsTQaNBMBb7LbKS3XkBwyL1UNGEwlicp399rJDeuNgVSHqBCZ/nE4yleNjgW9Y
|
|
17
|
+
N6DKdxXCvXmFUoHvUINeAvThHxmlEGhSfXl1x55xHEig0rEP4jgdjnYdQI4fqeOM
|
|
18
|
+
zIDN0F3ruArke4Y62SQgWVN7vspdAA41hBIRl1vsvs0KWG8DIVQ8cmR+TG6zjOmK
|
|
19
|
+
iUiUZGNPnStV1O1xW83c+Ba4Am0krP6/forxDPhRvTehqkVYvMdPfYcICNb5IM58
|
|
20
|
+
5vaYvuj5AtfZuZWN0F/0KtSphhNC17rh6h8QmuYdMJ1clvlrkiEaO1UjPVwjgtxC
|
|
21
|
+
x/ARh5X6q0/YTXl4r7EfSJEeqW7qhgu9EUj3mGIPHYdX0Dqih/NgK20F3GvQtTfB
|
|
22
|
+
7sse0duKBHXONYVwKAsceUVvqtCfyF608bCilCkbhw3QaInRj+BkRQKJ5Tuj2PZR
|
|
23
|
+
76a/hwaV2wWn5RtAFBaUQzQdo/xTqJ7IkwaFjOZe/QyvyoRjCK9Rdg==
|
|
24
|
+
-----END CERTIFICATE-----
|
data/releases.md
CHANGED
data.tar.gz.sig
CHANGED
|
Binary file
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: fiber-profiler
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Samuel Williams
|
|
@@ -9,43 +9,55 @@ bindir: bin
|
|
|
9
9
|
cert_chain:
|
|
10
10
|
- |
|
|
11
11
|
-----BEGIN CERTIFICATE-----
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
JkxfQWC+jBVeG9ZtPhQgZpfhvh+6hMhraUYRQ6XGyvBqEUe+yo6DKIT3MtGE2+CP
|
|
35
|
-
eX9i9ZWBydWb8/rvmwmX2kkcBbX0hZS1rcR593hGc61JR6lvkGYQ2MYskBveyaxt
|
|
36
|
-
Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
|
|
37
|
-
voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
|
|
12
|
+
MIIEFDCCAnygAwIBAgIBATANBgkqhkiG9w0BAQsFADAwMREwDwYDVQQKDAhTb2Nr
|
|
13
|
+
ZXRyeTEbMBkGA1UEAwwSU29ja2V0cnkgUnVieSBHZW1zMB4XDTI2MDkyMTA3NTgx
|
|
14
|
+
NFoXDTI3MDkyMTA3NTgxNFowMDERMA8GA1UECgwIU29ja2V0cnkxGzAZBgNVBAMM
|
|
15
|
+
ElNvY2tldHJ5IFJ1YnkgR2VtczCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoC
|
|
16
|
+
ggGBAM/QgjVgDzDo/xJEQoFvAzcVFP7msnswkhPJB2UDsbxXCZmers5jV512TM5s
|
|
17
|
+
NwLHfzJiC4DcI5ax9ZYKM9Q+dS21YYagNqjtg3YPyDqR6phoibEA0VoMuInUQW68
|
|
18
|
+
i5OkCJzYKGD2pYYVZnuFNqyM2ECUvXh/fBmvPoHbncAHhWPaCBH8mQJ8sRNc6+RV
|
|
19
|
+
SoZZu1Yo/aW1zQ+SpZwad7s1ZmigfDNHKhJ22KwZHk+/5Zw1QXVfcajjswV0Mm2P
|
|
20
|
+
irgjKN3fyNVkwlly63EBlR4VydT+g9QtJtqh7ee0ThhI+v4wYovf4dbLNcEBTHu1
|
|
21
|
+
x1pGbXGx/2fBAST5eVwaXIx1V8VU22AJcBk1H2o5/1F0HfbD2HQjmFOA112KUwBW
|
|
22
|
+
9TtspZD3g6rCQFX53XvL9h6j2y1ukl7s5AdEzOe/x6r1MO8tSDtgcYoF89wgF/88
|
|
23
|
+
Q/Eski0FckQ+znfESZcFcpzs2sPDoYW6SNVX4tZcNty85Y6WBHfHKio14x63PeyC
|
|
24
|
+
CG1gMwIDAQABozkwNzAJBgNVHRMEAjAAMAsGA1UdDwQEAwIEsDAdBgNVHQ4EFgQU
|
|
25
|
+
SuMc0x24Xshtaa8SQVkBr2Z5WzIwDQYJKoZIhvcNAQELBQADggGBABI62ey5lDWm
|
|
26
|
+
j8jsTQaNBMBb7LbKS3XkBwyL1UNGEwlicp399rJDeuNgVSHqBCZ/nE4yleNjgW9Y
|
|
27
|
+
N6DKdxXCvXmFUoHvUINeAvThHxmlEGhSfXl1x55xHEig0rEP4jgdjnYdQI4fqeOM
|
|
28
|
+
zIDN0F3ruArke4Y62SQgWVN7vspdAA41hBIRl1vsvs0KWG8DIVQ8cmR+TG6zjOmK
|
|
29
|
+
iUiUZGNPnStV1O1xW83c+Ba4Am0krP6/forxDPhRvTehqkVYvMdPfYcICNb5IM58
|
|
30
|
+
5vaYvuj5AtfZuZWN0F/0KtSphhNC17rh6h8QmuYdMJ1clvlrkiEaO1UjPVwjgtxC
|
|
31
|
+
x/ARh5X6q0/YTXl4r7EfSJEeqW7qhgu9EUj3mGIPHYdX0Dqih/NgK20F3GvQtTfB
|
|
32
|
+
7sse0duKBHXONYVwKAsceUVvqtCfyF608bCilCkbhw3QaInRj+BkRQKJ5Tuj2PZR
|
|
33
|
+
76a/hwaV2wWn5RtAFBaUQzQdo/xTqJ7IkwaFjOZe/QyvyoRjCK9Rdg==
|
|
38
34
|
-----END CERTIFICATE-----
|
|
39
35
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
40
|
-
dependencies:
|
|
36
|
+
dependencies:
|
|
37
|
+
- !ruby/object:Gem::Dependency
|
|
38
|
+
name: json
|
|
39
|
+
requirement: !ruby/object:Gem::Requirement
|
|
40
|
+
requirements:
|
|
41
|
+
- - ">="
|
|
42
|
+
- !ruby/object:Gem::Version
|
|
43
|
+
version: '0'
|
|
44
|
+
type: :runtime
|
|
45
|
+
prerelease: false
|
|
46
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
47
|
+
requirements:
|
|
48
|
+
- - ">="
|
|
49
|
+
- !ruby/object:Gem::Version
|
|
50
|
+
version: '0'
|
|
41
51
|
executables: []
|
|
42
52
|
extensions:
|
|
43
53
|
- ext/extconf.rb
|
|
44
54
|
extra_rdoc_files: []
|
|
45
55
|
files:
|
|
46
56
|
- bake/fiber/profiler/analyze.rb
|
|
57
|
+
- context/capture-mode.md
|
|
47
58
|
- context/getting-started.md
|
|
48
59
|
- context/index.yaml
|
|
60
|
+
- context/watchdog-mode.md
|
|
49
61
|
- ext/extconf.rb
|
|
50
62
|
- ext/fiber/profiler/capture.c
|
|
51
63
|
- ext/fiber/profiler/capture.h
|
|
@@ -60,9 +72,12 @@ files:
|
|
|
60
72
|
- lib/fiber/profiler/capture.rb
|
|
61
73
|
- lib/fiber/profiler/fork_handler.rb
|
|
62
74
|
- lib/fiber/profiler/native.rb
|
|
75
|
+
- lib/fiber/profiler/thread_local.rb
|
|
63
76
|
- lib/fiber/profiler/version.rb
|
|
77
|
+
- lib/fiber/profiler/watchdog.rb
|
|
64
78
|
- license.md
|
|
65
79
|
- readme.md
|
|
80
|
+
- release.cert
|
|
66
81
|
- releases.md
|
|
67
82
|
homepage: https://github.com/socketry/fiber-profiler
|
|
68
83
|
licenses:
|
|
@@ -84,7 +99,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
84
99
|
- !ruby/object:Gem::Version
|
|
85
100
|
version: '0'
|
|
86
101
|
requirements: []
|
|
87
|
-
rubygems_version: 4.0.
|
|
102
|
+
rubygems_version: 4.0.21
|
|
88
103
|
specification_version: 4
|
|
89
104
|
summary: A fiber stall profiler.
|
|
90
105
|
test_files: []
|
metadata.gz.sig
CHANGED
|
@@ -1 +1,3 @@
|
|
|
1
|
-
|
|
1
|
+
�I(-r[���-�������������/Bkh�ѻ��.��Ԗx�Y�h�3�+,T.B����`*VK�:�$Y�Z� F6�B�3��&9�{���5ҏ��8��Ϲ�!xוE���/h�҂&h�u�I�H�."�D�j�T���߫~X͠��c�.����L��x�ݲ�U_�KS*�x��hF�M�Q����"v�iC~���`GW
|
|
2
|
+
5�t���P@8�G�Z���J�FC�w��L2��}C]���7��U1Fv.u��1_@�e���V��*| :��~Ͻ3�d��å�̎�p�=]]Jx#�n�@���5PT���̾
|
|
3
|
+
|�2���io5EB#��K�[ H&\��G�Œ��PQ������s�?/��<u����mမ
|