Module: Polyrun::Benchmark::Profile

Defined in:
lib/polyrun/benchmark/profile.rb

Overview

Records benchmark output during performance specs and writes profile artifacts. Set POLYRUN_BENCH=1 to echo lines to stdout. JSON sidecar enables report-benchmark exports.

Constant Summary collapse

STORAGE_KEY =
:polyrun_benchmark_profile_storage

Class Method Summary collapse

Class Method Details

.commit_sha(repository_root: default_repository_root) ⇒ Object



127
128
129
# File 'lib/polyrun/benchmark/profile.rb', line 127

def commit_sha(repository_root: default_repository_root)
  git_command("git rev-parse HEAD", repository_root: repository_root) || "unknown"
end

.current_section_storageObject



182
183
184
# File 'lib/polyrun/benchmark/profile.rb', line 182

def current_section_storage
  storage["current_section"]
end

.default_repository_rootObject



186
187
188
# File 'lib/polyrun/benchmark/profile.rb', line 186

def default_repository_root
  Dir.pwd
end

.detect_section_title(line) ⇒ Object



148
149
150
151
152
153
154
155
# File 'lib/polyrun/benchmark/profile.rb', line 148

def detect_section_title(line)
  return unless line.is_a?(String)
  return if line.strip.empty?
  return if line.start_with?("#")
  return unless line.end_with?(":")

  line.strip.delete_suffix(":")
end

.export_extension(format) ⇒ Object



81
82
83
84
85
86
87
# File 'lib/polyrun/benchmark/profile.rb', line 81

def export_extension(format)
  case format.to_s.downcase
  when "markdown", "md" then "md"
  when "text", "console", "txt" then "txt"
  else format.to_s.downcase
  end
end

.export_formatsObject



74
75
76
77
78
79
# File 'lib/polyrun/benchmark/profile.rb', line 74

def export_formats
  raw = ENV["POLYRUN_BENCH_FORMATS"]
  return [] if raw.nil? || raw.to_s.strip.empty?

  raw.split(",").map(&:strip).reject(&:empty?)
end

.export_sidecars!(data, json_path) ⇒ Object



62
63
64
65
66
67
68
69
70
71
72
# File 'lib/polyrun/benchmark/profile.rb', line 62

def export_sidecars!(data, json_path)
  formats = export_formats
  return if formats.empty?

  require_relative "report"
  base = json_path.sub(/\.json\z/, "")
  formats.each do |format|
    path = "#{base}.#{export_extension(format)}"
    File.write(path, Report.render(data, format: format))
  end
end

.format_metric_line(metric) ⇒ Object



157
158
159
160
161
162
# File 'lib/polyrun/benchmark/profile.rb', line 157

def format_metric_line(metric)
  value = metric["value"]
  unit = metric["unit"]
  suffix = (unit == "seconds") ? "s" : " #{unit}"
  format("  %s: %s%s", metric["name"], value, suffix)
end

.git_command(command, repository_root:) ⇒ Object



139
140
141
142
143
144
145
146
# File 'lib/polyrun/benchmark/profile.rb', line 139

def git_command(command, repository_root:)
  stdout, status = Open3.capture2(command, chdir: repository_root)
  return nil unless status.success?

  stdout.strip
rescue
  nil
end

.lines_storageObject



174
175
176
# File 'lib/polyrun/benchmark/profile.rb', line 174

def lines_storage
  storage["lines"]
end

.log(message = "") ⇒ Object



18
19
20
21
22
23
24
25
# File 'lib/polyrun/benchmark/profile.rb', line 18

def log(message = "")
  line = message.to_s
  lines_storage << line
  section = detect_section_title(line) || current_section_storage.first
  current_section_storage[0] = section if detect_section_title(line)
  $stdout.puts(line) if verbose? && !line.empty?
  line
end

.metrics_storageObject



178
179
180
# File 'lib/polyrun/benchmark/profile.rb', line 178

def metrics_storage
  storage["metrics"]
end

.output_path(repository_root: default_repository_root, extension: "log", commit_sha: nil, working_tree_clean: nil, timestamp: nil) ⇒ Object



93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/polyrun/benchmark/profile.rb', line 93

