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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/.fiber-audit.example.yml +19 -0
  3. data/ARCHITECTURE.md +726 -0
  4. data/CHANGELOG.md +41 -0
  5. data/LICENSE +201 -0
  6. data/README.md +66 -8
  7. data/lib/fiber_audit/cli.rb +87 -2
  8. data/lib/fiber_audit/configuration.rb +106 -6
  9. data/lib/fiber_audit/errors.rb +2 -0
  10. data/lib/fiber_audit/operation_vocabulary.rb +42 -0
  11. data/lib/fiber_audit/reporters/text.rb +1 -1
  12. data/lib/fiber_audit/runtime/active_operations.rb +146 -0
  13. data/lib/fiber_audit/runtime/boot.rb +83 -0
  14. data/lib/fiber_audit/runtime/clock.rb +35 -0
  15. data/lib/fiber_audit/runtime/environment.rb +289 -0
  16. data/lib/fiber_audit/runtime/event.rb +86 -0
  17. data/lib/fiber_audit/runtime/execution_context.rb +89 -0
  18. data/lib/fiber_audit/runtime/heartbeat.rb +113 -0
  19. data/lib/fiber_audit/runtime/jsonl/schema.rb +312 -0
  20. data/lib/fiber_audit/runtime/jsonl/writer.rb +122 -0
  21. data/lib/fiber_audit/runtime/lifecycle.rb +343 -0
  22. data/lib/fiber_audit/runtime/limits.rb +102 -0
  23. data/lib/fiber_audit/runtime/location.rb +44 -0
  24. data/lib/fiber_audit/runtime/policy.rb +121 -0
  25. data/lib/fiber_audit/runtime/probes/base.rb +333 -0
  26. data/lib/fiber_audit/runtime/probes/http.rb +80 -0
  27. data/lib/fiber_audit/runtime/probes/io_select.rb +50 -0
  28. data/lib/fiber_audit/runtime/probes/registry.rb +156 -0
  29. data/lib/fiber_audit/runtime/probes/socket.rb +76 -0
  30. data/lib/fiber_audit/runtime/probes/subprocess.rb +83 -0
  31. data/lib/fiber_audit/runtime/probes/synchronization.rb +58 -0
  32. data/lib/fiber_audit/runtime/probes/thread_state.rb +39 -0
  33. data/lib/fiber_audit/runtime/probes/thread_wait.rb +25 -0
  34. data/lib/fiber_audit/runtime/rails_integration.rb +320 -0
  35. data/lib/fiber_audit/runtime/recorder.rb +333 -0
  36. data/lib/fiber_audit/runtime/redactor.rb +102 -0
  37. data/lib/fiber_audit/runtime/sampler.rb +26 -0
  38. data/lib/fiber_audit/runtime/scheduler_observer.rb +128 -0
  39. data/lib/fiber_audit/runtime/session.rb +112 -0
  40. data/lib/fiber_audit/runtime/supervisor.rb +114 -0
  41. data/lib/fiber_audit/runtime/validation.rb +68 -0
  42. data/lib/fiber_audit/runtime/watchdog.rb +479 -0
  43. data/lib/fiber_audit/runtime/watchdog_policy.rb +64 -0
  44. data/lib/fiber_audit/runtime.rb +37 -0
  45. data/lib/fiber_audit/static/rules/blocking_subprocess.rb +3 -8
  46. data/lib/fiber_audit/static/rules/direct_socket.rb +2 -3
  47. data/lib/fiber_audit/static/rules/io_select.rb +2 -4
  48. data/lib/fiber_audit/static/rules/net_http_in_request.rb +3 -5
  49. data/lib/fiber_audit/static/rules/synchronization.rb +2 -6
  50. data/lib/fiber_audit/static/rules/thread_current_state.rb +3 -2
  51. data/lib/fiber_audit/static/rules/thread_join.rb +3 -2
  52. data/lib/fiber_audit/version.rb +1 -1
  53. data/lib/fiber_audit.rb +1 -0
  54. 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
- 'Kernel' => %i[system exec spawn].freeze,
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 = %w[
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 = %i[get get_response start request].freeze
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 = %i[thread_variable_get thread_variable_set].freeze
24
- INDEX_METHODS = %i[[] []=].freeze
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 = %i[join value].freeze
34
- CANONICAL_OPS = %w[Thread.join Thread.value].freeze
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:)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FiberAudit
4
- VERSION = '0.1.0'
4
+ VERSION = '0.2.1'
5
5
  end
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.0
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 compatibility auditor for Ruby and Rails applications
143
+ summary: Static and observational runtime fiber-scheduler auditor for Ruby and Rails
108
144
  test_files: []