Class: Greenroom::Recorder
- Inherits:
-
Object
- Object
- Greenroom::Recorder
- Defined in:
- lib/greenroom/recorder.rb
Overview
The tallying half of a census run. A census job installs one of these on
the thread it is running on; while it is installed, enabled? is true
and comparisons flow into it instead of going nowhere. Uninstalling it
turns comparisons back off, so the presence of a recorder is the one
mechanism behind both "is the experiment running" and "where do the
results go".
Defined Under Namespace
Classes: Tally
Instance Attribute Summary collapse
-
#rows_scanned ⇒ Object
readonly
Returns the value of attribute rows_scanned.
-
#run_id ⇒ Object
readonly
Returns the value of attribute run_id.
-
#sample_limit ⇒ Object
readonly
Returns the value of attribute sample_limit.
-
#samples ⇒ Object
readonly
Returns the value of attribute samples.
-
#subject ⇒ Object
Returns the value of attribute subject.
-
#tallies ⇒ Object
readonly
Returns the value of attribute tallies.
Class Method Summary collapse
- .current ⇒ Object
- .install(recorder) ⇒ Object
-
.uninstall ⇒ Object
Returns the recorder that was installed, so a caller that wants to flush it (
on_stop) does not need to have held onto a reference itself. -
.with(recorder) ⇒ Object
For tests and any plain call site that wants a recorder installed for the duration of a block.
Instance Method Summary collapse
- #count_row ⇒ Object
-
#flush_segment(reporter) ⇒ Object
Sends one progress event (this run is alive, with the counts so far) and one mismatch event per sample gathered since the last flush.
-
#flush_total(reporter) ⇒ Object
Sends the run's aggregate event: the one number a judge reads as the denominator.
-
#initialize(run_id:, experiment: nil, sample_limit: 100) ⇒ Recorder
constructor
A new instance of Recorder.
-
#record(result) ⇒ Object
Receives a
Scientist::Resultfrompublish. - #record_row_error(_error) ⇒ Object
- #restore(snapshot) ⇒ Object
- #snapshot ⇒ Object
Constructor Details
#initialize(run_id:, experiment: nil, sample_limit: 100) ⇒ Recorder
Returns a new instance of Recorder.
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
# File 'lib/greenroom/recorder.rb', line 83 def initialize(run_id:, experiment: nil, sample_limit: 100) @run_id = run_id @sample_limit = sample_limit @rows_scanned = 0 @row_errors = 0 @tallies = {} @samples = [] @subject = nil # A run that never triggers a single comparison (a module-only # include, or a target whose `call` never reaches `Scientist.run`) # would otherwise leave `tallies` empty and `flush_total` silent. A # silent run and a run that never started look identical from the # outside, so the zero must be sent, not inferred from an absence. tally_for(experiment) if experiment end |
Instance Attribute Details
#rows_scanned ⇒ Object (readonly)
Returns the value of attribute rows_scanned.
80 81 82 |
# File 'lib/greenroom/recorder.rb', line 80 def rows_scanned @rows_scanned end |
#run_id ⇒ Object (readonly)
Returns the value of attribute run_id.
80 81 82 |
# File 'lib/greenroom/recorder.rb', line 80 def run_id @run_id end |
#sample_limit ⇒ Object (readonly)
Returns the value of attribute sample_limit.
80 81 82 |
# File 'lib/greenroom/recorder.rb', line 80 def sample_limit @sample_limit end |
#samples ⇒ Object (readonly)
Returns the value of attribute samples.
80 81 82 |
# File 'lib/greenroom/recorder.rb', line 80 def samples @samples end |
#subject ⇒ Object
Returns the value of attribute subject.
81 82 83 |
# File 'lib/greenroom/recorder.rb', line 81 def subject @subject end |
#tallies ⇒ Object (readonly)
Returns the value of attribute tallies.
80 81 82 |
# File 'lib/greenroom/recorder.rb', line 80 def tallies @tallies end |
Class Method Details
.current ⇒ Object
51 52 53 |
# File 'lib/greenroom/recorder.rb', line 51 def current Thread.current[THREAD_KEY] end |
.install(recorder) ⇒ Object
55 56 57 |
# File 'lib/greenroom/recorder.rb', line 55 def install(recorder) Thread.current[THREAD_KEY] = recorder end |
.uninstall ⇒ Object
Returns the recorder that was installed, so a caller that wants to
flush it (on_stop) does not need to have held onto a reference
itself.
62 63 64 65 66 |
# File 'lib/greenroom/recorder.rb', line 62 def uninstall recorder = Thread.current[THREAD_KEY] Thread.current[THREAD_KEY] = nil recorder end |
.with(recorder) ⇒ Object
For tests and any plain call site that wants a recorder installed
for the duration of a block. ensure uninstalls even if the block
raises, so a failing test cannot leave a recorder behind for the
next one.
72 73 74 75 76 77 |
# File 'lib/greenroom/recorder.rb', line 72 def with(recorder) install(recorder) yield ensure uninstall end |
Instance Method Details
#count_row ⇒ Object
100 101 102 |
# File 'lib/greenroom/recorder.rb', line 100 def count_row @rows_scanned += 1 end |
#flush_segment(reporter) ⇒ Object
Sends one progress event (this run is alive, with the counts so far)
and one mismatch event per sample gathered since the last flush. The
samples that were just sent are discarded afterward: a sample is
evidence, not part of the denominator, so re-sending it on resume
(see Census::Cursor) is harmless, and keeping it around forever
would only grow memory for no benefit.
158 159 160 161 162 |
# File 'lib/greenroom/recorder.rb', line 158 def flush_segment(reporter) emit(reporter, "GreenroomCensusProgress") @samples.each { |sample| reporter.record("GreenroomMismatch", sample) } @samples.clear end |
#flush_total(reporter) ⇒ Object
Sends the run's aggregate event: the one number a judge reads as the
denominator. Splitting counts across flush_segment calls and asking
a judge to sum them would make that sum itself something to verify for
double-counting or gaps; a single event at the end avoids that.
168 169 170 |
# File 'lib/greenroom/recorder.rb', line 168 def flush_total(reporter) emit(reporter, "GreenroomCensus") end |
#record(result) ⇒ Object
Receives a Scientist::Result from publish. Counts are split into
"mismatched" and "ignored" so that a host's ignore block, which
exists to suppress noisy known-differences, does not also hide how
much it is suppressing.
112 113 114 115 116 117 118 119 120 121 |
# File 'lib/greenroom/recorder.rb', line 112 def record(result) tally = tally_for(result.experiment_name) tally.comparisons += 1 tally.mismatches += 1 if result.mismatched? tally.ignored += 1 if result.ignored? tally.candidate_errors += result.candidates.count(&:raised?) tally.control_errors += 1 if result.control&.raised? sample(result) if result.mismatched? end |
#record_row_error(_error) ⇒ Object
104 105 106 |
# File 'lib/greenroom/recorder.rb', line 104 def record_row_error(_error) @row_errors += 1 end |
#restore(snapshot) ⇒ Object
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 |
# File 'lib/greenroom/recorder.rb', line 131 def restore(snapshot) @rows_scanned = snapshot.fetch("rows_scanned", 0).to_i @row_errors = snapshot.fetch("row_errors", 0).to_i # Merges into whatever `@tallies` already holds instead of replacing # it outright. A resumed segment's `Recorder.new(experiment:)` seeds a # zero tally under that name (see the comment in `initialize`) before # `restore` ever runs; a snapshot taken before the first comparison # for that name does not carry it, and replacing `@tallies` wholesale # would erase the seeded zero and lose the name -- the run's later # `flush_total` would then report `experiment: nil` instead of the # name it was actually comparing. snapshot.fetch("tallies", {}).each do |name, counts| tally = tally_for(name) tally.comparisons = counts.fetch("comparisons", 0).to_i tally.mismatches = counts.fetch("mismatches", 0).to_i tally.ignored = counts.fetch("ignored", 0).to_i tally.candidate_errors = counts.fetch("candidate_errors", 0).to_i tally.control_errors = counts.fetch("control_errors", 0).to_i end end |
#snapshot ⇒ Object
123 124 125 126 127 128 129 |
# File 'lib/greenroom/recorder.rb', line 123 def snapshot { "rows_scanned" => @rows_scanned, "row_errors" => @row_errors, "tallies" => @tallies.transform_values(&:to_h) } end |