table_printer.h Source File

CPP API: table_printer.h Source File
table_printer.h
Go to the documentation of this file.
1 /*
2 * Copyright (C) 2020-2026 MEmilio
3 *
4 * Authors: Rene Schmieding
5 *
6 * Contact: Martin J. Kuehn <Martin.Kuehn@DLR.de>
7 *
8 * Licensed under the Apache License, Version 2.0 (the "License");
9 * you may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 */
20 #ifndef MIO_TIMER_TABLE_PRINTER_H
21 #define MIO_TIMER_TABLE_PRINTER_H
22 
25 #include "memilio/utils/logging.h" // included for fmt
26 
27 #include <algorithm>
28 #include <iomanip>
29 #include <iostream>
30 #include <limits>
31 #include <list>
32 #include <map>
33 #include <ostream>
34 #include <string>
35 #include <vector>
36 
37 namespace mio
38 {
39 namespace timing
40 {
41 namespace details
42 {
43 
45 template <class T>
46 class Table
47 {
48  struct Row {
49  std::string name;
50  std::vector<T> values;
51  };
52 
53 public:
54  Table(const std::string& name, const std::vector<std::string>& column_names,
55  const std::vector<std::string>& row_names = {})
56  : m_name(name)
57  , m_column_names(column_names)
58  , m_rows()
59  {
60  m_rows.reserve(row_names.size());
61  for (auto& rn : row_names) {
62  add_row(rn);
63  }
64  }
65 
66  void add_row(const std::string& name, const std::vector<T>& values)
67  {
68  assert(values.size() == m_column_names.size());
69  m_rows.push_back({name, values});
70  }
71 
72  void add_row(const std::string& name)
73  {
74  add_row(name, std::vector<T>(m_column_names.size()));
75  }
76 
77  T& operator()(size_t row, size_t col)
78  {
79  assert(row < m_rows.size());
80  assert(col < m_rows[row].values.size());
81  return m_rows[row].values[col];
82  }
83 
84  std::string& get_row_name(size_t row)
85  {
86  assert(row < m_rows.size());
87  return m_rows[row].name;
88  }
89 
90  std::string& get_col_name(size_t col)
91  {
92  assert(col < m_column_names.size());
93  return m_column_names[col];
94  }
95 
96  std::string& get_name()
97  {
98  return m_name;
99  }
100 
101  size_t rows() const
102  {
103  return m_rows.size();
104  }
105 
106  size_t cols() const
107  {
108  return m_column_names.size();
109  }
110 
111 private:
112  std::string m_name;
113  std::vector<std::string> m_column_names;
114  std::vector<Row> m_rows;
115 };
116 
117 } // namespace details
118 
119 class TablePrinter : public Printer
120 {
121 public:
126  void set_time_format(std::string format_string)
127  {
128  m_time_format = format_string;
129  }
130 
138  void print(const std::list<TimerRegistration>& timer_register, std::ostream& out = std::cout) override
139  {
140  auto [table, is_multithreaded, name_width, max_val] = create_table(timer_register);
141  // calculate column_widths
142  const size_t time_width = fmt::format(fmt::runtime(m_time_format), max_val).size();
143  const size_t thread_width = table.get_col_name(table.cols() - 1).size();
144  std::vector<size_t> col_widths(table.cols() + 1);
145  // note that col_width is offset by 1 relative to table, as it uses index 0 for the first value,
146  // but here we want to print the timer (or row) name first
147  col_widths[0] = name_width;
148  for (size_t col = 0; col < table.cols(); col++) {
149  col_widths[col + 1] = std::max(time_width, table.get_col_name(col).size());
150  }
151  if (is_multithreaded) { // reduce size for "#Threads" column
152  col_widths[table.cols()] = thread_width;
153  }
154  // vertical decorations
155  const std::string separator = " | ";
156  const std::string border_l = "| ";
157  const std::string border_r = " |\n";
158  // horizontal decoration, as a lambda
159  const auto draw_hline = [&]() {
160  for (auto& w : col_widths) {
161  out << "+" + std::string(w + 2, '-');
162  }
163  out << "+\n";
164  };
165 
166  draw_hline();
167  // table header, prints table and column names
168  out << border_l << std::setw(col_widths[0]) << std::left << table.get_name();
169  for (size_t col = 0; col < table.cols(); col++) {
170  out << separator << std::setw(col_widths[col + 1]) << std::left << table.get_col_name(col);
171  }
172  out << border_r;
173  draw_hline();
174  // print table content, only adding statistics when multithreaded
175  for (size_t row = 0; row < table.rows(); row++) {
176  out << border_l << std::setw(col_widths[0]) << std::left << table.get_row_name(row);
177  out << separator << std::setw(col_widths[1]) << std::right
178  << fmt::format(fmt::runtime(m_time_format), table(row, 0));
179  if (is_multithreaded) {
180  const int& num_threads = static_cast<int>(table(row, 4));
181  for (int col = 1; col < 4; col++) {
182  if (num_threads == 1) {
183  out << separator << std::setw(col_widths[col + 1]) << std::right << "--";
184  }
185  else {
186  out << separator << std::setw(col_widths[col + 1]) << std::right
187  << fmt::format(fmt::runtime(m_time_format), table(row, col));
188  }
189  }
190  out << separator << std::setw(thread_width) << std::right << num_threads;
191  }
192  out << border_r;
193  }
194  draw_hline();
195  }
196 
197 private:
204  inline static std::tuple<details::Table<double>, bool, size_t, double>
205  create_table(const std::list<TimerRegistration>& timer_register)
206  {
207  std::vector<std::string> rows; // list of all timer names
208  std::map<std::string, size_t> row_to_index; // map from name to index, used to fill table
209  bool is_multithreaded = false; // keep track of whether a thread id > 0 exists
210  // map rows from thread 0 first, so the order of timers (mostly) corresponds to their call order
211  for (const auto& [name, scope, timer, thread, rank] : timer_register) {
212  if (thread == 0 && rank == 0) {
213  const std::string qn = qualified_name(name, scope);
214  if (row_to_index.emplace(qn, rows.size()).second) {
215  rows.push_back(qn);
216  }
217  }
218  else {
219  is_multithreaded = true;
220  }
221  }
222  // make a second pass to add timers from other threads
223  // this does nothing, if all timers are used on thread 0 at least once
224  if (is_multithreaded) {
225  for (auto& [name, scope, timer, thread, rank] : timer_register) {
226  if (thread != 0 || rank != 0) {
227  const std::string qn = qualified_name(name, scope);
228  if (row_to_index.emplace(qn, rows.size()).second) {
229  rows.push_back(qn);
230  }
231  }
232  }
233  }
234  // create a table, omitting statistics if not multithreaded
235  details::Table<double> table =
236  is_multithreaded
237  ? details::Table<double>("Timers", {"Elapsed Time", "Min", "Max", "Average", "#Threads"}, rows)
238  : details::Table<double>("Timers", {"Elapsed Time"}, rows);
239  size_t name_width = 6; // width of the name column, defaults to length of "Timers".
240  // shorthands for the columns, so there are fewer mystery numbers
241  const int elapsed = 0, min = 1, max = 2, avg = 3, num = 4;
242  // initialize min values to
243  if (is_multithreaded) {
244  for (size_t row = 0; row < table.rows(); row++) {
245  table(row, min) = std::numeric_limits<double>::max();
246  }
247  }
248  // accumulate elapsed time and gather statistics in the table
249  // averages are calculated later, using finished values from elapsed and num
250  for (auto& [name, scope, timer, thread, rank] : timer_register) {
251  const auto row = row_to_index[qualified_name(name, scope)];
252  const auto time = time_in_seconds(timer.get_elapsed_time());
253  table(row, elapsed) += time;
254  if (is_multithreaded) {
255  table(row, min) = std::min(table(row, min), time);
256  table(row, max) = std::max(table(row, max), time);
257  table(row, num) += 1;
258  }
259  }
260  double max_val = 0.0; // maximum table value to calculate formatted time width.
261  for (size_t row = 0; row < table.rows(); row++) {
262  max_val = std::max(max_val, table(row, elapsed));
263  name_width = std::max(name_width, table.get_row_name(row).size());
264  if (is_multithreaded) {
265  table(row, avg) = table(row, elapsed) / table(row, num);
266  }
267  }
268  return {table, is_multithreaded, name_width, max_val};
269  }
270 
271  std::string m_time_format = "{:e}";
272 };
273 
274 } // namespace timing
275 } // namespace mio
276 
277 #endif // MIO_TIMER_TABLE_PRINTER_H
Definition: table_printer.h:120
std::string m_time_format
Format string used to print elapsed time and other timing statistics.
Definition: table_printer.h:271
void print(const std::list< TimerRegistration > &timer_register, std::ostream &out=std::cout) override
Print a table with timing results (aggregated over threads).
Definition: table_printer.h:138
static std::tuple< details::Table< double >, bool, size_t, double > create_table(const std::list< TimerRegistration > &timer_register)
The first part of the print function, separated as a somewhat independent step.
Definition: table_printer.h:205
void set_time_format(std::string format_string)
Change the format_string used for printed time values.
Definition: table_printer.h:126
Table used by TablePrinter with named rows and columns. With some extra work, this could be reused el...
Definition: table_printer.h:47
void add_row(const std::string &name, const std::vector< T > &values)
Definition: table_printer.h:66
std::vector< Row > m_rows
Definition: table_printer.h:114
std::vector< std::string > m_column_names
Definition: table_printer.h:113
std::string & get_name()
Definition: table_printer.h:96
std::string & get_row_name(size_t row)
Definition: table_printer.h:84
T & operator()(size_t row, size_t col)
Definition: table_printer.h:77
std::string & get_col_name(size_t col)
Definition: table_printer.h:90
Table(const std::string &name, const std::vector< std::string > &column_names, const std::vector< std::string > &row_names={})
Definition: table_printer.h:54
std::string m_name
Definition: table_printer.h:112
void add_row(const std::string &name)
Definition: table_printer.h:72
size_t rows() const
Definition: table_printer.h:101
size_t cols() const
Definition: table_printer.h:106
static min_max_return_type< ad::internal::active_type< AD_TAPE_REAL, DATA_HANDLER_1 >, ad::internal::active_type< AD_TAPE_REAL, DATA_HANDLER_1 > >::type min(const ad::internal::active_type< AD_TAPE_REAL, DATA_HANDLER_1 > &a, const ad::internal::active_type< AD_TAPE_REAL, DATA_HANDLER_1 > &b)
Definition: ad.hpp:2599
static min_max_return_type< ad::internal::active_type< AD_TAPE_REAL, DATA_HANDLER_1 >, ad::internal::active_type< AD_TAPE_REAL, DATA_HANDLER_1 > >::type max(const ad::internal::active_type< AD_TAPE_REAL, DATA_HANDLER_1 > &a, const ad::internal::active_type< AD_TAPE_REAL, DATA_HANDLER_1 > &b)
Definition: ad.hpp:2596
int rank(Comm comm)
Return the rank of the calling process on the given communicator.
Definition: miompi.cpp:63
std::string qualified_name(const std::string &name, const std::string &scope)
If scope is empty, returns name.
Definition: registration.h:48
double time_in_seconds(DurationType duration)
Convert a duration to a (floating point) number of seconds.
Definition: definitions.cpp:41
A collection of classes to simplify handling of matrix shapes in meta programming.
Definition: models/abm/analyze_result.h:30
auto max(const Eigen::MatrixBase< A > &a, B &&b)
coefficient wise maximum of two matrices.
Definition: eigen_util.h:171
Struct with a virtual print method to allow exchanging how TimerRegistrations are evaluated.
Definition: registration.h:54
Definition: table_printer.h:48
std::vector< T > values
Definition: table_printer.h:50
std::string name
Definition: table_printer.h:49