polyrun 1.5.0 → 2.1.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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -0
  3. data/README.md +2 -2
  4. data/docs/SETUP_PROFILE.md +2 -0
  5. data/lib/polyrun/cli/help.rb +7 -2
  6. data/lib/polyrun/cli/helpers.rb +16 -0
  7. data/lib/polyrun/cli/init_command.rb +8 -1
  8. data/lib/polyrun/cli/partition_diagnostics.rb +22 -0
  9. data/lib/polyrun/cli/plan_command.rb +47 -18
  10. data/lib/polyrun/cli/queue_command.rb +25 -2
  11. data/lib/polyrun/cli/run_queue_command.rb +145 -0
  12. data/lib/polyrun/cli/run_shards_command.rb +6 -1
  13. data/lib/polyrun/cli/run_shards_parallel_children.rb +2 -1
  14. data/lib/polyrun/cli/run_shards_plan_boot_phases.rb +47 -2
  15. data/lib/polyrun/cli/run_shards_plan_options.rb +12 -2
  16. data/lib/polyrun/cli/run_shards_planning.rb +20 -12
  17. data/lib/polyrun/cli/run_shards_run.rb +21 -4
  18. data/lib/polyrun/cli/spec_quality_commands.rb +140 -0
  19. data/lib/polyrun/cli.rb +16 -2
  20. data/lib/polyrun/coverage/example_diff.rb +122 -0
  21. data/lib/polyrun/data/factory_counts.rb +14 -1
  22. data/lib/polyrun/database/clone_shards.rb +2 -0
  23. data/lib/polyrun/database/shard.rb +2 -1
  24. data/lib/polyrun/minitest.rb +9 -0
  25. data/lib/polyrun/partition/hrw.rb +40 -3
  26. data/lib/polyrun/partition/paths_build.rb +8 -3
  27. data/lib/polyrun/partition/plan.rb +88 -19
  28. data/lib/polyrun/partition/plan_lpt.rb +49 -7
  29. data/lib/polyrun/partition/plan_sharding.rb +8 -0
  30. data/lib/polyrun/partition/reports.rb +139 -0
  31. data/lib/polyrun/partition/timing_diagnostics.rb +139 -0
  32. data/lib/polyrun/partition/timing_keys.rb +2 -1
  33. data/lib/polyrun/queue/duration.rb +30 -0
  34. data/lib/polyrun/queue/file_store.rb +107 -3
  35. data/lib/polyrun/quick/example_runner.rb +2 -0
  36. data/lib/polyrun/quick/runner.rb +21 -0
  37. data/lib/polyrun/rspec.rb +8 -0
  38. data/lib/polyrun/spec_quality/config.rb +134 -0
  39. data/lib/polyrun/spec_quality/fragment.rb +39 -0
  40. data/lib/polyrun/spec_quality/merge.rb +78 -0
  41. data/lib/polyrun/spec_quality/minitest_hook.rb +42 -0
  42. data/lib/polyrun/spec_quality/plan_loader.rb +47 -0
  43. data/lib/polyrun/spec_quality/profile.rb +91 -0
  44. data/lib/polyrun/spec_quality/report.rb +261 -0
  45. data/lib/polyrun/spec_quality/rspec_hook.rb +55 -0
  46. data/lib/polyrun/spec_quality/sql_counter.rb +34 -0
  47. data/lib/polyrun/spec_quality.rb +205 -0
  48. data/lib/polyrun/templates/POLYRUN.md +6 -0
  49. data/lib/polyrun/templates/ci_matrix.polyrun.yml +4 -0
  50. data/lib/polyrun/templates/polyrun_hooks_spec_quality.rb +12 -0
  51. data/lib/polyrun/templates/polyrun_spec_quality.yml +20 -0
  52. data/lib/polyrun/templates/rails_prepare.polyrun.yml +5 -0
  53. data/lib/polyrun/timing/merge.rb +5 -5
  54. data/lib/polyrun/timing/stats.rb +76 -0
  55. data/lib/polyrun/timing/summary.rb +5 -2
  56. data/lib/polyrun/timing/variance_report.rb +51 -0
  57. data/lib/polyrun/version.rb +1 -1
  58. metadata +22 -1