def output_path(repository_root: default_repository_root, extension: "log", commit_sha: nil, working_tree_clean: nil, timestamp: nil)
  commit_identifier = commit_sha || self.commit_sha(repository_root: repository_root)
  clean_tree = working_tree_clean.nil? ? working_tree_clean?(repository_root: repository_root) : working_tree_clean
  filename = if clean_tree
    "profile_#{commit_identifier}.#{extension}"
  else
    recorded_at = timestamp || self.timestamp
    "profile_#{commit_identifier}_#{recorded_at}.#{extension}"
  end

  File.join(repository_root, "tmp", "benchmarks", filename)
end

.profile_header(meta) ⇒ Object



116
117
118
119
120
121
122
123
124
125
# File 'lib/polyrun/benchmark/profile.rb', line 116

def profile_header(meta)
  [
    "# Benchmark profile",
    "# commit: #{meta["commit"]}",
    "# recorded_at: #{meta["recorded_at"]}",
    "# working_tree_clean: #{meta["working_tree_clean"]}",
    "# ruby: #{meta["ruby"]}",
    ""
  ].join("\n")
end

.profile_meta(repository_root: default_repository_root) ⇒ Object



106
107
108
109
110
111
112
113
114
# File 'lib/polyrun/benchmark/profile.rb', line 106

def profile_meta(repository_root: default_repository_root)
  {
    "commit" => commit_sha(repository_root: repository_root),
    "recorded_at" => Time.now.utc.iso8601,
    "working_tree_clean" => working_tree_clean?(repository_root: repository_root),
    "ruby" => RUBY_VERSION,
    "polyrun_version" => Polyrun::VERSION
  }
end

.record_metric!(name:, value:, unit: "seconds", section: nil) ⇒ Object



27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/polyrun/benchmark/profile.rb', line 27

def record_metric!(name:, value:, unit: "seconds", section: nil)
  section_name = section || current_section_storage.first
  metric = {
    "section" => section_name.to_s,
    "name" => name.to_s,
    "value" => value,
    "unit" => unit.to_s
  }
  metrics_storage << metric
  log(format_metric_line(metric))
  metric
end

.reset!Object



12
13
14
15
16
# File 'lib/polyrun/benchmark/profile.rb', line 12

def reset!
  lines_storage.clear
  metrics_storage.clear
  current_section_storage.replace(["default"])
end

.snapshotObject



40
41
42
43
44
45
46
# File 'lib/polyrun/benchmark/profile.rb', line 40

def snapshot
  {
    "meta" => profile_meta,
    "lines" => lines_storage.dup,
    "metrics" => metrics_storage.dup
  }
end

.storageObject



166
167
168
169
170
171
172
# File 'lib/polyrun/benchmark/profile.rb', line 166

def storage
  Thread.current[STORAGE_KEY] ||= {
    "lines" => [],
    "metrics" => [],
    "current_section" => ["default"]
  }
end

.timestampObject



135
136
137
# File 'lib/polyrun/benchmark/profile.rb', line 135

def timestamp
  Time.now.utc.strftime("%Y%m%d%H%M%S")
end

.verbose?Boolean

Returns:

  • (Boolean)


89
90
91
# File 'lib/polyrun/benchmark/profile.rb', line 89

def verbose?
  %w[1 true yes].include?(ENV["POLYRUN_BENCH"]&.to_s&.downcase)
end

.working_tree_clean?(repository_root: default_repository_root) ⇒ Boolean

Returns:

  • (Boolean)


131
132
133
# File 'lib/polyrun/benchmark/profile.rb', line 131

def working_tree_clean?(repository_root: default_repository_root)
  git_command("git status --porcelain", repository_root: repository_root).to_s.empty?
end

.write!(repository_root: default_repository_root) ⇒ Object



48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/polyrun/benchmark/profile.rb', line 48

def write!(repository_root: default_repository_root)
  data = snapshot
  return if data["lines"].empty? && data["metrics"].empty?

  log_path = output_path(repository_root: repository_root, extension: "log")
  json_path = output_path(repository_root: repository_root, extension: "json")
  FileUtils.mkdir_p(File.dirname(log_path))
  File.write(log_path, profile_header(data["meta"]) + data["lines"].join("\n") + "\n")
  File.write(json_path, JSON.pretty_generate(data))
  export_sidecars!(data, json_path)
  $stdout.puts("\nBenchmark profile written to #{log_path}") if verbose?
  log_path
end