fiber_audit 0.1.0 → 0.2.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
- data/.fiber-audit.example.yml +19 -0
- data/ARCHITECTURE.md +726 -0
- data/CHANGELOG.md +41 -0
- data/LICENSE +201 -0
- data/README.md +66 -8
- data/lib/fiber_audit/cli.rb +87 -2
- data/lib/fiber_audit/configuration.rb +106 -6
- data/lib/fiber_audit/errors.rb +2 -0
- data/lib/fiber_audit/operation_vocabulary.rb +42 -0
- data/lib/fiber_audit/reporters/text.rb +1 -1
- data/lib/fiber_audit/runtime/active_operations.rb +146 -0
- data/lib/fiber_audit/runtime/boot.rb +83 -0
- data/lib/fiber_audit/runtime/clock.rb +35 -0
- data/lib/fiber_audit/runtime/environment.rb +289 -0
- data/lib/fiber_audit/runtime/event.rb +86 -0
- data/lib/fiber_audit/runtime/execution_context.rb +89 -0
- data/lib/fiber_audit/runtime/heartbeat.rb +113 -0
- data/lib/fiber_audit/runtime/jsonl/schema.rb +312 -0
- data/lib/fiber_audit/runtime/jsonl/writer.rb +122 -0
- data/lib/fiber_audit/runtime/lifecycle.rb +343 -0
- data/lib/fiber_audit/runtime/limits.rb +102 -0
- data/lib/fiber_audit/runtime/location.rb +44 -0
- data/lib/fiber_audit/runtime/policy.rb +121 -0
- data/lib/fiber_audit/runtime/probes/base.rb +333 -0
- data/lib/fiber_audit/runtime/probes/http.rb +80 -0
- data/lib/fiber_audit/runtime/probes/io_select.rb +50 -0
- data/lib/fiber_audit/runtime/probes/registry.rb +156 -0
- data/lib/fiber_audit/runtime/probes/socket.rb +76 -0
- data/lib/fiber_audit/runtime/probes/subprocess.rb +83 -0
- data/lib/fiber_audit/runtime/probes/synchronization.rb +58 -0
- data/lib/fiber_audit/runtime/probes/thread_state.rb +39 -0
- data/lib/fiber_audit/runtime/probes/thread_wait.rb +25 -0
- data/lib/fiber_audit/runtime/rails_integration.rb +320 -0
- data/lib/fiber_audit/runtime/recorder.rb +333 -0
- data/lib/fiber_audit/runtime/redactor.rb +102 -0
- data/lib/fiber_audit/runtime/sampler.rb +26 -0
- data/lib/fiber_audit/runtime/scheduler_observer.rb +128 -0
- data/lib/fiber_audit/runtime/session.rb +112 -0
- data/lib/fiber_audit/runtime/supervisor.rb +114 -0
- data/lib/fiber_audit/runtime/validation.rb +68 -0
- data/lib/fiber_audit/runtime/watchdog.rb +479 -0
- data/lib/fiber_audit/runtime/watchdog_policy.rb +64 -0
- data/lib/fiber_audit/runtime.rb +37 -0
- data/lib/fiber_audit/static/rules/blocking_subprocess.rb +3 -8
- data/lib/fiber_audit/static/rules/direct_socket.rb +2 -3
- data/lib/fiber_audit/static/rules/io_select.rb +2 -4
- data/lib/fiber_audit/static/rules/net_http_in_request.rb +3 -5
- data/lib/fiber_audit/static/rules/synchronization.rb +2 -6
- data/lib/fiber_audit/static/rules/thread_current_state.rb +3 -2
- data/lib/fiber_audit/static/rules/thread_join.rb +3 -2
- data/lib/fiber_audit/version.rb +1 -1
- data/lib/fiber_audit.rb +1 -0
- metadata +38 -2
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'validation'
|
|
4
|
+
|
|
5
|
+
module FiberAudit
|
|
6
|
+
module Runtime
|
|
7
|
+
WatchdogPolicy = Data.define(
|
|
8
|
+
:enabled,
|
|
9
|
+
:heartbeat_interval_ms,
|
|
10
|
+
:stall_threshold_ms,
|
|
11
|
+
:max_frames
|
|
12
|
+
) do
|
|
13
|
+
def initialize(**values)
|
|
14
|
+
unknown = values.keys - WatchdogPolicy::DEFAULTS.keys
|
|
15
|
+
raise RuntimeContractError, "unknown watchdog policy field: #{unknown.first}" unless unknown.empty?
|
|
16
|
+
|
|
17
|
+
fields = WatchdogPolicy::DEFAULTS.merge(values)
|
|
18
|
+
raise RuntimeContractError, 'enabled must be a Boolean' unless [true, false].include?(fields[:enabled])
|
|
19
|
+
|
|
20
|
+
limits = WatchdogPolicy::LIMITS.to_h do |name, range|
|
|
21
|
+
value = fields.fetch(name)
|
|
22
|
+
unless value.is_a?(Integer) && range.cover?(value)
|
|
23
|
+
raise RuntimeContractError, "#{name} must be an Integer in #{range}"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
[name, value]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
super(enabled: fields[:enabled], **limits)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def enabled?
|
|
33
|
+
enabled
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def heartbeat_interval_ns
|
|
37
|
+
heartbeat_interval_ms * WatchdogPolicy::NANOSECONDS_PER_MILLISECOND
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def stall_threshold_ns
|
|
41
|
+
stall_threshold_ms * WatchdogPolicy::NANOSECONDS_PER_MILLISECOND
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def stalled?(age_ns:)
|
|
45
|
+
age = Validation.integer(age_ns, 'heartbeat age')
|
|
46
|
+
age > stall_threshold_ns
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
WatchdogPolicy.const_set(:NANOSECONDS_PER_MILLISECOND, 1_000_000)
|
|
51
|
+
WatchdogPolicy.const_set(:DEFAULTS, {
|
|
52
|
+
enabled: true,
|
|
53
|
+
heartbeat_interval_ms: 25,
|
|
54
|
+
stall_threshold_ms: 100,
|
|
55
|
+
max_frames: 20
|
|
56
|
+
}.freeze)
|
|
57
|
+
WatchdogPolicy.const_set(:LIMITS, {
|
|
58
|
+
heartbeat_interval_ms: 1..60_000,
|
|
59
|
+
stall_threshold_ms: 1..600_000,
|
|
60
|
+
max_frames: 0..100
|
|
61
|
+
}.freeze)
|
|
62
|
+
WatchdogPolicy.const_set(:DISABLED, WatchdogPolicy.new(enabled: false))
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'version'
|
|
4
|
+
require_relative 'errors'
|
|
5
|
+
require_relative 'operation_vocabulary'
|
|
6
|
+
require_relative 'execution_context'
|
|
7
|
+
require_relative 'runtime/validation'
|
|
8
|
+
require_relative 'runtime/policy'
|
|
9
|
+
require_relative 'runtime/watchdog_policy'
|
|
10
|
+
require_relative 'runtime/location'
|
|
11
|
+
require_relative 'runtime/event'
|
|
12
|
+
require_relative 'runtime/session'
|
|
13
|
+
require_relative 'runtime/redactor'
|
|
14
|
+
require_relative 'runtime/clock'
|
|
15
|
+
require_relative 'runtime/sampler'
|
|
16
|
+
require_relative 'runtime/limits'
|
|
17
|
+
require_relative 'runtime/jsonl/schema'
|
|
18
|
+
require_relative 'runtime/jsonl/writer'
|
|
19
|
+
require_relative 'runtime/recorder'
|
|
20
|
+
require_relative 'runtime/execution_context'
|
|
21
|
+
require_relative 'runtime/rails_integration'
|
|
22
|
+
require_relative 'runtime/environment'
|
|
23
|
+
require_relative 'runtime/active_operations'
|
|
24
|
+
require_relative 'runtime/heartbeat'
|
|
25
|
+
require_relative 'runtime/watchdog'
|
|
26
|
+
require_relative 'runtime/scheduler_observer'
|
|
27
|
+
require_relative 'runtime/probes/base'
|
|
28
|
+
require_relative 'runtime/probes/subprocess'
|
|
29
|
+
require_relative 'runtime/probes/thread_wait'
|
|
30
|
+
require_relative 'runtime/probes/synchronization'
|
|
31
|
+
require_relative 'runtime/probes/thread_state'
|
|
32
|
+
require_relative 'runtime/probes/io_select'
|
|
33
|
+
require_relative 'runtime/probes/socket'
|
|
34
|
+
require_relative 'runtime/probes/http'
|
|
35
|
+
require_relative 'runtime/probes/registry'
|
|
36
|
+
require_relative 'runtime/lifecycle'
|
|
37
|
+
require_relative 'runtime/supervisor'
|
|
@@ -4,6 +4,7 @@ require_relative 'base'
|
|
|
4
4
|
require_relative '../../findings/evidence'
|
|
5
5
|
require_relative '../../correlation/fingerprint'
|
|
6
6
|
require_relative '../../findings/finding'
|
|
7
|
+
require_relative '../../operation_vocabulary'
|
|
7
8
|
|
|
8
9
|
module FiberAudit
|
|
9
10
|
module Static
|
|
@@ -14,14 +15,8 @@ module FiberAudit
|
|
|
14
15
|
default_confidence :high
|
|
15
16
|
description 'Blocking subprocess call in the fiber scheduler path'
|
|
16
17
|
|
|
17
|
-
TARGETS =
|
|
18
|
-
|
|
19
|
-
'Open3' => %i[capture2 capture2e capture3 pipeline].freeze,
|
|
20
|
-
'IO' => %i[popen].freeze,
|
|
21
|
-
'Process' => %i[waitall detach].freeze
|
|
22
|
-
}.freeze
|
|
23
|
-
|
|
24
|
-
BARE_KERNEL_METHODS = %i[system exec spawn].freeze
|
|
18
|
+
TARGETS = OperationVocabulary::FA1001_TARGETS
|
|
19
|
+
BARE_KERNEL_METHODS = OperationVocabulary::FA1001_KERNEL_METHODS
|
|
25
20
|
|
|
26
21
|
MESSAGE = 'Subprocess operation may block the thread running the fiber scheduler.'
|
|
27
22
|
REMEDIATION = 'Move long-running subprocess work outside the request path, or verify scheduler behaviour under load.'
|
|
@@ -4,6 +4,7 @@ require_relative 'base'
|
|
|
4
4
|
require_relative '../../findings/evidence'
|
|
5
5
|
require_relative '../../correlation/fingerprint'
|
|
6
6
|
require_relative '../../findings/finding'
|
|
7
|
+
require_relative '../../operation_vocabulary'
|
|
7
8
|
|
|
8
9
|
module FiberAudit
|
|
9
10
|
module Static
|
|
@@ -19,9 +20,7 @@ module FiberAudit
|
|
|
19
20
|
TITLE = 'Direct socket creation'
|
|
20
21
|
CATEGORY = :network
|
|
21
22
|
|
|
22
|
-
EXACT =
|
|
23
|
-
TCPSocket TCPServer UDPSocket UNIXSocket UNIXServer Socket IPSocket
|
|
24
|
-
].freeze
|
|
23
|
+
EXACT = OperationVocabulary::FA1006_EXACT
|
|
25
24
|
|
|
26
25
|
MESSAGE = 'Direct socket use may bypass scheduler-aware networking ' \
|
|
27
26
|
'and block the scheduler thread.'
|
|
@@ -4,6 +4,7 @@ require_relative 'base'
|
|
|
4
4
|
require_relative '../../findings/evidence'
|
|
5
5
|
require_relative '../../correlation/fingerprint'
|
|
6
6
|
require_relative '../../findings/finding'
|
|
7
|
+
require_relative '../../operation_vocabulary'
|
|
7
8
|
|
|
8
9
|
module FiberAudit
|
|
9
10
|
module Static
|
|
@@ -22,10 +23,7 @@ module FiberAudit
|
|
|
22
23
|
MESSAGE = 'IO.select may bypass scheduler-aware I/O and block the thread running the fiber scheduler.'
|
|
23
24
|
REMEDIATION = 'Use scheduler-aware I/O APIs or allow the active Fiber scheduler to manage readiness.'
|
|
24
25
|
|
|
25
|
-
TARGETS =
|
|
26
|
-
'IO' => :select,
|
|
27
|
-
'Kernel' => :select
|
|
28
|
-
}.freeze
|
|
26
|
+
TARGETS = OperationVocabulary::FA1005_TARGETS
|
|
29
27
|
|
|
30
28
|
class << self
|
|
31
29
|
def title = TITLE
|
|
@@ -4,6 +4,7 @@ require_relative 'base'
|
|
|
4
4
|
require_relative '../../findings/evidence'
|
|
5
5
|
require_relative '../../correlation/fingerprint'
|
|
6
6
|
require_relative '../../findings/finding'
|
|
7
|
+
require_relative '../../operation_vocabulary'
|
|
7
8
|
|
|
8
9
|
module FiberAudit
|
|
9
10
|
module Static
|
|
@@ -22,11 +23,8 @@ module FiberAudit
|
|
|
22
23
|
MESSAGE = 'Synchronous HTTP activity in a request-like context may block the thread running the fiber scheduler.'
|
|
23
24
|
REMEDIATION = 'Use a scheduler-aware HTTP client, or move outbound HTTP work outside the request path.'
|
|
24
25
|
|
|
25
|
-
NET_HTTP_METHODS =
|
|
26
|
-
URI_METHODS =
|
|
27
|
-
'URI' => :open,
|
|
28
|
-
'OpenURI' => :open_uri
|
|
29
|
-
}.freeze
|
|
26
|
+
NET_HTTP_METHODS = OperationVocabulary::FA1007_NET_HTTP_METHODS
|
|
27
|
+
URI_METHODS = OperationVocabulary::FA1007_URI_METHODS
|
|
30
28
|
EMIT_CONTEXTS = %i[request middleware websocket callback].freeze
|
|
31
29
|
|
|
32
30
|
def analyze(call_sites:)
|
|
@@ -4,6 +4,7 @@ require_relative 'base'
|
|
|
4
4
|
require_relative '../../findings/evidence'
|
|
5
5
|
require_relative '../../correlation/fingerprint'
|
|
6
6
|
require_relative '../../findings/finding'
|
|
7
|
+
require_relative '../../operation_vocabulary'
|
|
7
8
|
|
|
8
9
|
module FiberAudit
|
|
9
10
|
module Static
|
|
@@ -16,12 +17,7 @@ module FiberAudit
|
|
|
16
17
|
|
|
17
18
|
RULE_TITLE = 'Thread synchronization'
|
|
18
19
|
RULE_CATEGORY = :synchronization
|
|
19
|
-
TARGETS =
|
|
20
|
-
'Mutex' => %i[lock synchronize try_lock],
|
|
21
|
-
'ConditionVariable' => %i[wait],
|
|
22
|
-
'Monitor' => %i[synchronize],
|
|
23
|
-
'MonitorMixin' => %i[synchronize]
|
|
24
|
-
}.freeze
|
|
20
|
+
TARGETS = OperationVocabulary::FA1003_TARGETS
|
|
25
21
|
TRY_LOCK_MSG = 'Mutex.try_lock is non-blocking but may indicate ' \
|
|
26
22
|
'thread-oriented synchronization in fiber-scheduled code.'
|
|
27
23
|
NORMAL_MSG = 'Synchronization operation may block the thread running the fiber scheduler.'
|
|
@@ -4,6 +4,7 @@ require_relative 'base'
|
|
|
4
4
|
require_relative '../../findings/evidence'
|
|
5
5
|
require_relative '../../correlation/fingerprint'
|
|
6
6
|
require_relative '../../findings/finding'
|
|
7
|
+
require_relative '../../operation_vocabulary'
|
|
7
8
|
|
|
8
9
|
module FiberAudit
|
|
9
10
|
module Static
|
|
@@ -20,8 +21,8 @@ module FiberAudit
|
|
|
20
21
|
MESSAGE = 'Thread-local state may be shared across fibers and leak request-local data.'
|
|
21
22
|
REMEDIATION = 'Use fiber-local or framework-provided request-local state instead of Thread thread variables.'
|
|
22
23
|
|
|
23
|
-
THREAD_VARIABLE_METHODS =
|
|
24
|
-
INDEX_METHODS =
|
|
24
|
+
THREAD_VARIABLE_METHODS = OperationVocabulary::FA1004_THREAD_VARIABLE_METHODS
|
|
25
|
+
INDEX_METHODS = OperationVocabulary::FA1004_INDEX_METHODS
|
|
25
26
|
|
|
26
27
|
def analyze(call_sites:)
|
|
27
28
|
findings = []
|
|
@@ -4,6 +4,7 @@ require_relative 'base'
|
|
|
4
4
|
require_relative '../../findings/evidence'
|
|
5
5
|
require_relative '../../correlation/fingerprint'
|
|
6
6
|
require_relative '../../findings/finding'
|
|
7
|
+
require_relative '../../operation_vocabulary'
|
|
7
8
|
|
|
8
9
|
module FiberAudit
|
|
9
10
|
module Static
|
|
@@ -30,8 +31,8 @@ module FiberAudit
|
|
|
30
31
|
MESSAGE = 'Waiting for a thread may block the thread running the fiber scheduler.'
|
|
31
32
|
REMEDIATION = 'Replace thread waits with scheduler-aware coordination, ' \
|
|
32
33
|
'or move the work outside the fiber-scheduled path.'
|
|
33
|
-
TARGET_METHODS =
|
|
34
|
-
CANONICAL_OPS =
|
|
34
|
+
TARGET_METHODS = OperationVocabulary::FA1002_METHODS
|
|
35
|
+
CANONICAL_OPS = OperationVocabulary::FA1002_OPERATIONS
|
|
35
36
|
DIRECT_CLASS_SOURCE = 'Thread'
|
|
36
37
|
|
|
37
38
|
def analyze(call_sites:)
|
data/lib/fiber_audit/version.rb
CHANGED
data/lib/fiber_audit.rb
CHANGED
|
@@ -9,6 +9,7 @@ require_relative 'fiber_audit/findings/evidence'
|
|
|
9
9
|
require_relative 'fiber_audit/correlation/fingerprint'
|
|
10
10
|
require_relative 'fiber_audit/findings/finding'
|
|
11
11
|
require_relative 'fiber_audit/findings/collection'
|
|
12
|
+
require_relative 'fiber_audit/runtime'
|
|
12
13
|
require_relative 'fiber_audit/configuration'
|
|
13
14
|
require_relative 'fiber_audit/suppressions/parser'
|
|
14
15
|
require_relative 'fiber_audit/suppressions/store'
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: fiber_audit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1
|
|
4
|
+
version: 0.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- FiberAudit Contributors
|
|
@@ -43,7 +43,9 @@ extensions: []
|
|
|
43
43
|
extra_rdoc_files: []
|
|
44
44
|
files:
|
|
45
45
|
- ".fiber-audit.example.yml"
|
|
46
|
+
- ARCHITECTURE.md
|
|
46
47
|
- CHANGELOG.md
|
|
48
|
+
- LICENSE
|
|
47
49
|
- README.md
|
|
48
50
|
- bin/fiber-audit
|
|
49
51
|
- lib/fiber_audit.rb
|
|
@@ -59,11 +61,45 @@ files:
|
|
|
59
61
|
- lib/fiber_audit/findings/finding.rb
|
|
60
62
|
- lib/fiber_audit/findings/location.rb
|
|
61
63
|
- lib/fiber_audit/findings/severity.rb
|
|
64
|
+
- lib/fiber_audit/operation_vocabulary.rb
|
|
62
65
|
- lib/fiber_audit/project.rb
|
|
63
66
|
- lib/fiber_audit/reporters/base.rb
|
|
64
67
|
- lib/fiber_audit/reporters/json.rb
|
|
65
68
|
- lib/fiber_audit/reporters/schema.rb
|
|
66
69
|
- lib/fiber_audit/reporters/text.rb
|
|
70
|
+
- lib/fiber_audit/runtime.rb
|
|
71
|
+
- lib/fiber_audit/runtime/active_operations.rb
|
|
72
|
+
- lib/fiber_audit/runtime/boot.rb
|
|
73
|
+
- lib/fiber_audit/runtime/clock.rb
|
|
74
|
+
- lib/fiber_audit/runtime/environment.rb
|
|
75
|
+
- lib/fiber_audit/runtime/event.rb
|
|
76
|
+
- lib/fiber_audit/runtime/execution_context.rb
|
|
77
|
+
- lib/fiber_audit/runtime/heartbeat.rb
|
|
78
|
+
- lib/fiber_audit/runtime/jsonl/schema.rb
|
|
79
|
+
- lib/fiber_audit/runtime/jsonl/writer.rb
|
|
80
|
+
- lib/fiber_audit/runtime/lifecycle.rb
|
|
81
|
+
- lib/fiber_audit/runtime/limits.rb
|
|
82
|
+
- lib/fiber_audit/runtime/location.rb
|
|
83
|
+
- lib/fiber_audit/runtime/policy.rb
|
|
84
|
+
- lib/fiber_audit/runtime/probes/base.rb
|
|
85
|
+
- lib/fiber_audit/runtime/probes/http.rb
|
|
86
|
+
- lib/fiber_audit/runtime/probes/io_select.rb
|
|
87
|
+
- lib/fiber_audit/runtime/probes/registry.rb
|
|
88
|
+
- lib/fiber_audit/runtime/probes/socket.rb
|
|
89
|
+
- lib/fiber_audit/runtime/probes/subprocess.rb
|
|
90
|
+
- lib/fiber_audit/runtime/probes/synchronization.rb
|
|
91
|
+
- lib/fiber_audit/runtime/probes/thread_state.rb
|
|
92
|
+
- lib/fiber_audit/runtime/probes/thread_wait.rb
|
|
93
|
+
- lib/fiber_audit/runtime/rails_integration.rb
|
|
94
|
+
- lib/fiber_audit/runtime/recorder.rb
|
|
95
|
+
- lib/fiber_audit/runtime/redactor.rb
|
|
96
|
+
- lib/fiber_audit/runtime/sampler.rb
|
|
97
|
+
- lib/fiber_audit/runtime/scheduler_observer.rb
|
|
98
|
+
- lib/fiber_audit/runtime/session.rb
|
|
99
|
+
- lib/fiber_audit/runtime/supervisor.rb
|
|
100
|
+
- lib/fiber_audit/runtime/validation.rb
|
|
101
|
+
- lib/fiber_audit/runtime/watchdog.rb
|
|
102
|
+
- lib/fiber_audit/runtime/watchdog_policy.rb
|
|
67
103
|
- lib/fiber_audit/static/call_site.rb
|
|
68
104
|
- lib/fiber_audit/static/call_site_extractor.rb
|
|
69
105
|
- lib/fiber_audit/static/execution_context_resolver.rb
|
|
@@ -104,5 +140,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
104
140
|
requirements: []
|
|
105
141
|
rubygems_version: 3.6.9
|
|
106
142
|
specification_version: 4
|
|
107
|
-
summary: Static fiber-scheduler
|
|
143
|
+
summary: Static and observational runtime fiber-scheduler auditor for Ruby and Rails
|
|
108
144
|
test_files: []
|