1 /* Copyright 2017 The TensorFlow Authors. All Rights Reserved.
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
16 #include "tensorflow/compiler/xla/metric_table_report.h"
17
18 #include <unordered_map>
19
20 #include "absl/strings/ascii.h"
21 #include "absl/strings/str_cat.h"
22 #include "absl/strings/str_format.h"
23 #include "tensorflow/core/platform/logging.h"
24 #include "tensorflow/core/platform/types.h"
25
26 namespace xla {
27
AddEntry(Entry entry)28 void MetricTableReport::AddEntry(Entry entry) {
29 entries_.push_back(std::move(entry));
30 }
31
SetMetricName(string metric_name)32 void MetricTableReport::SetMetricName(string metric_name) {
33 metric_name_ = std::move(metric_name);
34 }
35
SetEntryName(string entry_name)36 void MetricTableReport::SetEntryName(string entry_name) {
37 entry_name_ = std::move(entry_name);
38 }
39
SetShowAllEntries()40 void MetricTableReport::SetShowAllEntries() {
41 max_entries_to_show_ = std::numeric_limits<int64>::max();
42 max_entries_per_category_to_show_ = std::numeric_limits<int64>::max();
43 max_metric_proportion_to_show_ = 1.1; // more than 100%
44 }
45
SetShowCategoryTable()46 void MetricTableReport::SetShowCategoryTable() { show_category_table_ = true; }
47
SetShowEntryTable()48 void MetricTableReport::SetShowEntryTable() { show_entry_table_ = true; }
49
MakeReport(double expected_metric_sum)50 string MetricTableReport::MakeReport(double expected_metric_sum) {
51 expected_metric_sum_ = expected_metric_sum;
52 report_.clear();
53
54 // Sort the entries.
55 const auto metric_greater = [](const Entry& a, const Entry& b) {
56 return a.metric > b.metric;
57 };
58 absl::c_sort(entries_, metric_greater);
59
60 // Create the report
61 AppendLine();
62 AppendHeader();
63
64 if (show_category_table_) {
65 AppendLine();
66 AppendCategoryTable();
67 }
68 if (show_entry_table_) {
69 AppendLine();
70 AppendEntryTable();
71 }
72 AppendLine();
73
74 return std::move(report_);
75 }
76
WriteReportToInfoLog(double expected_metric_sum)77 void MetricTableReport::WriteReportToInfoLog(double expected_metric_sum) {
78 // Write something to the log normally to get the date-time and file prefix.
79 LOG(INFO) << "Writing report to log.";
80
81 int64 pos = 0;
82 const string report = MakeReport(expected_metric_sum);
83 while (pos < report.size()) {
84 int64 end_of_line = report.find('\n', pos);
85 if (end_of_line == string::npos) {
86 end_of_line = report.size();
87 }
88 absl::string_view line(report.data() + pos, end_of_line - pos);
89
90 // TODO(b/34779244): Figure out how to do this without the verbose log-line
91 // prefix. The usual way didn't compile on open source.
92 LOG(INFO) << line;
93
94 pos = end_of_line + 1;
95 }
96 }
97
MakeCategories(const std::vector<Entry> * entries)98 std::vector<MetricTableReport::Category> MetricTableReport::MakeCategories(
99 const std::vector<Entry>* entries) {
100 // Create the categories using a category_text -> category map.
101 std::unordered_map<string, Category> category_map;
102 for (const Entry& entry : *entries) {
103 Category& category = category_map[entry.category_text];
104 category.metric_sum += entry.metric;
105 category.entries.push_back(&entry);
106 }
107
108 // Move the categories to a vector.
109 std::vector<Category> categories;
110 categories.reserve(category_map.size());
111 for (auto& key_value_pair : category_map) {
112 categories.push_back(std::move(key_value_pair.second));
113 categories.back().category_text = key_value_pair.first;
114 }
115
116 // Sort the categories.
117 auto metric_sum_greater = [](const Category& a, const Category& b) {
118 return a.metric_sum > b.metric_sum;
119 };
120 absl::c_sort(categories, metric_sum_greater);
121
122 return categories;
123 }
124
AppendHeader()125 void MetricTableReport::AppendHeader() {
126 AppendLine("********** ", metric_name_, " report **********");
127 AppendLine("There are ", MetricString(expected_metric_sum_), " ",
128 metric_name_, " in total.");
129 AppendLine("There are ", MetricString(UnaccountedMetric()), " ", metric_name_,
130 " (", MetricPercent(UnaccountedMetric()),
131 ") not accounted for by the data.");
132 AppendLine("There are ", entries_.size(), " ", entry_name_, ".");
133 }
134
AppendCategoryTable()135 void MetricTableReport::AppendCategoryTable() {
136 const std::vector<Category> categories = MakeCategories(&entries_);
137
138 AppendLine("********** categories table for ", metric_name_, " **********");
139 AppendLine();
140
141 double metric_sum = UnaccountedMetric();
142 int64 categories_shown = 0;
143 for (const auto& category : categories) {
144 if (categories_shown >= max_entries_to_show_ ||
145 metric_sum / expected_metric_sum_ > max_metric_proportion_to_show_) {
146 break;
147 }
148 ++categories_shown;
149 metric_sum += category.metric_sum;
150
151 // Show the category.
152 string text = category.category_text;
153 if (text.empty()) {
154 text = "[no category]";
155 }
156 absl::StrAppend(&text, " (", category.entries.size(), " ", entry_name_,
157 ")");
158 AppendTableRow(text, category.metric_sum, metric_sum);
159
160 // Show the top entries in the category.
161 const char* const kIndentPrefix = " * ";
162 int64 entries_to_show = std::min<int64>(max_entries_per_category_to_show_,
163 category.entries.size());
164 if (category.entries.size() == entries_to_show + 1) {
165 // May as well show the last entry on the line that would otherwise say
166 // that there is a single entry not shown.
167 ++entries_to_show;
168 }
169 for (int64 i = 0; i < entries_to_show; ++i) {
170 AppendLine(kIndentPrefix, MetricPercent(category.entries[i]->metric), " ",
171 category.entries[i]->short_text);
172 }
173 const int64 remaining_entries = category.entries.size() - entries_to_show;
174 if (remaining_entries > 0) {
175 AppendLine(kIndentPrefix, "... (", remaining_entries, " more ",
176 entry_name_, ")");
177 }
178 }
179 const int64 remaining_categories = categories.size() - categories_shown;
180 if (remaining_categories > 0) {
181 AppendTableRow(
182 absl::StrCat("... (", remaining_categories, " more categories)"),
183 expected_metric_sum_ - metric_sum, expected_metric_sum_);
184 }
185 }
186
AppendEntryTable()187 void MetricTableReport::AppendEntryTable() {
188 AppendLine("********** ", entry_name_, " table for ", metric_name_,
189 " **********");
190 AppendLine();
191
192 double metric_sum = UnaccountedMetric();
193 int64 entries_shown = 0;
194 for (const auto& entry : entries_) {
195 if (entries_shown >= max_entries_to_show_ ||
196 metric_sum / expected_metric_sum_ > max_metric_proportion_to_show_) {
197 break;
198 }
199 ++entries_shown;
200 metric_sum += entry.metric;
201
202 string text = entry.text;
203 if (text.empty()) {
204 text = "[no entry text]";
205 }
206 AppendTableRow(text, entry.metric, metric_sum);
207 }
208 const int64 remaining_entries = entries_.size() - entries_shown;
209 if (remaining_entries > 0) {
210 AppendTableRow(
211 absl::StrCat("... (", remaining_entries, " more ", entry_name_, ")"),
212 expected_metric_sum_ - metric_sum, expected_metric_sum_);
213 }
214 }
215
AppendTableRow(const string & text,const double metric,const double running_metric_sum)216 void MetricTableReport::AppendTableRow(const string& text, const double metric,
217 const double running_metric_sum) {
218 // This is the widest metric number possible, assuming non-negative metrics,
219 // so align to that width.
220 const int64 max_metric_string_size =
221 MetricString(expected_metric_sum_).size();
222 string metric_string = MetricString(metric);
223
224 // Don't try to make a gigantic string and crash if expected_metric_sum_ is
225 // wrong somehow.
226 int64 padding_len = 1;
227 if (max_metric_string_size >= metric_string.size()) {
228 padding_len += max_metric_string_size - metric_string.size();
229 }
230 string padding(padding_len, ' ');
231 AppendLine(padding, metric_string, " (", MetricPercent(metric), " Σ",
232 MetricPercent(running_metric_sum), ") ", text);
233 }
234
UnaccountedMetric()235 double MetricTableReport::UnaccountedMetric() {
236 double metric_sum = 0.0;
237 for (const auto& entry : entries_) {
238 metric_sum += entry.metric;
239 }
240 return expected_metric_sum_ - metric_sum;
241 }
242
MetricString(double metric)243 string MetricTableReport::MetricString(double metric) {
244 // Round to integer and stringify.
245 string s1 = absl::StrCat(std::llround(metric));
246
247 // Code below commafies the string, e.g. "1234" becomes "1,234".
248 absl::string_view sp1(s1);
249 string output;
250 // Copy leading non-digit characters unconditionally.
251 // This picks up the leading sign.
252 while (!sp1.empty() && !absl::ascii_isdigit(sp1[0])) {
253 output.push_back(sp1[0]);
254 sp1.remove_prefix(1);
255 }
256 // Copy rest of input characters.
257 for (int64 i = 0; i < sp1.size(); ++i) {
258 if (i > 0 && (sp1.size() - i) % 3 == 0) {
259 output.push_back(',');
260 }
261 output.push_back(sp1[i]);
262 }
263 return output;
264 }
265
MetricPercent(double metric)266 string MetricTableReport::MetricPercent(double metric) {
267 return absl::StrFormat("%5.2f%%", metric / expected_metric_sum_ * 100.0);
268 }
269
270 } // namespace xla
271