@@ -0,0 +1,76 @@
1
+ module Polyrun
2
+ module Timing
3
+ # Normalizes scalar or object timing entries for merge and binpack weight lookup.
4
+ module Stats
5
+ STAT_KEYS = %w[last_seconds min max mean p95 runs failures timeouts].freeze
6
+
7
+ module_function
8
+
9
+ def normalize_entry(value)
10
+ case value
11
+ when Hash
12
+ normalize_hash(value)
13
+ else
14
+ sec = value.to_f
15
+ {
16
+ "last_seconds" => sec,
17
+ "min" => sec,
18
+ "max" => sec,
19
+ "mean" => sec,
20
+ "p95" => sec,
21
+ "runs" => 1,
22
+ "failures" => 0,
23
+ "timeouts" => 0
24
+ }
25
+ end
26
+ end
27
+
28
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity -- timing hash key coercion
29
+ def normalize_hash(h)
30
+ out = {}
31
+ sec = h["last_seconds"] || h[:last_seconds] || h["seconds"] || h[:seconds]
32
+ sec = sec.to_f if sec
33
+ mean = (h["mean"] || h[:mean] || sec)&.to_f
34
+ out["last_seconds"] = (sec || mean || 0.0).to_f
35
+ out["min"] = (h["min"] || h[:min] || out["last_seconds"]).to_f
36
+ out["max"] = (h["max"] || h[:max] || out["last_seconds"]).to_f
37
+ out["mean"] = (mean || out["last_seconds"]).to_f
38
+ out["p95"] = (h["p95"] || h[:p95] || out["max"]).to_f
39
+ out["runs"] = Integer(h["runs"] || h[:runs] || 1)
40
+ out["failures"] = Integer(h["failures"] || h[:failures] || 0)
41
+ out["timeouts"] = Integer(h["timeouts"] || h[:timeouts] || 0)
42
+ out
43
+ end
44
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
45
+
46
+ def binpack_weight(entry)
47
+ h = normalize_entry(entry)
48
+ h["last_seconds"].positive? ? h["last_seconds"] : h["mean"]
49
+ end
50
+
51
+ # rubocop:disable Metrics/AbcSize -- weighted mean merge
52
+ def merge_entries(a, b)
53
+ ha = normalize_entry(a)
54
+ hb = normalize_entry(b)
55
+ runs = ha["runs"] + hb["runs"]
56
+ mean =
57
+ if runs.positive?
58
+ ((ha["mean"] * ha["runs"]) + (hb["mean"] * hb["runs"])) / runs.to_f
59
+ else
60
+ 0.0
61
+ end
62
+ {
63
+ "last_seconds" => [ha["last_seconds"], hb["last_seconds"]].max,
64
+ "min" => [ha["min"], hb["min"]].min,
65
+ "max" => [ha["max"], hb["max"]].max,
66
+ "mean" => mean,
67
+ "p95" => [ha["p95"], hb["p95"]].max,
68
+ "runs" => runs,
69
+ "failures" => ha["failures"] + hb["failures"],
70
+ "timeouts" => ha["timeouts"] + hb["timeouts"]
71
+ }
72
+ end
73
+ # rubocop:enable Metrics/AbcSize
74
+ end
75
+ end
76
+ end
@@ -1,14 +1,17 @@
1
+ require_relative "stats"
2
+
1
3
  module Polyrun
2
4
  module Timing
3
5
  # Human-readable slow-file list from merged timing JSON (per-file cost).
4
6
  module Summary
5
7
  module_function
6
8
 
7
- # +merged+ is path (String) => seconds (Float), as produced by +Timing::Merge.merge_files+.
9
+ # +merged+ is path (String) => seconds (Float) or stats Hash, as produced by +Timing::Merge.merge_files+.
8
10
  def format_slow_files(merged, top: 30, title: "Polyrun slowest files (by wall time, seconds)")
