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 <cctype> 19 #include <unordered_map> 20 21 #include "tensorflow/core/lib/strings/stringprintf.h" 22 #include "tensorflow/core/platform/logging.h" 23 #include "tensorflow/core/platform/types.h" 24 25 namespace xla { 26 27 void MetricTableReport::AddEntry(Entry entry) { 28 entries_.push_back(std::move(entry)); 29 } 30 31 void MetricTableReport::SetMetricName(string metric_name) { 32 metric_name_ = std::move(metric_name); 33 } 34 35 void MetricTableReport::SetEntryName(string entry_name) { 36 entry_name_ = std::move(entry_name); 37 } 38 39 void MetricTableReport::SetShowAllEntries() { 40 max_entries_to_show_ = std::numeric_limits<int64>::max(); 41 max_entries_per_category_to_show_ = std::numeric_limits<int64>::max(); 42 max_metric_proportion_to_show_ = 1.1; // more than 100% 43 } 44 45 void MetricTableReport::SetShowCategoryTable() { show_category_table_ = true; } 46 47 void MetricTableReport::SetShowEntryTable() { show_entry_table_ = true; } 48 49 string MetricTableReport::MakeReport(double expected_metric_sum) { 50 expected_metric_sum_ = expected_metric_sum; 51 report_.clear(); 52 53 // Sort the entries. 54 const auto metric_greater = [](const Entry& a, const Entry& b) { 55 return a.metric > b.metric; 56 }; 57 std::sort(entries_.begin(), entries_.end(), metric_greater); 58 59 // Create the report 60 AppendLine(); 61 AppendHeader(); 62 63 if (show_category_table_) { 64 AppendLine(); 65 AppendCategoryTable(); 66 } 67 if (show_entry_table_) { 68 AppendLine(); 69 AppendEntryTable(); 70 } 71 AppendLine(); 72 73 return std::move(report_); 74 } 75 76 void MetricTableReport::WriteReportToInfoLog(double expected_metric_sum) { 77 // Write something to the log normally to get the date-time and file prefix. 78 LOG(INFO) << "Writing report to log."; 79 80 int64 pos = 0; 81 const string report = MakeReport(expected_metric_sum); 82 while (pos < report.size()) { 83 int64 end_of_line = report.find('\n', pos); 84 if (end_of_line == string::npos) { 85 end_of_line = report.size(); 86 } 87 tensorflow::StringPiece line(report.data() + pos, end_of_line - pos); 88 89 // TODO(b/34779244): Figure out how to do this without the verbose log-line 90 // prefix. The usual way didn't compile on open source. 91 LOG(INFO) << line; 92 93 pos = end_of_line + 1; 94 } 95 } 96 97 std::vector<MetricTableReport::Category> MetricTableReport::MakeCategories( 98 const std::vector<Entry>* entries) { 99 // Create the categories using a category_text -> category map. 100 std::unordered_map<string, Category> category_map; 101 for (const Entry& entry : *entries) { 102 Category& category = category_map[entry.category_text]; 103 category.metric_sum += entry.metric; 104 category.entries.push_back(&entry); 105 } 106 107 // Move the categories to a vector. 108 std::vector<Category> categories; 109 categories.reserve(category_map.size()); 110 for (auto& key_value_pair : category_map) { 111 categories.push_back(std::move(key_value_pair.second)); 112 categories.back().category_text = key_value_pair.first; 113 } 114 115 // Sort the categories. 116 auto metric_sum_greater = [](const Category& a, const Category& b) { 117 return a.metric_sum > b.metric_sum; 118 }; 119 std::sort(categories.begin(), categories.end(), metric_sum_greater); 120 121 return categories; 122 } 123 124 void MetricTableReport::AppendHeader() { 125 AppendLine("********** ", metric_name_, " report **********"); 126 AppendLine("There are ", MetricString(expected_metric_sum_), " ", 127 metric_name_, " in total."); 128 AppendLine("There are ", MetricString(UnaccountedMetric()), " ", metric_name_, 129 " (", MetricPercent(UnaccountedMetric()), 130 ") not accounted for by the data."); 131 AppendLine("There are ", entries_.size(), " ", entry_name_, "."); 132 } 133 134 void MetricTableReport::AppendCategoryTable() { 135 const std::vector<Category> categories = MakeCategories(&entries_); 136 137 AppendLine("********** categories table **********"); 138 AppendLine("The left hand side numbers are ", 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 tensorflow::strings::StrAppend(&text, " (", category.entries.size(), " ", 157 entry_name_, ")"); 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(tensorflow::strings::StrCat("... (", remaining_categories, 182 " more categories)"), 183 expected_metric_sum_ - metric_sum, expected_metric_sum_); 184 } 185 } 186 187 void MetricTableReport::AppendEntryTable() { 188 AppendLine("********** ", entry_name_, " table **********"); 189 AppendLine("The left hand side numbers are ", metric_name_, "."); 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(tensorflow::strings::StrCat("... (", remaining_entries, 211 " more ", entry_name_, ")"), 212 expected_metric_sum_ - metric_sum, expected_metric_sum_); 213 } 214 } 215 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 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 243 string MetricTableReport::MetricString(double metric) { 244 // Round to integer and stringify. 245 string s1 = tensorflow::strings::StrCat(std::llround(metric)); 246 247 // Code below commafies the string, e.g. "1234" becomes "1,234". 248 tensorflow::StringPiece sp1(s1); 249 string output; 250 // Copy leading non-digit characters unconditionally. 251 // This picks up the leading sign. 252 while (!sp1.empty() && !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 266 string MetricTableReport::MetricPercent(double metric) { 267 return tensorflow::strings::Printf("%5.2f%%", 268 metric / expected_metric_sum_ * 100.0); 269 } 270 271 } // namespace xla 272