Class: Binpacker::Timing
- Inherits:
-
Object
- Object
- Binpacker::Timing
- Defined in:
- lib/binpacker/timing.rb,
sig/binpacker/timing.rbs
Defined Under Namespace
Classes: Entry
Constant Summary collapse
- DEFAULT_WEIGHT =
1.0- MAX_SAMPLES_PER_TEST =
Samples retained per test by #compact! and consulted by the median in #load_per_file. Three samples make a single anomalous run (GC pause, noisy CI neighbour) unable to move the weight.
3
Instance Method Summary collapse
- #append(file:, name:, time:) ⇒ Object
- #append_all(entries) ⇒ nil
-
#calibrated? ⇒ Boolean
True once any timing samples exist, i.e.
-
#compact! ⇒ nil
Rewrites the timing file keeping only the most recent MAX_SAMPLES_PER_TEST samples per test, so the append-only history (and any CI cache built from it) stays bounded instead of growing by one run per invocation.
-
#initialize(path) ⇒ Timing
constructor
A new instance of Timing.
-
#load_per_file ⇒ Hash[String, untyped], {}
Predicted weight per file: the median of each test's recent samples, summed per file.
- #load_raw ⇒ Hash[[String, untyped], untyped], {}
- #load_with_fallback(tests) ⇒ Hash[untyped, Float]
-
#measured?(file:, name:) ⇒ Boolean
True when a measured Weight already exists for this Test.
- #normalize_path(path) ⇒ String
Constructor Details
#initialize(path) ⇒ Timing
Returns a new instance of Timing.
16 17 18 |
# File 'lib/binpacker/timing.rb', line 16 def initialize(path) @path = Pathname(path) end |
Instance Method Details
#append(file:, name:, time:) ⇒ Object
81 82 83 84 85 |
# File 'lib/binpacker/timing.rb', line 81 def append(file:, name:, time:) @path.dirname.mkpath unless @path.dirname.directory? @path.open('a', encoding: 'UTF-8') { |io| io.puts JSON.generate({ file: file, name: name, time: time }) } invalidate end |
#append_all(entries) ⇒ nil
87 88 89 90 91 92 93 94 95 |
# File 'lib/binpacker/timing.rb', line 87 def append_all(entries) return if entries.empty? @path.dirname.mkpath unless @path.dirname.directory? @path.open('a', encoding: 'UTF-8') do |io| entries.each { |e| io.puts JSON.generate({ file: e[:file], name: e[:name], time: e[:time] }) } end invalidate end |
#calibrated? ⇒ Boolean
True once any timing samples exist, i.e. the project has been calibrated at least once. Callers use this to tell a measured run (weights in seconds) from a pure cold start (fallbacks only).
23 24 25 |
# File 'lib/binpacker/timing.rb', line 23 def calibrated? !samples_by_test.empty? end |
#compact! ⇒ nil
Rewrites the timing file keeping only the most recent MAX_SAMPLES_PER_TEST samples per test, so the append-only history (and any CI cache built from it) stays bounded instead of growing by one run per invocation.
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
# File 'lib/binpacker/timing.rb', line 56 def compact! samples = samples_by_test return if samples.empty? tmp = Pathname("#{@path}.tmp") tmp.open('w', encoding: 'UTF-8') do |io| samples.each do |(file, name), times| times.last(MAX_SAMPLES_PER_TEST).each do |time| io.puts JSON.generate({ file: file, name: name, time: time }) end end end File.rename(tmp.to_s, @path.to_s) invalidate end |
#load_per_file ⇒ Hash[String, untyped], {}
Predicted weight per file: the median of each test's recent samples, summed per file. The append-only history must NOT be summed wholesale — a file present in N historical runs would weigh ~N times its true cost, so long-lived files dominate and newly added ones are starved, skewing the partition.
45 46 47 48 49 50 |
# File 'lib/binpacker/timing.rb', line 45 def load_per_file samples_by_test.each_with_object({}) do |((file, _name), times), per_file| weight = median(times.last(MAX_SAMPLES_PER_TEST)) per_file[file] = per_file.fetch(file, 0.0) + weight end end |
#load_raw ⇒ Hash[[String, untyped], untyped], {}
36 37 38 |
# File 'lib/binpacker/timing.rb', line 36 def load_raw @load_raw ||= samples_by_test.transform_values(&:last) end |
#load_with_fallback(tests) ⇒ Hash[untyped, Float]
27 28 29 30 31 32 33 34 |
# File 'lib/binpacker/timing.rb', line 27 def load_with_fallback(tests) per_file = load_per_file coefficient = seconds_per_kb(per_file) tests.each_with_object({}) do |test, hash| key = normalize_path(test.file) hash[test.key] = per_file.fetch(key) { fallback_weight(test.file, coefficient) } end end |
#measured?(file:, name:) ⇒ Boolean
True when a measured Weight already exists for this Test.
77 78 79 |
# File 'lib/binpacker/timing.rb', line 77 def measured?(file:, name:) load_raw.key?([normalize_path(file), name]) end |
#normalize_path(path) ⇒ String
72 73 74 |
# File 'lib/binpacker/timing.rb', line 72 def normalize_path(path) Pathname(path).cleanpath.to_s.sub(%r{\A\./}, '') end |