9
11
  return "#{title}\n (no data)\n" if merged.nil? || merged.empty?
10
12
 
11
- pairs = merged.sort_by { |_, sec| -sec.to_f }.first(Integer(top))
13
+ pairs = merged.map { |path, sec| [path, Stats.binpack_weight(sec)] }
14
+ .sort_by { |(_, sec)| -sec.to_f }.first(Integer(top))
12
15
  lines = [title, ""]
13
16
  pairs.each_with_index do |(path, sec), i|
14
17
  lines << format(" %2d. %s %.4f", i + 1, path, sec.to_f)
@@ -0,0 +1,51 @@
1
+ module Polyrun
2
+ module Timing
3
+ # Flags high-variance, flaky, and regression timing entries.
4
+ module VarianceReport
5
+ module_function
6
+
7
+ # rubocop:disable Metrics/AbcSize -- variance flag scan
8
+ def analyze(merged_stats)
9
+ flags = []
10
+ merged_stats.each do |path, entry|
11
+ h = Stats.normalize_entry(entry)
12
+ next if h["runs"] < 2
13
+
14
+ median = h["mean"]
15
+ if median.positive? && (h["p95"] / median) > 2.0
16
+ flags << {path: path, kind: "high_variance", detail: "p95/mean=#{format("%.2f", h["p95"] / median)}"}
17
+ end
18
+
19
+ if h["runs"] >= 3 && (h["failures"].to_f / h["runs"]) > 0.3
20
+ flags << {path: path, kind: "often_failed", detail: "failures=#{h["failures"]}/#{h["runs"]}"}
21
+ end
22
+
23
+ if h["timeouts"].to_i >= 2
24
+ flags << {path: path, kind: "timeout_cluster", detail: "timeouts=#{h["timeouts"]}"}
25
+ end
26
+
27
+ if h["mean"].positive? && h["last_seconds"] > (2.0 * h["mean"])
28
+ flags << {path: path, kind: "runtime_regression", detail: "last=#{h["last_seconds"]} mean=#{h["mean"]}"}
29
+ end
30
+ end
31
+ flags
32
+ end
33
+ # rubocop:enable Metrics/AbcSize
34
+
35
+ def emit_warnings!(merged_stats)
36
+ analyze(merged_stats).each do |f|
37
+ Polyrun::Log.warn "polyrun timing #{f[:kind]}: #{f[:path]} (#{f[:detail]})"
38
+ end
39
+ end
40
+
41
+ def format_report(merged_stats)
42
+ lines = ["Polyrun timing variance report", ""]
43
+ analyze(merged_stats).each do |f|
44
+ lines << " [#{f[:kind]}] #{f[:path]} — #{f[:detail]}"
45
+ end
46
+ lines << " (none)" if lines.size == 2
47
+ lines.join("\n") + "\n"
48
+ end
49
+ end
50
+ end
51
+ end
@@ -1,3 +1,3 @@
1
1
  module Polyrun
2
- VERSION = "1.5.0"
2
+ VERSION = "2.1.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: polyrun
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.0
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrei Makarov
@@ -180,12 +180,14 @@ files:
180
180
  - lib/polyrun/cli/helpers.rb
181
181
  - lib/polyrun/cli/hooks_command.rb
182
182
  - lib/polyrun/cli/init_command.rb
183
+ - lib/polyrun/cli/partition_diagnostics.rb
183
184
  - lib/polyrun/cli/plan_command.rb
184
185
  - lib/polyrun/cli/prepare_command.rb
185
186
  - lib/polyrun/cli/prepare_recipe.rb
186
187
  - lib/polyrun/cli/queue_command.rb
187
188
  - lib/polyrun/cli/quick_command.rb
188
189
  - lib/polyrun/cli/report_commands.rb
190
+ - lib/polyrun/cli/run_queue_command.rb
189
191
  - lib/polyrun/cli/run_shards_command.rb
