ci-queue 0.80.0 → 0.82.0
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/Gemfile.lock +1 -1
- data/README.md +22 -0
- data/lib/ci/queue/build_record.rb +22 -10
- data/lib/ci/queue/redis/base.rb +1 -1
- data/lib/ci/queue/redis/build_record.rb +77 -22
- data/lib/ci/queue/redis/grind_record.rb +17 -13
- data/lib/ci/queue/redis.rb +0 -1
- data/lib/ci/queue/version.rb +1 -1
- data/lib/minitest/queue/build_status_recorder.rb +32 -14
- data/lib/minitest/queue/failure_formatter.rb +3 -5
- data/lib/minitest/queue/grind_recorder.rb +3 -3
- data/lib/minitest/queue/runner.rb +20 -5
- metadata +2 -3
- data/lib/ci/queue/redis/key_shortener.rb +0 -53
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3db30c3671ffa851f03a771eaf73a1210b51213b4039cdb17bfa29489a5b21ec
|
|
4
|
+
data.tar.gz: 5ae004105d90eb2d1383439f71ce355643c67b5e1e35a75782fb6b94815a62b1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4a1c6ba221d268f37b32d804fde95827159e79dceaf51a156af5abf774e57a04c4dc98d781f3f7c9bcd065006b1b11e70ee2237e3e6109eb9f32c3bef9da2f3b
|
|
7
|
+
data.tar.gz: a62858950ae3c22ad1f7ae0dba2f85f5daa6de2fa17730efe2faf91b32b1e3be42ee750f65febc48cc53757e417a9f449d177cecf4dcaffb7b03736825a1d7d4
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -71,6 +71,28 @@ rspec-queue --queue redis://example.com --timeout 600 --report
|
|
|
71
71
|
|
|
72
72
|
Because of how `ci-queue` executes the examples, `before(:all)` and `after(:all)` hooks are not supported. `rspec-queue` will explicitly reject them.
|
|
73
73
|
|
|
74
|
+
## Releasing a New Version
|
|
75
|
+
|
|
76
|
+
After merging changes to `main`, follow these steps to release and propagate the update:
|
|
77
|
+
|
|
78
|
+
1. **Bump the version** in `ruby/lib/ci/queue/version.rb`:
|
|
79
|
+
|
|
80
|
+
```ruby
|
|
81
|
+
VERSION = '0.XX.0'
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
2. **Update `Gemfile.lock`** by running `bundle install` in the `ruby/` directory (or manually updating the version string in `Gemfile.lock` if native dependencies prevent `bundle install`).
|
|
85
|
+
|
|
86
|
+
3. **Commit and merge** the version bump to `main`. ShipIt will automatically publish the gem to RubyGems.
|
|
87
|
+
|
|
88
|
+
4. **Update dependent apps/zones**: Any application that depends on `ci-queue` (e.g. via its `Gemfile`) needs to pick up the new version by running:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
bundle update ci-queue
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
This updates the app's `Gemfile.lock` to reference the new `ci-queue` version. Commit the updated `Gemfile.lock` and deploy.
|
|
95
|
+
|
|
74
96
|
## Custom Redis Expiry
|
|
75
97
|
|
|
76
98
|
`ci-queue` expects the Redis server to have an [eviction policy](https://redis.io/docs/manual/eviction/#eviction-policies) of `allkeys-lru`.
|
|
@@ -18,18 +18,35 @@ module CI
|
|
|
18
18
|
@queue.exhausted?
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
-
def record_error(id, payload,
|
|
21
|
+
def record_error(id, payload, stat_delta: nil)
|
|
22
22
|
error_reports[id] = payload
|
|
23
|
-
|
|
23
|
+
true
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
-
def record_success(id,
|
|
26
|
+
def record_success(id, skip_flaky_record: false, acknowledge: true)
|
|
27
27
|
error_reports.delete(id)
|
|
28
|
-
|
|
28
|
+
true
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def record_requeue(id)
|
|
32
|
+
true
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def record_stats(builds_stats)
|
|
36
|
+
return unless builds_stats
|
|
37
|
+
stats.merge!(builds_stats)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def record_stats_delta(delta, pipeline: nil)
|
|
41
|
+
return if delta.nil? || delta.empty?
|
|
42
|
+
delta.each do |stat_name, value|
|
|
43
|
+
next unless value.is_a?(Numeric) || value.to_s.match?(/\A-?\d+\.?\d*\z/)
|
|
44
|
+
stats[stat_name] = (stats[stat_name] || 0).to_f + value.to_f
|
|
45
|
+
end
|
|
29
46
|
end
|
|
30
47
|
|
|
31
48
|
def fetch_stats(stat_names)
|
|
32
|
-
stat_names.zip(stats.values_at(*stat_names).map(&:to_f))
|
|
49
|
+
stat_names.zip(stats.values_at(*stat_names).map(&:to_f)).to_h
|
|
33
50
|
end
|
|
34
51
|
|
|
35
52
|
def reset_stats(stat_names)
|
|
@@ -47,11 +64,6 @@ module CI
|
|
|
47
64
|
private
|
|
48
65
|
|
|
49
66
|
attr_reader :stats
|
|
50
|
-
|
|
51
|
-
def record_stats(builds_stats)
|
|
52
|
-
return unless builds_stats
|
|
53
|
-
stats.merge!(builds_stats)
|
|
54
|
-
end
|
|
55
67
|
end
|
|
56
68
|
end
|
|
57
69
|
end
|
data/lib/ci/queue/redis/base.rb
CHANGED
|
@@ -56,30 +56,71 @@ module CI
|
|
|
56
56
|
redis.rpush(key('warnings'), Marshal.dump([type, attributes]))
|
|
57
57
|
end
|
|
58
58
|
|
|
59
|
-
def record_error(id, payload,
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
def record_error(id, payload, stat_delta: nil)
|
|
60
|
+
# Run acknowledge first so we know whether we're the first to ack
|
|
61
|
+
acknowledged = @queue.acknowledge(id, error: payload)
|
|
62
|
+
|
|
63
|
+
if acknowledged
|
|
64
|
+
# We were the first to ack; another worker already ack'd would get falsy from SADD
|
|
65
|
+
@queue.increment_test_failed
|
|
66
|
+
# Only the acknowledging worker's stats include this failure (others skip increment when ack=false).
|
|
67
|
+
# Store so we can subtract it if another worker records success later.
|
|
68
|
+
store_error_report_delta(id, stat_delta) if stat_delta && stat_delta.any?
|
|
63
69
|
end
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
nil
|
|
70
|
+
# Return so caller can roll back local counter when not acknowledged
|
|
71
|
+
!!acknowledged
|
|
67
72
|
end
|
|
68
73
|
|
|
69
|
-
def record_success(id,
|
|
70
|
-
|
|
74
|
+
def record_success(id, skip_flaky_record: false)
|
|
75
|
+
acknowledged, error_reports_deleted_count, requeued_count, delta_json = redis.multi do |transaction|
|
|
71
76
|
@queue.acknowledge(id, pipeline: transaction)
|
|
72
77
|
transaction.hdel(key('error-reports'), id)
|
|
73
78
|
transaction.hget(key('requeues-count'), id)
|
|
74
|
-
|
|
79
|
+
transaction.hget(key('error-report-deltas'), id)
|
|
80
|
+
end
|
|
81
|
+
# When we're replacing a failure, subtract the (single) acknowledging worker's stat contribution
|
|
82
|
+
if error_reports_deleted_count.to_i > 0 && delta_json
|
|
83
|
+
apply_error_report_delta_correction(delta_json)
|
|
84
|
+
redis.hdel(key('error-report-deltas'), id)
|
|
75
85
|
end
|
|
76
86
|
record_flaky(id) if !skip_flaky_record && (error_reports_deleted_count.to_i > 0 || requeued_count.to_i > 0)
|
|
77
|
-
|
|
87
|
+
# Count this run when we ack'd or when we replaced a failure (so stats delta is applied)
|
|
88
|
+
!!(acknowledged || error_reports_deleted_count.to_i > 0)
|
|
78
89
|
end
|
|
79
90
|
|
|
80
|
-
def record_requeue(id
|
|
81
|
-
|
|
82
|
-
|
|
91
|
+
def record_requeue(id)
|
|
92
|
+
true
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def record_stats(stats = nil, pipeline: nil)
|
|
96
|
+
return unless stats
|
|
97
|
+
if pipeline
|
|
98
|
+
stats.each do |stat_name, stat_value|
|
|
99
|
+
pipeline.hset(key(stat_name), config.worker_id, stat_value)
|
|
100
|
+
pipeline.expire(key(stat_name), config.redis_ttl)
|
|
101
|
+
end
|
|
102
|
+
else
|
|
103
|
+
redis.pipelined do |p|
|
|
104
|
+
record_stats(stats, pipeline: p)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Apply a delta to this worker's stats in Redis (HINCRBY). Use this instead of
|
|
110
|
+
# record_stats when recording per-test so we never overwrite and correction sticks.
|
|
111
|
+
def record_stats_delta(delta, pipeline: nil)
|
|
112
|
+
return if delta.nil? || delta.empty?
|
|
113
|
+
apply_delta = lambda do |p|
|
|
114
|
+
delta.each do |stat_name, value|
|
|
115
|
+
next unless value.is_a?(Numeric) || value.to_s.match?(/\A-?\d+\.?\d*\z/)
|
|
116
|
+
p.hincrbyfloat(key(stat_name), config.worker_id.to_s, value.to_f)
|
|
117
|
+
p.expire(key(stat_name), config.redis_ttl)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
if pipeline
|
|
121
|
+
apply_delta.call(pipeline)
|
|
122
|
+
else
|
|
123
|
+
redis.pipelined { |p| apply_delta.call(p) }
|
|
83
124
|
end
|
|
84
125
|
end
|
|
85
126
|
|
|
@@ -130,16 +171,30 @@ module CI
|
|
|
130
171
|
|
|
131
172
|
attr_reader :config, :redis
|
|
132
173
|
|
|
133
|
-
def
|
|
134
|
-
|
|
135
|
-
stats.each do |stat_name, stat_value|
|
|
136
|
-
pipeline.hset(key(stat_name), config.worker_id, stat_value)
|
|
137
|
-
pipeline.expire(key(stat_name), config.redis_ttl)
|
|
138
|
-
end
|
|
174
|
+
def key(*args)
|
|
175
|
+
['build', config.build_id, *args].join(':')
|
|
139
176
|
end
|
|
140
177
|
|
|
141
|
-
def
|
|
142
|
-
|
|
178
|
+
def store_error_report_delta(test_id, stat_delta)
|
|
179
|
+
# Only the acknowledging worker's stats include this test; store their delta for correction on success
|
|
180
|
+
payload = { 'worker_id' => config.worker_id.to_s }.merge(stat_delta)
|
|
181
|
+
redis.hset(key('error-report-deltas'), test_id, JSON.generate(payload))
|
|
182
|
+
redis.expire(key('error-report-deltas'), config.redis_ttl)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def apply_error_report_delta_correction(delta_json)
|
|
186
|
+
delta = JSON.parse(delta_json)
|
|
187
|
+
worker_id = delta.delete('worker_id')&.to_s
|
|
188
|
+
return if worker_id.nil? || worker_id.empty? || delta.empty?
|
|
189
|
+
|
|
190
|
+
redis.pipelined do |pipeline|
|
|
191
|
+
delta.each do |stat_name, value|
|
|
192
|
+
next unless value.is_a?(Numeric) || value.to_s.match?(/\A-?\d+\.?\d*\z/)
|
|
193
|
+
|
|
194
|
+
pipeline.hincrbyfloat(key(stat_name), worker_id, -value.to_f)
|
|
195
|
+
pipeline.expire(key(stat_name), config.redis_ttl)
|
|
196
|
+
end
|
|
197
|
+
end
|
|
143
198
|
end
|
|
144
199
|
end
|
|
145
200
|
end
|
|
@@ -10,20 +10,32 @@ module CI
|
|
|
10
10
|
@config = config
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
def record_error(payload
|
|
13
|
+
def record_error(payload)
|
|
14
14
|
redis.pipelined do |pipeline|
|
|
15
15
|
pipeline.lpush(
|
|
16
16
|
key('error-reports'),
|
|
17
17
|
payload,
|
|
18
18
|
)
|
|
19
19
|
pipeline.expire(key('error-reports'), config.redis_ttl)
|
|
20
|
-
record_stats(stats, pipeline: pipeline)
|
|
21
20
|
end
|
|
22
21
|
nil
|
|
23
22
|
end
|
|
24
23
|
|
|
25
|
-
def record_success
|
|
26
|
-
|
|
24
|
+
def record_success
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def record_stats(stats, pipeline: nil)
|
|
28
|
+
return unless stats
|
|
29
|
+
if pipeline
|
|
30
|
+
stats.each do |stat_name, stat_value|
|
|
31
|
+
pipeline.hset(key(stat_name), config.worker_id, stat_value)
|
|
32
|
+
pipeline.expire(key(stat_name), config.redis_ttl)
|
|
33
|
+
end
|
|
34
|
+
else
|
|
35
|
+
redis.pipelined do |p|
|
|
36
|
+
record_stats(stats, pipeline: p)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
27
39
|
end
|
|
28
40
|
|
|
29
41
|
def record_warning(_,_)
|
|
@@ -52,15 +64,7 @@ module CI
|
|
|
52
64
|
attr_reader :redis, :config
|
|
53
65
|
|
|
54
66
|
def key(*args)
|
|
55
|
-
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def record_stats(stats, pipeline: redis)
|
|
59
|
-
return unless stats
|
|
60
|
-
stats.each do |stat_name, stat_value|
|
|
61
|
-
pipeline.hset(key(stat_name), config.worker_id, stat_value)
|
|
62
|
-
pipeline.expire(key(stat_name), config.redis_ttl)
|
|
63
|
-
end
|
|
67
|
+
['build', config.build_id, *args].join(':')
|
|
64
68
|
end
|
|
65
69
|
end
|
|
66
70
|
end
|
data/lib/ci/queue/redis.rb
CHANGED
data/lib/ci/queue/version.rb
CHANGED
|
@@ -38,28 +38,46 @@ module Minitest
|
|
|
38
38
|
super
|
|
39
39
|
|
|
40
40
|
self.total_time = Minitest.clock_time - start_time
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
elsif test.error?
|
|
46
|
-
self.errors += 1
|
|
47
|
-
elsif test.failure
|
|
48
|
-
self.failures += 1
|
|
49
|
-
end
|
|
41
|
+
|
|
42
|
+
# Determine what type of result this is and record it
|
|
43
|
+
test_id = "#{test.klass}##{test.name}"
|
|
44
|
+
delta = delta_for(test)
|
|
50
45
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
build.record_error("#{test.klass}##{test.name}", dump(test), stats: stats)
|
|
46
|
+
acknowledged = if (test.failure || test.error?) && !test.skipped?
|
|
47
|
+
build.record_error(test_id, dump(test), stat_delta: delta)
|
|
54
48
|
elsif test.requeued?
|
|
55
|
-
build.record_requeue(
|
|
49
|
+
build.record_requeue(test_id)
|
|
56
50
|
else
|
|
57
|
-
build.record_success(
|
|
51
|
+
build.record_success(test_id, skip_flaky_record: test.skipped?)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
if acknowledged
|
|
55
|
+
if (test.failure || test.error?) && !test.skipped?
|
|
56
|
+
test.error? ? self.errors += 1 : self.failures += 1
|
|
57
|
+
elsif test.requeued?
|
|
58
|
+
self.requeues += 1
|
|
59
|
+
elsif test.skipped?
|
|
60
|
+
self.skips += 1
|
|
61
|
+
end
|
|
62
|
+
# Apply delta to Redis (record_success returns true when ack'd or when we replaced a failure)
|
|
63
|
+
build.record_stats_delta(delta)
|
|
58
64
|
end
|
|
59
65
|
end
|
|
60
66
|
|
|
61
67
|
private
|
|
62
68
|
|
|
69
|
+
def delta_for(test)
|
|
70
|
+
h = { 'assertions' => (test.assertions || 0).to_i, 'errors' => 0, 'failures' => 0, 'skips' => 0, 'requeues' => 0, 'total_time' => test.time.to_f }
|
|
71
|
+
if (test.failure || test.error?) && !test.skipped?
|
|
72
|
+
test.error? ? h['errors'] = 1 : h['failures'] = 1
|
|
73
|
+
elsif test.requeued?
|
|
74
|
+
h['requeues'] = 1
|
|
75
|
+
elsif test.skipped?
|
|
76
|
+
h['skips'] = 1
|
|
77
|
+
end
|
|
78
|
+
h
|
|
79
|
+
end
|
|
80
|
+
|
|
63
81
|
def dump(test)
|
|
64
82
|
ErrorReport.new(self.class.failure_formatter.new(test).to_h).dump
|
|
65
83
|
end
|
|
@@ -32,12 +32,12 @@ module Minitest
|
|
|
32
32
|
private
|
|
33
33
|
|
|
34
34
|
def record_test(test)
|
|
35
|
-
stats = self.class.counters
|
|
36
35
|
if (test.failure || test.error?) && !test.skipped?
|
|
37
|
-
build.record_error(dump(test)
|
|
36
|
+
build.record_error(dump(test))
|
|
38
37
|
else
|
|
39
|
-
build.record_success
|
|
38
|
+
build.record_success
|
|
40
39
|
end
|
|
40
|
+
build.record_stats(self.class.counters)
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
def increment_counter(test)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
require 'optparse'
|
|
3
3
|
require 'json'
|
|
4
|
+
require 'fileutils'
|
|
4
5
|
require 'minitest/queue'
|
|
5
6
|
require 'ci/queue'
|
|
6
7
|
require 'digest/md5'
|
|
@@ -242,16 +243,16 @@ module Minitest
|
|
|
242
243
|
puts
|
|
243
244
|
|
|
244
245
|
File.write('log/test_order.log', failing_order.to_a.map(&:id).join("\n"))
|
|
245
|
-
|
|
246
|
+
|
|
246
247
|
bisect_test_details = failing_order.to_a.map do |test|
|
|
247
248
|
source_location = test.source_location
|
|
248
249
|
file_path = source_location&.first || 'unknown'
|
|
249
250
|
line_number = source_location&.last || -1
|
|
250
251
|
"#{test.id} #{file_path}:#{line_number}"
|
|
251
252
|
end
|
|
252
|
-
|
|
253
|
+
|
|
253
254
|
File.write('log/bisect_test_details.log', bisect_test_details.join("\n"))
|
|
254
|
-
|
|
255
|
+
|
|
255
256
|
exit! 0
|
|
256
257
|
end
|
|
257
258
|
end
|
|
@@ -336,8 +337,22 @@ module Minitest
|
|
|
336
337
|
warnings = build.pop_warnings.map do |type, attributes|
|
|
337
338
|
attributes.merge(type: type)
|
|
338
339
|
end.compact
|
|
339
|
-
|
|
340
|
-
|
|
340
|
+
|
|
341
|
+
return if warnings.empty?
|
|
342
|
+
|
|
343
|
+
begin
|
|
344
|
+
# Ensure directory exists
|
|
345
|
+
dir = File.dirname(queue_config.warnings_file)
|
|
346
|
+
FileUtils.mkdir_p(dir) unless File.directory?(dir)
|
|
347
|
+
|
|
348
|
+
# Write each warning as a separate JSON line (JSONL format)
|
|
349
|
+
File.open(queue_config.warnings_file, 'a') do |f|
|
|
350
|
+
warnings.each do |warning|
|
|
351
|
+
f.puts(JSON.dump(warning))
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
rescue => error
|
|
355
|
+
STDERR.puts "Failed to write warnings: #{error.message}"
|
|
341
356
|
end
|
|
342
357
|
end
|
|
343
358
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ci-queue
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.82.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jean Boussier
|
|
@@ -203,7 +203,6 @@ files:
|
|
|
203
203
|
- lib/ci/queue/redis/grind_record.rb
|
|
204
204
|
- lib/ci/queue/redis/grind_supervisor.rb
|
|
205
205
|
- lib/ci/queue/redis/heartbeat.lua
|
|
206
|
-
- lib/ci/queue/redis/key_shortener.rb
|
|
207
206
|
- lib/ci/queue/redis/monitor.rb
|
|
208
207
|
- lib/ci/queue/redis/release.lua
|
|
209
208
|
- lib/ci/queue/redis/requeue.lua
|
|
@@ -257,7 +256,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
257
256
|
- !ruby/object:Gem::Version
|
|
258
257
|
version: '0'
|
|
259
258
|
requirements: []
|
|
260
|
-
rubygems_version:
|
|
259
|
+
rubygems_version: 4.0.6
|
|
261
260
|
specification_version: 4
|
|
262
261
|
summary: Distribute tests over many workers using a queue
|
|
263
262
|
test_files: []
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
require 'digest/md5'
|
|
3
|
-
|
|
4
|
-
module CI
|
|
5
|
-
module Queue
|
|
6
|
-
module Redis
|
|
7
|
-
module KeyShortener
|
|
8
|
-
# Suffix mapping for common key patterns
|
|
9
|
-
SUFFIX_ALIASES = {
|
|
10
|
-
'running' => 'r',
|
|
11
|
-
'processed' => 'p',
|
|
12
|
-
'queue' => 'q',
|
|
13
|
-
'owners' => 'o',
|
|
14
|
-
'error-reports' => 'e',
|
|
15
|
-
'requeues-count' => 'rc',
|
|
16
|
-
'assertions' => 'a',
|
|
17
|
-
'errors' => 'er',
|
|
18
|
-
'failures' => 'f',
|
|
19
|
-
'skips' => 's',
|
|
20
|
-
'requeues' => 'rq',
|
|
21
|
-
'total_time' => 't',
|
|
22
|
-
'test_failed_count' => 'fc',
|
|
23
|
-
'completed' => 'c',
|
|
24
|
-
'master-status' => 'm',
|
|
25
|
-
'created-at' => 'ca',
|
|
26
|
-
'workers' => 'w',
|
|
27
|
-
'worker' => 'w',
|
|
28
|
-
'warnings' => 'wn',
|
|
29
|
-
'worker-errors' => 'we',
|
|
30
|
-
'flaky-reports' => 'fl',
|
|
31
|
-
}.freeze
|
|
32
|
-
|
|
33
|
-
# We're transforming the key to a shorter format to minimize network traffic.
|
|
34
|
-
#
|
|
35
|
-
# Strategy:
|
|
36
|
-
# - Shorten prefix: 'b' instead of 'build'
|
|
37
|
-
# - Hash UUID: 8-char MD5 instead of 36-char UUID
|
|
38
|
-
# - Alias suffixes: single letters instead of full words
|
|
39
|
-
#
|
|
40
|
-
# Example:
|
|
41
|
-
# build:unit:019aef0e-c010-433e-b706-c658d3c16372:running (55 bytes)
|
|
42
|
-
# -> b:f03d3bef:r (13 bytes, 76% reduction)
|
|
43
|
-
|
|
44
|
-
def self.key(build_id, *args)
|
|
45
|
-
digest = Digest::MD5.hexdigest(build_id)[0..7]
|
|
46
|
-
shortened_args = args.map { |arg| SUFFIX_ALIASES[arg] || arg }
|
|
47
|
-
|
|
48
|
-
['b', digest, *shortened_args].join(':')
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
end
|