Class: Corkscrews::Recorder

Inherits:
Object
  • Object
show all
Defined in:
lib/corkscrews/recorder.rb

Constant Summary collapse

INTERNAL_PATH =
File.expand_path("..", __dir__).freeze
DEFAULT_SAMPLE_PERIOD_MS =
1.0
MAX_BACKTRACE_DEPTH =
32
FNV64_OFFSET_BASIS =
14_695_981_039_346_656_037
FNV64_PRIME =
1_099_511_628_211
FNV64_MASK =
(1 << 64) - 1

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(output:, run_id:, repeat_index:, sample_period_ms:) ⇒ Recorder

Returns a new instance of Recorder.



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/corkscrews/recorder.rb', line 76

def initialize(output:, run_id:, repeat_index:, sample_period_ms:)
  @output = output
  @run_id = run_id || "run-#{Process.pid}"
  @repeat_index = repeat_index&.to_i
  @sample_period = ((sample_period_ms || DEFAULT_SAMPLE_PERIOD_MS).to_f / 1000.0)
  @sample_period_ns = (@sample_period * 1_000_000_000).to_i
  @started_ns = TimeSource.monotonic_ns
  @mutex = Mutex.new
  @samples_by_site = Hash.new(0)
  @causal_samples_by_site = Hash.new(0)
  @sample_count = 0
  @progress_threads = {}
  @progress_by_name = Hash.new { |hash, key| hash[key] = progress_bucket }
  @latency_by_name = Hash.new { |hash, key| hash[key] = latency_bucket }
  @wait_by_kind = Hash.new { |hash, key| hash[key] = wait_bucket }
  @stop_requested = false
  @setitimer = nil
  @previous_prof_trap = nil
  @sampler_thread = nil
  @gc_profiler_was_enabled = false
  @gc_started_total_time = 0.0
  @round_records = []
  @round_index = 0
  @round_started_ns = @started_ns
  @round_progress_start = 0
  @current_experiment = nil
  @experiment_random = Random.new((@run_id.hash ^ Process.pid) & 0xFFFF_FFFF)
  @controller = Controller.new(targets: [], random: @experiment_random)
  @round_max_ns = (ENV.fetch("CORKSCREWS_ROUND_MS", "200").to_f * 1_000_000).round
  @target_mode = ENV.fetch("CORKSCREWS_TARGETS", "lines")
  @max_slowdown_pct = max_slowdown_pct
  @issued_delay_ns = 0
  @suppressed_delay_ns = 0
  @fiber_switches = 0
  @fiber_ids = {}
  @fiber_thread_ids = {}
  @fiber_tracepoint = nil
  @native_signal_source = false
end

Class Method Details

.currentObject



25
26
27
# File 'lib/corkscrews/recorder.rb', line 25

def current
  @current
end

.start!(output:, run_id: nil, repeat_index: nil, sample_period_ms: nil) ⇒ Object



29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/corkscrews/recorder.rb', line 29

def start!(output:, run_id: nil, repeat_index: nil, sample_period_ms: nil)
  lock_class_mutex
  stop_locked if @current

  @current = new(
    output: output,
    run_id: run_id,
    repeat_index: repeat_index,
    sample_period_ms: sample_period_ms
  )
  @current.start
ensure
  unlock_class_mutex
end

.stop!Object



44
45
46
47
48
49
# File 'lib/corkscrews/recorder.rb', line 44

def stop!
  lock_class_mutex
  stop_locked
ensure
  unlock_class_mutex
end

Instance Method Details

#latency_begin(name) ⇒ Object



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/corkscrews/recorder.rb', line 153

def latency_begin(name)
  now = TimeSource.monotonic_ns
  key = name.to_s

  synchronize_state do
    @progress_threads[Thread.current.object_id] = true
    bucket = @latency_by_name[key]
    update_latency_area(bucket, now)
    bucket[:begin_count] += 1
    bucket[:in_flight] += 1
    bucket[:first_ns] ||= now
    bucket[:last_begin_ns] = now
    bucket[:starts][Thread.current.object_id] << now
  end
end

#latency_end(name) ⇒ Object



169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/corkscrews/recorder.rb', line 169

def latency_end(name)
  now = TimeSource.monotonic_ns
  key = name.to_s

  synchronize_state do
    @progress_threads[Thread.current.object_id] = true
    bucket = @latency_by_name[key]
    update_latency_area(bucket, now)
    bucket[:end_count] += 1
    bucket[:in_flight] -= 1 if bucket[:in_flight].positive?
    bucket[:first_ns] ||= now
    bucket[:last_end_ns] = now
    started_ns = bucket[:starts][Thread.current.object_id].pop
    if started_ns
      bucket[:total_duration_ns] += now - started_ns
      bucket[:completed_count] += 1
    end
  end
end

#progress(name) ⇒ Object



140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/corkscrews/recorder.rb', line 140

def progress(name)
  now = TimeSource.monotonic_ns
  key = name.to_s

  synchronize_state do
    @progress_threads[Thread.current.object_id] = true
    bucket = @progress_by_name[key]
    bucket[:count] += 1
    bucket[:first_ns] ||= now
    bucket[:last_ns] = now
  end
end

#record_wait(kind, duration_ns) ⇒ Object



189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/corkscrews/recorder.rb', line 189

def record_wait(kind, duration_ns)
  return if Thread.current[:corkscrews_internal]
  return unless wait_targets_enabled?
  return unless duration_ns.positive?

  key = kind.to_s
  synchronize_state do
    bucket = @wait_by_kind[key]
    bucket[:count] += 1
    bucket[:duration_ns] += duration_ns
    advance_experiment_round({ kind: "wait", name: key }) unless @stop_requested
  end
end

#startObject



116
117
118
119
120
121
122
123
124
# File 'lib/corkscrews/recorder.rb', line 116

def start
  Corkscrews::Native.set_sample_period(@sample_period_ns)
  start_gc_profiler if wait_targets_enabled?
  start_fiber_tracepoint
  register_native_signal_source if line_targets_enabled?
  start_signal_sampler if line_targets_enabled?
  Corkscrews::Native.start_monitor
  start_thread_sampler if line_targets_enabled?
end

#stopObject



126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/corkscrews/recorder.rb', line 126

def stop
  @stop_requested = true
  ended_ns = TimeSource.monotonic_ns
  stop_signal_sampler
  stop_thread_sampler
  stop_fiber_tracepoint
  Corkscrews::Native.clear_monitor_thread if @native_signal_source
  Corkscrews::Native.stop_monitor
  finish_round(ended_ns)
  Corkscrews::Native.clear_experiment
  record_gc_pause_delta if wait_targets_enabled?
  flush(ended_ns)
end