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 const int report_size = report.size();
84 while (pos < report_size) {
85 int64 end_of_line = report.find('\n', pos);
86 const int64 _npos = string::npos;
87 if (end_of_line == _npos) {
88 end_of_line = report.size();
89 }
90 absl::string_view line(report.data() + pos, end_of_line - pos);
91
92 // TODO(b/34779244): Figure out how to do this without the verbose log-line
93 // prefix. The usual way didn't compile on open source.
94 LOG(INFO) << line;
95
96 pos = end_of_line + 1;
97 }
98 }
99
MakeCategories(const std::vector<Entry> * entries)100 std::vector<MetricTableReport::Category> MetricTableReport::MakeCategories(
101 const std::vector<Entry>* entries) {
102 // Create the categories using a category_text -> category map.
103 std::unordered_map<string, Category> category_map;
104 for (const Entry& entry : *entries) {
105 Category& category = category_map[entry.category_text];
106 category.metric_sum += entry.metric;
107 category.entries.push_back(&entry);
108 }
109
110 // Move the categories to a vector.
111 std::vector<Category> categories;
112 categories.reserve(category_map.size());
113 for (auto& key_value_pair : category_map) {
114 categories.push_back(std::move(key_value_pair.second));
115 categories.back().category_text = key_value_pair.first;
116 }
117
118 // Sort the categories.
119 auto metric_sum_greater = [](const Category& a, const Category& b) {
120 return a.metric_sum > b.metric_sum;
121 };
122 absl::c_sort(categories, metric_sum_greater);
123
124 return categories;
125 }
126
AppendHeader()127 void MetricTableReport::AppendHeader() {
128 AppendLine("********** ", metric_name_, " report **********");
129 AppendLine("There are ", MetricString(expected_metric_sum_), " ",
130 metric_name_, " in total.");
131 AppendLine("There are ", MetricString(UnaccountedMetric()), " ", metric_name_,
132 " (", MetricPercent(UnaccountedMetric()),
133 ") not accounted for by the data.");
134 AppendLine("There are ", entries_.size(), " ", entry_name_, ".");
135 }
136
AppendCategoryTable()137 void MetricTableReport::AppendCategoryTable() {
138 const std::vector<Category> categories = MakeCategories(&entries_);
139
140 AppendLine("********** categories table for ", metric_name_, " **********");
141 AppendLine();
142
143 double metric_sum = UnaccountedMetric();
144 int64 categories_shown = 0;
145 for (const auto& category : categories) {
146 if (categories_shown >= max_entries_to_show_ ||
147 metric_sum / expected_metric_sum_ > max_metric_proportion_to_show_) {
148 break;
149 }
150 ++categories_shown;
151 metric_sum += category.metric_sum;
152
153 // Show the category.
154 string text = category.category_text;
155 if (text.empty()) {
156 text = "[no category]";
157 }
158 absl::StrAppend(&text, " (", category.entries.size(), " ", entry_name_,
159 ")");
160 AppendTableRow(text, category.metric_sum, metric_sum);
161
162 // Show the top entries in the category.
163 const char* const kIndentPrefix = " * ";
164 int64 entries_to_show = std::min<int64>(max_entries_per_category_to_show_,
165 category.entries.size());
166 const int64 category_entries_size = category.entries.size();
167 if (category_entries_size == entries_to_show + 1) {
168 // May as well show the last entry on the line that would otherwise say
169 // that there is a single entry not shown.
170 ++entries_to_show;
171 }
172 for (int64 i = 0; i < entries_to_show; ++i) {
173 AppendLine(kIndentPrefix, MetricPercent(category.entries[i]->metric), " ",
174 category.entries[i]->short_text);
175 }
176 const int64 remaining_entries = category.entries.size() - entries_to_show;
177 if (remaining_entries > 0) {
178 AppendLine(kIndentPrefix, "... (", remaining_entries, " more ",
179 entry_name_, ")");
180 }
181 }
182 const int64 remaining_categories = categories.size() - categories_shown;
183 if (remaining_categories > 0) {
184 AppendTableRow(
185 absl::StrCat("... (", remaining_categories, " more categories)"),
186 expected_metric_sum_ - metric_sum, expected_metric_sum_);
187 }
188 }
189
AppendEntryTable()190 void MetricTableReport::AppendEntryTable() {
191 AppendLine("********** ", entry_name_, " table for ", metric_name_,
192 " **********");
193 AppendLine();
194
195 double metric_sum = UnaccountedMetric();
196 int64 entries_shown = 0;
197 for (const auto& entry : entries_) {
198 if (entries_shown >= max_entries_to_show_ ||
199 metric_sum / expected_metric_sum_ > max_metric_proportion_to_show_) {
200 break;
201 }
202 ++entries_shown;
203 metric_sum += entry.metric;
204
205 string text = entry.text;
206 if (text.empty()) {
207 text = "[no entry text]";
208 }
209 AppendTableRow(text, entry.metric, metric_sum);
210 }
211 const int64 remaining_entries = entries_.size() - entries_shown;
212 if (remaining_entries > 0) {
213 AppendTableRow(
214 absl::StrCat("... (", remaining_entries, " more ", entry_name_, ")"),
215 expected_metric_sum_ - metric_sum, expected_metric_sum_);
216 }
217 }
218
AppendTableRow(const string & text,const double metric,const double running_metric_sum)219 void MetricTableReport::AppendTableRow(const string& text, const double metric,
220 const double running_metric_sum) {
221 // This is the widest metric number possible, assuming non-negative metrics,
222 // so align to that width.
223 const int64 max_metric_string_size =
224 MetricString(expected_metric_sum_).size();
225 string metric_string = MetricString(metric);
226
227 // Don't try to make a gigantic string and crash if expected_metric_sum_ is
228 // wrong somehow.
229 int64 padding_len = 1;
230 const int64 metric_string_size = metric_string.size();
231 if (max_metric_string_size >= metric_string_size) {
232 padding_len += max_metric_string_size - metric_string.size();
233 }
234 string padding(padding_len, ' ');
235 AppendLine(padding, metric_string, " (", MetricPercent(metric), " Σ",
236 MetricPercent(running_metric_sum), ") ", text);
237 }
238
UnaccountedMetric()239 double MetricTableReport::UnaccountedMetric() {
240 double metric_sum = 0.0;
241 for (const auto& entry : entries_) {
242 metric_sum += entry.metric;
243 }
244 return expected_metric_sum_ - metric_sum;
245 }
246
MetricString(double metric)247 string MetricTableReport::MetricString(double metric) {
248 // Round to integer and stringify.
249 string s1 = absl::StrCat(std::llround(metric));
250
251 // Code below commafies the string, e.g. "1234" becomes "1,234".
252 absl::string_view sp1(s1);
253 string output;
254 // Copy leading non-digit characters unconditionally.
255 // This picks up the leading sign.
256 while (!sp1.empty() && !absl::ascii_isdigit(sp1[0])) {
257 output.push_back(sp1[0]);
258 sp1.remove_prefix(1);
259 }
260 // Copy rest of input characters.
261 for (int64 i = 0, end = sp1.size(); i < end; ++i) {
262 if (i > 0 && (sp1.size() - i) % 3 == 0) {
263 output.push_back(',');
264 }
265 output.push_back(sp1[i]);
266 }
267 return output;
268 }
269
MetricPercent(double metric)270 string MetricTableReport::MetricPercent(double metric) {
271 return absl::StrFormat("%5.2f%%", metric / expected_metric_sum_ * 100.0);
272 }
273
274 } // namespace xla
275