SIRIUS 7.5.0
Electronic structure library and applications
rt_graph.cpp
1/*
2 * Copyright (c) 2019 Simon Frasch
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are met:
6 *
7 * 1. Redistributions of source code must retain the above copyright notice,
8 * this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * 3. Neither the name of the copyright holder nor the names of its contributors
13 * may be used to endorse or promote products derived from this software
14 * without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#include "rt_graph.hpp"
30#include <algorithm>
31#include <cmath>
32#include <iomanip>
33#include <list>
34#include <numeric>
35#include <ostream>
36#include <ratio>
37#include <sstream>
38#include <string>
39#include <tuple>
40
41namespace rt_graph {
42
43// ======================
44// internal helper
45// ======================
46namespace internal {
47namespace {
48
49struct StatFormat {
50 StatFormat(Stat stat_) : stat(stat_) {
51 switch (stat_) {
52 case Stat::Count:
53 header = "#";
54 space = 9;
55 break;
56 case Stat::Total:
57 header = "Total";
58 space = 14;
59 break;
60 case Stat::Self:
61 header = "Self";
62 space = 14;
63 break;
64 case Stat::Mean:
65 header = "Mean";
66 space = 14;
67 break;
68 case Stat::Median:
69 header = "Median";
70 space = 14;
71 break;
72 case Stat::QuartileHigh:
73 header = "Quartile High";
74 space = 14;
75 break;
76 case Stat::QuartileLow:
77 header = "Quartile Low";
78 space = 14;
79 break;
80 case Stat::Min:
81 header = "Min";
82 space = 14;
83 break;
84 case Stat::Max:
85 header = "Max";
86 space = 14;
87 break;
88 case Stat::Percentage:
89 header = "%";
90 space = 11;
91 break;
92 case Stat::ParentPercentage:
93 header = "Parent %";
94 space = 11;
95 break;
96 case Stat::SelfPercentage:
97 header = "Self %";
98 space = 11;
99 break;
100 }
101 }
102
103 Stat stat;
104 std::string header;
105 std::size_t space;
106};
107
108auto unit_prefix(const double value) -> std::pair<double, char> {
109 if (value == 0.0 || value == -0.0) {
110 return {value, '\0'};
111 }
112
113 const double exponent = std::log10(std::abs(value));
114 const int siExponent = static_cast<int>(std::floor(exponent / 3.0)) * 3;
115
116 char prefix = '\0';
117 switch (siExponent) {
118 case 24:
119 prefix = 'Y';
120 break;
121 case 21:
122 prefix = 'Z';
123 break;
124 case 18:
125 prefix = 'E';
126 break;
127 case 15:
128 prefix = 'P';
129 break;
130 case 12:
131 prefix = 'T';
132 break;
133 case 9:
134 prefix = 'G';
135 break;
136 case 6:
137 prefix = 'M';
138 break;
139 case 3:
140 prefix = 'k';
141 break;
142 case 0:
143 break;
144 case -3:
145 prefix = 'm';
146 break;
147 case -6:
148 prefix = 'u';
149 break;
150 case -9:
151 prefix = 'n';
152 break;
153 case -12:
154 prefix = 'p';
155 break;
156 case -15:
157 prefix = 'f';
158 break;
159 case -18:
160 prefix = 'a';
161 break;
162 case -21:
163 prefix = 'z';
164 break;
165 case -24:
166 prefix = 'y';
167 break;
168 default:
169 prefix = '?';
170 }
171
172 return {value * std::pow(10.0, static_cast<double>(-siExponent)), prefix};
173}
174
175// format time input in seconds into string with appropriate unit
176auto format_time(const double time_seconds) -> std::string {
177 if (time_seconds <= 0.0) return std::string("0.00 s ");
178
179 double value;
180 char prefix;
181 std::tie(value, prefix) = unit_prefix(time_seconds);
182
183 std::stringstream result;
184 result << std::fixed << std::setprecision(2);
185 result << value;
186 result << " ";
187 if (prefix) result << prefix;
188 result << "s";
189 if (!prefix) result << " ";
190 return result.str();
191}
192
193auto calc_median(const std::vector<double>::const_iterator& begin,
194 const std::vector<double>::const_iterator& end) -> double {
195 const auto n = end - begin;
196 if (n == 0) return 0.0;
197 if (n % 2 == 0) {
198 return (*(begin + n / 2) + *(begin + n / 2 - 1)) / 2.0;
199 } else {
200 return *(begin + n / 2);
201 }
202}
203
204auto print_stat(std::ostream& out, const StatFormat& format,
205 const std::vector<double>& sortedTimings, double totalSum, double parentSum,
206 double currentSum, double subSum) -> void {
207 switch (format.stat) {
208 case Stat::Count:
209 if (sortedTimings.size() >= 100000) {
210 double value;
211 char prefix;
212 std::tie(value, prefix) = unit_prefix(sortedTimings.size());
213 out << std::right << std::setw(format.space)
214 << std::to_string(static_cast<int>(value)) + prefix;
215 } else {
216 out << std::right << std::setw(format.space) << sortedTimings.size();
217 }
218 break;
219 case Stat::Total:
220 out << std::right << std::setw(format.space) << format_time(currentSum);
221 break;
222 case Stat::Self:
223 out << std::right << std::setw(format.space)
224 << format_time(std::max<double>(currentSum - subSum, 0.0));
225 break;
226 case Stat::Mean:
227 out << std::right << std::setw(format.space)
228 << format_time(currentSum / sortedTimings.size());
229 break;
230 case Stat::Median:
231 out << std::right << std::setw(format.space)
232 << format_time(calc_median(sortedTimings.begin(), sortedTimings.end()));
233 break;
234 case Stat::QuartileHigh: {
235 const double upperQuartile =
236 calc_median(sortedTimings.begin() + sortedTimings.size() / 2 +
237 (sortedTimings.size() % 2) * (sortedTimings.size() > 1),
238 sortedTimings.end());
239 out << std::right << std::setw(format.space) << format_time(upperQuartile);
240 } break;
241 case Stat::QuartileLow: {
242 const double lowerQuartile =
243 calc_median(sortedTimings.begin(), sortedTimings.begin() + sortedTimings.size() / 2);
244 out << std::right << std::setw(format.space) << format_time(lowerQuartile);
245 } break;
246 case Stat::Min:
247 out << std::right << std::setw(format.space) << format_time(sortedTimings.front());
248 break;
249 case Stat::Max:
250 out << std::right << std::setw(format.space) << format_time(sortedTimings.back());
251 break;
252 case Stat::Percentage: {
253 const double p =
254 (totalSum < currentSum || totalSum == 0) ? 100.0 : currentSum / totalSum * 100.0;
255 out << std::right << std::fixed << std::setprecision(2) << std::setw(format.space) << p;
256 } break;
257 case Stat::ParentPercentage: {
258 const double p =
259 (parentSum < currentSum || parentSum == 0) ? 100.0 : currentSum / parentSum * 100.0;
260 out << std::right << std::fixed << std::setprecision(2) << std::setw(format.space) << p;
261 } break;
262 case Stat::SelfPercentage: {
263 const double p = (currentSum == 0)
264 ? 100.0
265 : std::max<double>(currentSum - subSum, 0.0) / currentSum * 100.0;
266 out << std::right << std::fixed << std::setprecision(2) << std::setw(format.space) << p;
267 } break;
268 }
269}
270
271// Helper struct for creating a tree of timings
272struct TimeStampPair {
273 std::string identifier;
274 double time = 0.0;
275 double startTime = 0.0;
276 std::size_t startIdx = 0;
277 std::size_t stopIdx = 0;
278 internal::TimingNode* nodePtr = nullptr;
279};
280
281// print nodes recursively
282auto print_node(const std::size_t level, std::ostream& out,
283 const std::vector<internal::StatFormat> formats, std::size_t const identifierSpace,
284 std::string nodePrefix, const internal::TimingNode& node, bool isLastSubnode,
285 double parentTime, double totalTime) -> void {
286 // print label
287 out << std::left;
288 std::string identifier = nodePrefix;
289 if (level > 0) {
290 // std::setw counts the special characters as 1 instead of 3 -> add missing 2
291 out << std::setw(identifierSpace + 2);
292 if (isLastSubnode)
293 identifier += "\u2514 "; // "up and right"
294 else
295 identifier += "\u251c "; // "vertical and right"
296 } else {
297 out << std::setw(identifierSpace);
298 }
299 identifier += node.identifier;
300 out << identifier;
301
302 auto sortedTimings = node.timings;
303 std::sort(sortedTimings.begin(), sortedTimings.end());
304
305 double subTime = 0.0;
306 for (const auto& subNode : node.subNodes) {
307 subTime += subNode.totalTime;
308 }
309 for (const auto& format : formats) {
310 print_stat(out, format, sortedTimings, totalTime, parentTime, node.totalTime, subTime);
311 }
312 out << std::endl;
313 for (const auto& subNode : node.subNodes) {
314 std::string newNodePrefix = nodePrefix;
315 std::size_t newIdentifierSpace = identifierSpace;
316 if (level > 0) {
317 if (isLastSubnode) {
318 newNodePrefix += " ";
319 } else {
320 newNodePrefix += "\u2502 "; // "vertical"
321 // std::setw counts the special characters as 1 instead of 3 -> add missing 2
322 newIdentifierSpace += 2;
323 }
324 }
325 print_node(level + 1, out, formats, newIdentifierSpace, newNodePrefix, subNode,
326 &subNode == &node.subNodes.back(), node.totalTime, totalTime);
327 }
328}
329
330// determine length of padding required for printing entire tree identifiers recursively
331auto max_node_identifier_length(const internal::TimingNode& node, const std::size_t recursionDepth,
332 const std::size_t addPerLevel, const std::size_t parentMax)
333 -> std::size_t {
334 std::size_t currentLength = node.identifier.length() + recursionDepth * addPerLevel;
335 std::size_t max = currentLength > parentMax ? currentLength : parentMax;
336 for (const auto& subNode : node.subNodes) {
337 const std::size_t subMax =
338 max_node_identifier_length(subNode, recursionDepth + 1, addPerLevel, max);
339 if (subMax > max) max = subMax;
340 }
341
342 return max;
343}
344
345auto export_node_json(const std::string& padding, const std::list<internal::TimingNode>& nodeList,
346 std::ostream& stream) -> void {
347 stream << "{" << std::endl;
348 const std::string nodePadding = padding + " ";
349 const std::string subNodePadding = nodePadding + " ";
350 for (const auto& node : nodeList) {
351 stream << nodePadding << "\"" << node.identifier << "\" : {" << std::endl;
352 stream << subNodePadding << "\"timings\" : [";
353 for (const auto& value : node.timings) {
354 stream << value;
355 if (&value != &(node.timings.back())) stream << ", ";
356 }
357 stream << "]," << std::endl;
358 stream << subNodePadding << "\"start-times\" : [";
359 for (const auto& value : node.startTimes) {
360 stream << value;
361 if (&value != &(node.startTimes.back())) stream << ", ";
362 }
363 stream << "]," << std::endl;
364 stream << subNodePadding << "\"sub-timings\" : ";
365 export_node_json(subNodePadding, node.subNodes, stream);
366 stream << nodePadding << "}";
367 if (&node != &(nodeList.back())) stream << ",";
368 stream << std::endl;
369 }
370 stream << padding << "}" << std::endl;
371}
372
373auto extract_timings(const std::string& identifier, const std::list<TimingNode>& nodes,
374 std::vector<double>& timings) -> void {
375 for (const auto& node : nodes) {
376 if (node.identifier == identifier) {
377 timings.insert(timings.end(), node.timings.begin(), node.timings.end());
378 }
379 extract_timings(identifier, node.subNodes, timings);
380 }
381}
382
383auto sort_timings_nodes(std::list<TimingNode>& nodes) -> void {
384 // sort large to small
385 nodes.sort([](const TimingNode& n1, const TimingNode& n2) -> bool {
386 return n1.totalTime > n2.totalTime;
387 });
388 for (auto& n : nodes) {
389 sort_timings_nodes(n.subNodes);
390 }
391}
392
393auto flatten_timings_nodes(std::list<TimingNode>& rootNodes, std::list<TimingNode>& nodes) -> void {
394 // call recursively first, since nodes will be invalidated next
395 for (auto& n : nodes) {
396 flatten_timings_nodes(rootNodes, n.subNodes);
397 }
398
399 for (auto& n : nodes) {
400 auto it = std::find_if(
401 rootNodes.begin(), rootNodes.end(),
402 [&n](const TimingNode& element) -> bool { return element.identifier == n.identifier; });
403 if (it == rootNodes.end()) {
404 // identifier not in rootNodes -> append full TimingNode
405 rootNodes.emplace_back(std::move(n));
406 } else {
407 // identifier already in rootNodes -> only append timings
408 it->timings.insert(it->timings.end(), n.timings.begin(), n.timings.end());
409 it->startTimes.insert(it->startTimes.end(), n.startTimes.begin(), n.startTimes.end());
410 it->totalTime += n.totalTime;
411 }
412 }
413
414 // drop all nodes
415 nodes.clear();
416}
417
418auto flatten_timings_nodes_from_level(std::list<TimingNode>& nodes, std::size_t targetLevel,
419 std::size_t currentLevel) -> void {
420 if (targetLevel > currentLevel) {
421 for (auto& n : nodes) {
422 flatten_timings_nodes_from_level(n.subNodes, targetLevel, currentLevel + 1);
423 }
424 } else {
425 for (auto& n : nodes) {
426 flatten_timings_nodes(nodes, n.subNodes);
427 }
428 }
429}
430
431} // namespace
432} // namespace internal
433
434// ======================
435// Timer
436// ======================
437auto Timer::process() const -> TimingResult {
438 std::list<internal::TimingNode> results;
439 std::stringstream warnings;
440
441 try {
442 std::vector<internal::TimeStampPair> timePairs;
443 timePairs.reserve(timeStamps_.size() / 2);
444
445 // create pairs of start / stop timings
446 for (std::size_t i = 0; i < timeStamps_.size(); ++i) {
447 if (timeStamps_[i].type == internal::TimeStampType::Start) {
448 internal::TimeStampPair pair;
449 pair.startIdx = i;
450 pair.identifier = std::string(timeStamps_[i].identifierPtr);
451 std::size_t numInnerMatchingIdentifiers = 0;
452 // search for matching stop after start
453 for (std::size_t j = i + 1; j < timeStamps_.size(); ++j) {
454 // only consider matching identifiers
455 if (std::string(timeStamps_[j].identifierPtr) ==
456 std::string(timeStamps_[i].identifierPtr)) {
457 if (timeStamps_[j].type == internal::TimeStampType::Stop &&
458 numInnerMatchingIdentifiers == 0) {
459 // Matching stop found
460 std::chrono::duration<double> duration = timeStamps_[j].time - timeStamps_[i].time;
461 pair.time = duration.count();
462 duration = timeStamps_[i].time - timeStamps_[0].time;
463 pair.startTime = duration.count();
464 pair.stopIdx = j;
465 timePairs.push_back(pair);
466 if (pair.time < 0 || pair.startTime < 0) {
467 warnings << "rt_graph WARNING:Measured time is negative. Non-steady system-clock?!"
468 << std::endl;
469 }
470 break;
471 } else if (timeStamps_[j].type == internal::TimeStampType::Stop &&
472 numInnerMatchingIdentifiers > 0) {
473 // inner stop with matching identifier
474 --numInnerMatchingIdentifiers;
475 } else if (timeStamps_[j].type == internal::TimeStampType::Start) {
476 // inner start with matching identifier
477 ++numInnerMatchingIdentifiers;
478 }
479 }
480 }
481 if (pair.stopIdx == 0) {
482 warnings << "rt_graph WARNING: Start / stop time stamps do not match for \""
483 << timeStamps_[i].identifierPtr << "\"!" << std::endl;
484 }
485 }
486 }
487
488 // create tree of timings where sub-nodes represent timings fully enclosed by another start /
489 // stop pair. Use the fact that timePairs is sorted by startIdx
490 for (std::size_t i = 0; i < timePairs.size(); ++i) {
491 auto& pair = timePairs[i];
492
493 // find potential parent by going backwards through pairs, starting with the current pair
494 // position
495 for (auto timePairIt = timePairs.rbegin() + (timePairs.size() - i);
496 timePairIt != timePairs.rend(); ++timePairIt) {
497 if (timePairIt->stopIdx > pair.stopIdx && timePairIt->nodePtr != nullptr) {
498 auto& parentNode = *(timePairIt->nodePtr);
499 // check if sub-node with identifier exists
500 bool nodeFound = false;
501 for (auto& subNode : parentNode.subNodes) {
502 if (subNode.identifier == pair.identifier) {
503 nodeFound = true;
504 subNode.add_time(pair.startTime, pair.time);
505 // mark node position in pair for finding sub-nodes
506 pair.nodePtr = &(subNode);
507 break;
508 }
509 }
510 if (!nodeFound) {
511 // create new sub-node
512 internal::TimingNode newNode;
513 newNode.identifier = pair.identifier;
514 newNode.add_time(pair.startTime, pair.time);
515 parentNode.subNodes.push_back(std::move(newNode));
516 // mark node position in pair for finding sub-nodes
517 pair.nodePtr = &(parentNode.subNodes.back());
518 }
519 break;
520 }
521 }
522
523 // No parent found, must be top level node
524 if (pair.nodePtr == nullptr) {
525 // Check if top level node with same name exists
526 for (auto& topNode : results) {
527 if (topNode.identifier == pair.identifier) {
528 topNode.add_time(pair.startTime, pair.time);
529 pair.nodePtr = &(topNode);
530 break;
531 }
532 }
533 }
534
535 // New top level node
536 if (pair.nodePtr == nullptr) {
537 internal::TimingNode newNode;
538 newNode.identifier = pair.identifier;
539 newNode.add_time(pair.startTime, pair.time);
540 // newNode.parent = nullptr;
541 results.push_back(std::move(newNode));
542
543 // mark node position in pair for finding sub-nodes
544 pair.nodePtr = &(results.back());
545 }
546 }
547 } catch (const std::exception& e) {
548 warnings << "rt_graph WARNING: Processing of timings failed: " << e.what() << std::endl;
549 } catch (...) {
550 warnings << "rt_graph WARNING: Processing of timings failed!" << std::endl;
551 }
552
553 return TimingResult(std::move(results), warnings.str());
554}
555
556auto TimingResult::json() const -> std::string {
557 std::stringstream jsonStream;
558 jsonStream << std::scientific;
559 internal::export_node_json("", rootNodes_, jsonStream);
560 return jsonStream.str();
561}
562
563auto TimingResult::get_timings(const std::string& identifier) const -> std::vector<double> {
564 std::vector<double> timings;
565 internal::extract_timings(identifier, rootNodes_, timings);
566 return timings;
567}
568
569auto TimingResult::print(std::vector<Stat> statistic) const -> std::string {
570 std::stringstream stream;
571
572 // print warnings
573 stream << warnings_;
574
575 // calculate space for printing identifiers
576 std::size_t identifierSpace = 0;
577 for (const auto& node : rootNodes_) {
578 const auto nodeMax = internal::max_node_identifier_length(node, 0, 2, identifierSpace);
579 if (nodeMax > identifierSpace) identifierSpace = nodeMax;
580 }
581
582 auto totalSpace = identifierSpace;
583
584 std::vector<internal::StatFormat> formats;
585 formats.reserve(statistic.size());
586 for (const auto& stat : statistic) {
587 formats.emplace_back(stat);
588 totalSpace += formats.back().space;
589 }
590
591 // Construct table header
592
593 // Table start
594 stream << std::string(totalSpace, '=') << std::endl;
595
596 // header
597 stream << std::right << std::setw(identifierSpace) << "";
598 for (const auto& format : formats) {
599 stream << std::right << std::setw(format.space) << format.header;
600 }
601 stream << std::endl;
602
603 // Header separtion line
604 stream << std::string(totalSpace, '-') << std::endl;
605
606 // print all timings
607 for (const auto& node : rootNodes_) {
608 internal::print_node(0, stream, formats, identifierSpace, "", node, true, node.totalTime,
609 node.totalTime);
610 stream << std::endl;
611 }
612
613 // End table
614 stream << std::string(totalSpace, '=') << std::endl;
615
616 return stream.str();
617}
618
619auto TimingResult::flatten(std::size_t level) -> TimingResult& {
620 internal::flatten_timings_nodes_from_level(rootNodes_, level, 0);
621 return *this;
622}
623
624auto TimingResult::sort_nodes() -> TimingResult& {
625 internal::sort_timings_nodes(rootNodes_);
626 return *this;
627}
628
629} // namespace rt_graph
@ value
the parser finished reading a JSON value
acc_stream_t stream(stream_id sid__)
Return a single device stream.
Definition: acc.hpp:202