Module: Rperf::Table

Defined in:
lib/rperf/table.rb

Constant Summary collapse

ROWS_LIMIT =
50

Class Method Summary collapse

Class Method Details

.diff_json(base, head) ⇒ Object



119
120
121
# File 'lib/rperf/table.rb', line 119

def diff_json(base, head)
  JSON.generate(diff_rows(base, head) + [{ summary: diff_summary(base, head) }])
end

.diff_rows(base, head, limit: ROWS_LIMIT) ⇒ Object

Rows: method, self_pct_base, self_pct_head, delta_pt — |delta_pt| descending, top 50. Per-method allocation data does not exist in rperf profiles, so allocation appears only in the summary.



78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/rperf/table.rb', line 78

def diff_rows(base, head, limit: ROWS_LIMIT)
  base_flat, _, base_total = flat_cum_by_name(base)
  head_flat, _, head_total = flat_cum_by_name(head)
  names = (base_flat.keys | head_flat.keys)
  rows = names.map do |name|
    b = pct(base_flat[name] || 0, base_total)
    h = pct(head_flat[name] || 0, head_total)
    {
      method: name,
      self_pct_base: b,
      self_pct_head: h,
      delta_pt: (h - b).round(2),
    }
  end
  rows.sort_by! { |r| [-r[:delta_pt].abs, r[:method]] }
  rows.first(limit)
end

.diff_summary(base, head) ⇒ Object



96
97
98
99
100
101
102
103
104
105
106
# File 'lib/rperf/table.rb', line 96

def diff_summary(base, head)
  b = report_summary(base)
  h = report_summary(head)
  out = {}
  %i[total_ms allocated_objects gc_count_minor gc_count_major].each do |key|
    out[:"#{key}_base"] = b[key] if b[key]
    out[:"#{key}_head"] = h[key] if h[key]
    out[:"#{key}_delta"] = (h[key] - b[key]).round(1) if b[key] && h[key]
  end
  out
end

.diff_tsv(base, head) ⇒ Object



108
109
110
111
112
113
114
115
116
117
# File 'lib/rperf/table.rb', line 108

def diff_tsv(base, head)
  rows = diff_rows(base, head)
  out = String.new
  out << "method\tself_pct_base\tself_pct_head\tdelta_pt\n"
  rows.each do |r|
    out << [tsv_cell(r[:method]), r[:self_pct_base], r[:self_pct_head], r[:delta_pt]].join("\t") << "\n"
  end
  out << summary_line(diff_summary(base, head))
  out
end

.flat_cum_by_name(data) ⇒ Object

Flat / cumulative weights merged by method name (compute_flat_cum keys are [label, path]; a method split across paths counts once).



127
128
129
130
131
132
133
134
# File 'lib/rperf/table.rb', line 127

def flat_cum_by_name(data)
  result = Rperf.send(:compute_flat_cum, data[:aggregated_samples] || [])
  flat = Hash.new(0)
  result[:flat].each { |(label, _path), w| flat[label] += w }
  cum = Hash.new(0)
  result[:cum].each { |(label, _path), w| cum[label] += w }
  [flat, cum, result[:total_weight]]
end

.ms(ns) ⇒ Object



140
141
142
# File 'lib/rperf/table.rb', line 140

def ms(ns)
  (ns / 1e6).round(1)
end

.pct(weight, total) ⇒ Object



136
137
138
# File 'lib/rperf/table.rb', line 136

def pct(weight, total)
  total > 0 ? (weight * 100.0 / total).round(2) : 0.0
end

.report_json(data) ⇒ Object



69
70
71
# File 'lib/rperf/table.rb', line 69

def report_json(data)
  JSON.generate(report_rows(data) + [{ summary: report_summary(data) }])
end

.report_rows(data, limit: ROWS_LIMIT) ⇒ Object

Rows: method, self_pct, total_pct, self_ms — self_pct descending, top 50 plus an “(other)” row aggregating the rest.



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/rperf/table.rb', line 21

def report_rows(data, limit: ROWS_LIMIT)
  flat, cum, total = flat_cum_by_name(data)
  entries = flat.sort_by { |name, w| [-w, name] }
  rows = entries.first(limit).map do |name, w|
    {
      method: name,
      self_pct: pct(w, total),
      total_pct: pct(cum[name], total),
      self_ms: ms(w),
    }
  end
  if entries.size > limit
    # Aggregate the raw weights, not the already-rounded row values:
    # per-row rounding errors are systematic and would make the (other)
    # row inconsistent with itself (pct vs ms) and with the true total
    rest_weight = entries.drop(limit).sum { |_, w| w }
    rows << {
      method: "(other)",
      self_pct: pct(rest_weight, total),
      total_pct: nil,  # overlapping cumulative values cannot be summed
      self_ms: ms(rest_weight),
    }
  end
  rows
end

.report_summary(data) ⇒ Object



47
48
49
50
51
52
53
54
55
56
# File 'lib/rperf/table.rb', line 47

def report_summary(data)
  s = data[:summary] || Meta.build_summary(data)
  out = {}
  out[:total_ms] = s[:total_ms] if s[:total_ms]
  out[:cpu_ms] = s[:cpu_ms] if s[:cpu_ms]
  out[:allocated_objects] = s[:allocated_objects] if s[:allocated_objects]
  out[:gc_count_minor] = s[:gc_count_minor] if s[:gc_count_minor]
  out[:gc_count_major] = s[:gc_count_major] if s[:gc_count_major]
  out
end

.report_tsv(data) ⇒ Object



58
59
60
61
62
63
64
65
66
67
# File 'lib/rperf/table.rb', line 58

def report_tsv(data)
  rows = report_rows(data)
  out = String.new
  out << "method\tself_pct\ttotal_pct\tself_ms\n"
  rows.each do |r|
    out << [tsv_cell(r[:method]), r[:self_pct], r[:total_pct], r[:self_ms]].map { |v| v.nil? ? "" : v.to_s }.join("\t") << "\n"
  end
  out << summary_line(report_summary(data))
  out
end

.summary_line(summary) ⇒ Object



144
145
146
# File 'lib/rperf/table.rb', line 144

def summary_line(summary)
  (["# summary"] + summary.map { |k, v| "#{k}=#{v}" }).join("\t") + "\n"
end

.tsv_cell(value) ⇒ Object

TSV has no escaping convention — replace separator characters so a pathological method name (define_method allows any string) cannot shift columns or split a row.



151
152
153
154
# File 'lib/rperf/table.rb', line 151

def tsv_cell(value)
  s = value.to_s
  s.match?(/[\t\n\r]/) ? s.gsub(/[\t\n\r]/, " ") : s
end