String ReportNode::AsTable()

in src/runtime/profiling.cc [201:392]


String ReportNode::AsTable(bool sort, bool aggregate) const {
  // aggregate calls by op hash (or op name if hash is not set) + argument shapes
  std::vector<Map<String, ObjectRef>> aggregated_calls;
  if (aggregate) {
    std::unordered_map<std::string, std::vector<size_t>> aggregates;
    for (size_t i = 0; i < calls.size(); i++) {
      auto& frame = calls[i];
      auto it = frame.find("Hash");
      std::string name = Downcast<String>(frame["Name"]);
      if (it != frame.end()) {
        name = Downcast<String>((*it).second);
      }
      if (frame.find("Argument Shapes") != frame.end()) {
        name += Downcast<String>(frame["Argument Shapes"]);
      }

      if (aggregates.find(name) == aggregates.end()) {
        aggregates[name] = {i};
      } else {
        aggregates[name].push_back(i);
      }
    }
    for (const auto& p : aggregates) {
      std::unordered_map<String, ObjectRef> aggregated;
      for (auto i : p.second) {
        for (auto& metric : calls[i]) {
          auto it = aggregated.find(metric.first);
          if (it == aggregated.end()) {
            aggregated[metric.first] = metric.second;
          } else {
            if (metric.second.as<DurationNode>()) {
              aggregated[metric.first] = ObjectRef(
                  make_object<DurationNode>(it->second.as<DurationNode>()->microseconds +
                                            metric.second.as<DurationNode>()->microseconds));
            } else if (metric.second.as<CountNode>()) {
              aggregated[metric.first] = ObjectRef(make_object<CountNode>(
                  it->second.as<CountNode>()->value + metric.second.as<CountNode>()->value));
            } else if (metric.second.as<PercentNode>()) {
              aggregated[metric.first] =
                  ObjectRef(make_object<PercentNode>(it->second.as<PercentNode>()->percent +
                                                     metric.second.as<PercentNode>()->percent));
            } else if (metric.second.as<StringObj>()) {
              // Don't do anything. Assume the two strings are the same.
            } else {
              LOG(FATAL) << "Can only aggregate metrics with types DurationNode, CountNode, "
                            "PercentNode, and StringObj, but got "
                         << metric.second->GetTypeKey();
            }
          }
        }
      }
      aggregated_calls.push_back(aggregated);
    }
  } else {
    for (auto call : calls) {
      aggregated_calls.push_back(call);
    }
  }

  // sort rows by duration
  if (sort) {
    std::sort(aggregated_calls.begin(), aggregated_calls.end(),
              [&](const Map<String, ObjectRef>& a, const Map<String, ObjectRef>& b) {
                return a.at("Duration (us)").as<DurationNode>()->microseconds >
                       b.at("Duration (us)").as<DurationNode>()->microseconds;
              });
  }

  // compute columnwise sums
  std::unordered_map<String, ObjectRef> col_sums;
  for (auto call : aggregated_calls) {
    for (auto p : call) {
      if (p.second.as<CountNode>()) {
        int64_t val = p.second.as<CountNode>()->value;
        auto it = col_sums.find(p.first);
        if (it != col_sums.end()) {
          val += it->second.as<CountNode>()->value;
        }
        col_sums[p.first] = ObjectRef(make_object<CountNode>(val));
      } else if (p.second.as<DurationNode>()) {
        double val = p.second.as<DurationNode>()->microseconds;
        auto it = col_sums.find(p.first);
        if (it != col_sums.end()) {
          val += it->second.as<DurationNode>()->microseconds;
        }
        col_sums[p.first] = ObjectRef(make_object<DurationNode>(val));
      } else if (p.second.as<PercentNode>()) {
        double val = p.second.as<PercentNode>()->percent;
        auto it = col_sums.find(p.first);
        if (it != col_sums.end()) {
          val += it->second.as<PercentNode>()->percent;
        }
        col_sums[p.first] = ObjectRef(make_object<PercentNode>(val));
      }
    }
  }
  col_sums["Name"] = String("Sum");
  aggregated_calls.push_back({{String("Name"), String("----------")}});  // separator
  aggregated_calls.push_back(col_sums);

  // per-device metrics
  for (auto p : device_metrics) {
    Map<String, ObjectRef> metrics = p.second;
    metrics.Set("Name", String("Total"));
    aggregated_calls.push_back(metrics);
  }

  // Table formatting
  std::unordered_set<std::string> unique_headers;

  for (auto row : aggregated_calls) {
    for (auto p : row) {
      unique_headers.insert(p.first);
    }
  }

  std::vector<std::string> headers = {"Name", "Duration (us)",
                                      "Percent"};  // always include these headers
  for (auto header : unique_headers) {
    if (header != "Name" && header != "Duration (us)" && header != "Percent") {
      headers.push_back(header);
    }
  }

  // Switch layout from row major to column major so we can easily compute column widths.
  std::vector<std::vector<std::string>> cols;
  for (auto header : headers) {
    cols.push_back({header});
  }
  for (auto row : aggregated_calls) {
    for (size_t i = 0; i < headers.size(); i++) {
      auto it = row.find(headers[i]);
      if (it == row.end()) {
        // fill empty data with empty strings
        cols[i].push_back("");
      } else {
        std::string val;
        if ((*it).second.as<CountNode>()) {
          std::stringstream s;
          s.imbue(std::locale(""));  // for 1000s seperators
          s << std::fixed << (*it).second.as<CountNode>()->value;
          val = s.str();
        } else if ((*it).second.as<DurationNode>()) {
          std::stringstream s;
          s.imbue(std::locale(""));  // for 1000s seperators
          s << std::fixed << std::setprecision(2) << (*it).second.as<DurationNode>()->microseconds;
          val = s.str();
        } else if ((*it).second.as<PercentNode>()) {
          std::stringstream s;
          s << std::fixed << std::setprecision(2) << (*it).second.as<PercentNode>()->percent;
          val = s.str();
        } else if ((*it).second.as<StringObj>()) {
          val = Downcast<String>((*it).second);
        }
        cols[i].push_back(val);
      }
    }
  }

  std::vector<size_t> widths;
  for (auto v : cols) {
    size_t width = 0;
    for (auto x : v) {
      width = std::max(width, x.size());
    }
    widths.push_back(width);
  }
  size_t length = 0;
  for (auto v : cols) {
    length = std::max(length, v.size());
  }

  std::stringstream s;
  for (size_t row = 0; row < length; row++) {
    for (size_t col = 0; col < cols.size(); col++) {
      // left align first column
      if (col == 0) {
        s << std::left;
      } else {
        s << std::right;
      }
      if (row < cols[col].size()) {
        s << std::setw(widths[col]) << cols[col][row] << "  ";
      } else {
        s << std::setw(widths[col]) << ""
          << "  ";
      }
    }
    s << std::endl;
  }
  return s.str();
}