|
| 1 | +// Copyright 2025 The XLS Authors |
| 2 | +// |
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | +// you may not use this file except in compliance with the License. |
| 5 | +// You may obtain a copy of the License at |
| 6 | +// |
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +// |
| 9 | +// Unless required by applicable law or agreed to in writing, software |
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | +// See the License for the specific language governing permissions and |
| 13 | +// limitations under the License. |
| 14 | + |
| 15 | +#include "xls/dev_tools/pipeline_metrics.h" |
| 16 | + |
| 17 | +#include <algorithm> |
| 18 | +#include <cstdint> |
| 19 | +#include <numeric> |
| 20 | +#include <optional> |
| 21 | +#include <string> |
| 22 | +#include <string_view> |
| 23 | +#include <tuple> |
| 24 | +#include <utility> |
| 25 | +#include <vector> |
| 26 | + |
| 27 | +#include "google/protobuf/duration.pb.h" |
| 28 | +#include "absl/container/flat_hash_map.h" |
| 29 | +#include "absl/strings/str_cat.h" |
| 30 | +#include "absl/strings/str_format.h" |
| 31 | +#include "absl/strings/str_join.h" |
| 32 | +#include "absl/time/time.h" |
| 33 | +#include "xls/ir/package.h" |
| 34 | +#include "xls/passes/pass_metrics.pb.h" |
| 35 | + |
| 36 | +namespace xls { |
| 37 | +namespace { |
| 38 | + |
| 39 | +int64_t DurationToMs(absl::Duration duration) { |
| 40 | + return duration / absl::Milliseconds(1); |
| 41 | +} |
| 42 | + |
| 43 | +// Struct holding the aggregation of multiple PassResultProtos. |
| 44 | +struct AggregateMetrics { |
| 45 | + std::string pass_name; |
| 46 | + int64_t run_count = 0; |
| 47 | + int64_t changed_count = 0; |
| 48 | + absl::Duration run_duration; |
| 49 | + TransformMetrics metrics; |
| 50 | + |
| 51 | + AggregateMetrics operator+(const AggregateMetrics& other) const { |
| 52 | + AggregateMetrics result; |
| 53 | + result.pass_name = (pass_name.empty() || pass_name == other.pass_name) |
| 54 | + ? other.pass_name |
| 55 | + : ""; |
| 56 | + result.run_count = run_count + other.run_count; |
| 57 | + result.changed_count = changed_count + other.changed_count; |
| 58 | + result.run_duration = run_duration + other.run_duration; |
| 59 | + result.metrics = metrics + other.metrics; |
| 60 | + return result; |
| 61 | + } |
| 62 | + |
| 63 | + static AggregateMetrics FromProto(const PassResultProto& proto) { |
| 64 | + AggregateMetrics metrics; |
| 65 | + metrics.pass_name = proto.pass_name(); |
| 66 | + metrics.run_count = 1; |
| 67 | + metrics.changed_count = proto.changed() ? 1 : 0; |
| 68 | + metrics.run_duration = absl::Seconds(proto.pass_duration().seconds()) + |
| 69 | + absl::Nanoseconds(proto.pass_duration().nanos()); |
| 70 | + metrics.metrics = TransformMetrics::FromProto(proto.metrics()); |
| 71 | + return metrics; |
| 72 | + } |
| 73 | +}; |
| 74 | + |
| 75 | +void AggregatePassResultsInternal( |
| 76 | + const PassResultProto& proto, |
| 77 | + absl::flat_hash_map<std::string, AggregateMetrics>& metrics_map) { |
| 78 | + if (proto.nested_results().empty()) { |
| 79 | + // Non-compound pass. |
| 80 | + AggregateMetrics& metrics = metrics_map[proto.pass_name()]; |
| 81 | + metrics = metrics + AggregateMetrics::FromProto(proto); |
| 82 | + } else { |
| 83 | + for (const PassResultProto& nested_proto : proto.nested_results()) { |
| 84 | + AggregatePassResultsInternal(nested_proto, metrics_map); |
| 85 | + } |
| 86 | + } |
| 87 | +} |
| 88 | + |
| 89 | +// Recursively walk the pass results within `proto` and aggregate the metrics by |
| 90 | +// pass name. Returns a vector sorted (decreasing) by run time. |
| 91 | +std::vector<AggregateMetrics> AggregatePassResults( |
| 92 | + const PassResultProto& proto) { |
| 93 | + absl::flat_hash_map<std::string, AggregateMetrics> metrics_map; |
| 94 | + AggregatePassResultsInternal(proto, metrics_map); |
| 95 | + std::vector<AggregateMetrics> metrics; |
| 96 | + for (auto& [_, m] : metrics_map) { |
| 97 | + metrics.push_back(m); |
| 98 | + } |
| 99 | + std::sort(metrics.begin(), metrics.end(), |
| 100 | + [&](const AggregateMetrics& a, const AggregateMetrics& b) { |
| 101 | + // Sort by time (at the same resolution we show), breaking ties by |
| 102 | + // # of times run, # of times changed, and finally pass name. |
| 103 | + auto key = [](const AggregateMetrics& x) { |
| 104 | + return std::tuple(DurationToMs(x.run_duration), x.run_count, |
| 105 | + x.changed_count, x.pass_name); |
| 106 | + }; |
| 107 | + // Sort high to low. |
| 108 | + return key(a) > key(b); |
| 109 | + }); |
| 110 | + return metrics; |
| 111 | +} |
| 112 | + |
| 113 | +void BuildHierarchicalTableInternal( |
| 114 | + const PassResultProto& proto, int64_t indent, |
| 115 | + std::vector<std::string>& lines, |
| 116 | + std::optional<AggregateMetrics>& collapsed_summary_metrics) { |
| 117 | + std::string indent_str(indent * 2, ' '); |
| 118 | + if (proto.nested_results().empty()) { |
| 119 | + // Collapse sequences of non-compound passes into a single line. |
| 120 | + if (!collapsed_summary_metrics.has_value()) { |
| 121 | + collapsed_summary_metrics = AggregateMetrics::FromProto(proto); |
| 122 | + } else { |
| 123 | + *collapsed_summary_metrics = |
| 124 | + *collapsed_summary_metrics + AggregateMetrics::FromProto(proto); |
| 125 | + } |
| 126 | + return; |
| 127 | + } |
| 128 | + |
| 129 | + auto add_line = [&](std::string_view pass_name, |
| 130 | + const AggregateMetrics& metrics) { |
| 131 | + lines.push_back(absl::StrFormat( |
| 132 | + "%-55s %6dms %4d/%4d %8d(+)/%8d(-)/%8d(R) " |
| 133 | + " %8d(-)/%8d(R)", |
| 134 | + indent_str + std::string{pass_name}, DurationToMs(metrics.run_duration), |
| 135 | + metrics.changed_count, metrics.run_count, metrics.metrics.nodes_added, |
| 136 | + metrics.metrics.nodes_removed, metrics.metrics.nodes_replaced, |
| 137 | + metrics.metrics.operands_removed, metrics.metrics.operands_replaced)); |
| 138 | + }; |
| 139 | + |
| 140 | + auto maybe_add_summary_line = [&](bool extra_indent) { |
| 141 | + if (collapsed_summary_metrics.has_value()) { |
| 142 | + add_line(absl::StrFormat("%s[%d passes run]", extra_indent ? " " : "", |
| 143 | + collapsed_summary_metrics->run_count), |
| 144 | + *collapsed_summary_metrics); |
| 145 | + collapsed_summary_metrics.reset(); |
| 146 | + } |
| 147 | + }; |
| 148 | + |
| 149 | + maybe_add_summary_line(false); |
| 150 | + |
| 151 | + std::vector<std::pair<int64_t, int64_t>> intervals; |
| 152 | + if (proto.fixed_point_iterations() > 0) { |
| 153 | + // Fixed-point pass. Break the nested results into iterations. |
| 154 | + int64_t end = 0; |
| 155 | + int64_t pass_count = |
| 156 | + proto.nested_results().size() / proto.fixed_point_iterations(); |
| 157 | + while (end < proto.nested_results().size()) { |
| 158 | + int64_t next_end = |
| 159 | + std::min(int64_t{proto.nested_results().size()}, end + pass_count); |
| 160 | + intervals.push_back({end, next_end}); |
| 161 | + end = next_end; |
| 162 | + } |
| 163 | + } else { |
| 164 | + // Non-fixed point pass. Aggregate all nested results together. |
| 165 | + intervals.push_back({0, proto.nested_results().size()}); |
| 166 | + } |
| 167 | + |
| 168 | + int64_t iteration = 0; |
| 169 | + for (auto [start, end] : intervals) { |
| 170 | + AggregateMetrics interval_metrics; |
| 171 | + for (int64_t i = start; i < end; ++i) { |
| 172 | + interval_metrics = interval_metrics + |
| 173 | + AggregateMetrics::FromProto(proto.nested_results()[i]); |
| 174 | + } |
| 175 | + std::string pass_name = |
| 176 | + proto.fixed_point_iterations() > 0 |
| 177 | + ? absl::StrFormat("%s [iter #%d]", proto.pass_name(), iteration) |
| 178 | + : proto.pass_name(); |
| 179 | + add_line(pass_name, interval_metrics); |
| 180 | + for (int64_t i = start; i < end; ++i) { |
| 181 | + const PassResultProto& nested_proto = proto.nested_results()[i]; |
| 182 | + BuildHierarchicalTableInternal(nested_proto, indent + 1, lines, |
| 183 | + collapsed_summary_metrics); |
| 184 | + } |
| 185 | + |
| 186 | + maybe_add_summary_line(true); |
| 187 | + |
| 188 | + ++iteration; |
| 189 | + } |
| 190 | +} |
| 191 | + |
| 192 | +// Returns the lines of a table which mirrors the hierarchical structure of the |
| 193 | +// (compound) passes which generated the metrics in `proto`. |
| 194 | +std::string BuildHierarchicalTable(const PassResultProto& proto) { |
| 195 | + std::vector<std::string> lines; |
| 196 | + std::optional<AggregateMetrics> collapsed_summary_metrics; |
| 197 | + BuildHierarchicalTableInternal(proto, 0, lines, collapsed_summary_metrics); |
| 198 | + return absl::StrCat(absl::StrJoin(lines, "\n"), "\n"); |
| 199 | +} |
| 200 | + |
| 201 | +} // namespace |
| 202 | + |
| 203 | +std::string SummarizePipelineMetrics(const PipelineMetricsProto& metrics) { |
| 204 | + // The metrics object is recursive. Aggregate the results by pass name. |
| 205 | + std::vector<AggregateMetrics> aggregate_metrics = |
| 206 | + AggregatePassResults(metrics.pass_results()); |
| 207 | + std::string str = "Aggregate pass statistics:\n\n"; |
| 208 | + absl::StrAppendFormat( |
| 209 | + &str, |
| 210 | + "%-30s Duration Runs: changed/total Nodes " |
| 211 | + "added(+)/removed(-)/replaced(R) Operands removed(-)/replaced(R)\n", |
| 212 | + "Pass name"); |
| 213 | + std::string divider = std::string(135, '-') + "\n"; |
| 214 | + absl::StrAppend(&str, divider); |
| 215 | + auto make_line = [](const AggregateMetrics& metric) { |
| 216 | + return absl::StrFormat( |
| 217 | + " %-30s %6dms %4d/%4d %8d(+)/%8d(-)/%8d(R) " |
| 218 | + " %8d(-)/%8d(R)\n", |
| 219 | + metric.pass_name, DurationToMs(metric.run_duration), |
| 220 | + metric.changed_count, metric.run_count, metric.metrics.nodes_added, |
| 221 | + metric.metrics.nodes_removed, metric.metrics.nodes_replaced, |
| 222 | + metric.metrics.operands_removed, metric.metrics.operands_replaced); |
| 223 | + }; |
| 224 | + for (const AggregateMetrics& metric : aggregate_metrics) { |
| 225 | + absl::StrAppend(&str, make_line(metric)); |
| 226 | + } |
| 227 | + absl::StrAppend(&str, divider); |
| 228 | + AggregateMetrics total = std::accumulate( |
| 229 | + aggregate_metrics.begin(), aggregate_metrics.end(), AggregateMetrics()); |
| 230 | + total.pass_name = "Total"; |
| 231 | + absl::StrAppend(&str, make_line(total)); |
| 232 | + |
| 233 | + absl::StrAppend(&str, "\n\nHierarchical pass statistics:\n\n"); |
| 234 | + absl::StrAppendFormat( |
| 235 | + &str, |
| 236 | + "%-55s Duration Runs: changed/total Nodes " |
| 237 | + "added(+)/removed(-)/replaced(R) Operands removed(-)/replaced(R)\n", |
| 238 | + "Pass name"); |
| 239 | + absl::StrAppend(&str, std::string(161, '-') + "\n"); |
| 240 | + absl::StrAppend(&str, BuildHierarchicalTable(metrics.pass_results())); |
| 241 | + return str; |
| 242 | +} |
| 243 | + |
| 244 | +} // namespace xls |
0 commit comments