190
192
  - lib/polyrun/cli/run_shards_parallel_children.rb
191
193
  - lib/polyrun/cli/run_shards_parallel_wait.rb
@@ -194,6 +196,7 @@ files:
194
196
  - lib/polyrun/cli/run_shards_planning.rb
195
197
  - lib/polyrun/cli/run_shards_run.rb
196
198
  - lib/polyrun/cli/run_shards_worker_interrupt.rb
199
+ - lib/polyrun/cli/spec_quality_commands.rb
197
200
  - lib/polyrun/cli/start_bootstrap.rb
198
201
  - lib/polyrun/cli/timing_command.rb
199
202
  - lib/polyrun/config.rb
@@ -204,6 +207,7 @@ files:
204
207
  - lib/polyrun/coverage/collector.rb
205
208
  - lib/polyrun/coverage/collector_finish.rb
206
209
  - lib/polyrun/coverage/collector_fragment_meta.rb
210
+ - lib/polyrun/coverage/example_diff.rb
207
211
  - lib/polyrun/coverage/filter.rb
208
212
  - lib/polyrun/coverage/formatter.rb
209
213
  - lib/polyrun/coverage/merge.rb
@@ -252,11 +256,14 @@ files:
252
256
  - lib/polyrun/partition/plan.rb
253
257
  - lib/polyrun/partition/plan_lpt.rb
254
258
  - lib/polyrun/partition/plan_sharding.rb
259
+ - lib/polyrun/partition/reports.rb
255
260
  - lib/polyrun/partition/stable_shuffle.rb
261
+ - lib/polyrun/partition/timing_diagnostics.rb
256
262
  - lib/polyrun/partition/timing_keys.rb
257
263
  - lib/polyrun/prepare/artifacts.rb
258
264
  - lib/polyrun/prepare/assets.rb
259
265
  - lib/polyrun/process_stdio.rb
266
+ - lib/polyrun/queue/duration.rb
260
267
  - lib/polyrun/queue/file_store.rb
261
268
  - lib/polyrun/queue/file_store_pending.rb
262
269
  - lib/polyrun/quick.rb
@@ -274,13 +281,27 @@ files:
274
281
  - lib/polyrun/reporting/rspec_failure_fragment_formatter.rb
275
282
  - lib/polyrun/reporting/rspec_junit.rb
276
283
  - lib/polyrun/rspec.rb
284
+ - lib/polyrun/spec_quality.rb
285
+ - lib/polyrun/spec_quality/config.rb
286
+ - lib/polyrun/spec_quality/fragment.rb
287
+ - lib/polyrun/spec_quality/merge.rb
288
+ - lib/polyrun/spec_quality/minitest_hook.rb
289
+ - lib/polyrun/spec_quality/plan_loader.rb
290
+ - lib/polyrun/spec_quality/profile.rb
291
+ - lib/polyrun/spec_quality/report.rb
292
+ - lib/polyrun/spec_quality/rspec_hook.rb
293
+ - lib/polyrun/spec_quality/sql_counter.rb
277
294
  - lib/polyrun/templates/POLYRUN.md
278
295
  - lib/polyrun/templates/ci_matrix.polyrun.yml
279
296
  - lib/polyrun/templates/minimal_gem.polyrun.yml
297
+ - lib/polyrun/templates/polyrun_hooks_spec_quality.rb
298
+ - lib/polyrun/templates/polyrun_spec_quality.yml
280
299
  - lib/polyrun/templates/rails_prepare.polyrun.yml
281
300
  - lib/polyrun/timing/merge.rb
282
301
  - lib/polyrun/timing/rspec_example_formatter.rb
302
+ - lib/polyrun/timing/stats.rb
283
303
  - lib/polyrun/timing/summary.rb
304
+ - lib/polyrun/timing/variance_report.rb
284
305
  - lib/polyrun/version.rb
285
306
  - lib/polyrun/worker_ping.rb
286
307
  - polyrun.gemspec