1 | /*
|
2 | * Souffle - A Datalog Compiler
|
3 | * Copyright (c) 2016, The Souffle Developers. All rights reserved
|
4 | * Licensed under the Universal Permissive License v 1.0 as shown at:
|
5 | * - https://opensource.org/licenses/UPL
|
6 | * - <souffle root>/licenses/SOUFFLE-UPL.txt
|
7 | */
|
8 |
|
9 | #pragma once
|
10 |
|
11 | #include "souffle/profile/CellInterface.h"
|
12 | #include "souffle/profile/HtmlGenerator.h"
|
13 | #include "souffle/profile/Iteration.h"
|
14 | #include "souffle/profile/OutputProcessor.h"
|
15 | #include "souffle/profile/ProfileDatabase.h"
|
16 | #include "souffle/profile/ProfileEvent.h"
|
17 | #include "souffle/profile/ProgramRun.h"
|
18 | #include "souffle/profile/Reader.h"
|
19 | #include "souffle/profile/Relation.h"
|
20 | #include "souffle/profile/Row.h"
|
21 | #include "souffle/profile/Rule.h"
|
22 | #include "souffle/profile/StringUtils.h"
|
23 | #include "souffle/profile/Table.h"
|
24 | #include "souffle/profile/UserInputReader.h"
|
25 | #include "souffle/utility/MiscUtil.h"
|
26 | #include <algorithm>
|
27 | #include <chrono>
|
28 | #include <cstdint>
|
29 | #include <cstdio>
|
30 | #include <cstdlib>
|
31 | #include <filesystem>
|
32 | #include <iostream>
|
33 | #include <iterator>
|
34 | #include <map>
|
35 | #include <memory>
|
36 | #include <ratio>
|
37 | #include <set>
|
38 | #include <string>
|
39 | #include <thread>
|
40 | #include <unordered_map>
|
41 | #include <utility>
|
42 | #include <vector>
|
43 | #ifndef _MSC_VER
|
44 | #include <dirent.h>
|
45 | #include <sys/ioctl.h>
|
46 | #include <sys/stat.h>
|
47 | #endif
|
48 |
|
49 | namespace souffle {
|
50 | namespace profile {
|
51 |
|
52 | /*
|
53 | * Text User interface for SouffleProf
|
54 | * OutputProcessor creates a ProgramRun object
|
55 | * ProgramRun -> Reader.h ProgramRun stores all the data
|
56 | * OutputProcessor grabs the data and makes tables
|
57 | * Tui displays the data
|
58 | */
|
59 | class Tui {
|
60 | private:
|
61 | OutputProcessor out;
|
62 | bool loaded;
|
63 | std::string f_name;
|
64 | bool alive = false;
|
65 | std::thread updater;
|
66 | int sortColumn = 0;
|
67 | int precision = 3;
|
68 | Table relationTable;
|
69 | Table ruleTable;
|
70 | std::shared_ptr<Reader> reader;
|
71 | InputReader linereader;
|
72 | /// Limit results shown. Default value chosen to approximate unlimited
|
73 | std::size_t resultLimit = 20000;
|
74 |
|
75 | struct Usage {
|
76 | std::chrono::microseconds time;
|
77 | uint64_t maxRSS;
|
78 | std::chrono::microseconds systemtime;
|
79 | std::chrono::microseconds usertime;
|
80 | bool operator<(const Usage& other) const {
|
81 | return time < other.time;
|
82 | }
|
83 | };
|
84 |
|
85 | public:
|
86 | Tui(std::string filename, bool live, bool /* gui */) {
|
87 | // Set a friendlier output size if we're being interacted with directly.
|
88 | if (live) {
|
89 | resultLimit = 20;
|
90 | }
|
91 | this->f_name = filename;
|
92 |
|
93 | const std::shared_ptr<ProgramRun>& run = out.getProgramRun();
|
94 |
|
95 | this->reader = std::make_shared<Reader>(filename, run);
|
96 |
|
97 | this->alive = false;
|
98 | updateDB();
|
99 | this->loaded = reader->isLoaded();
|
100 | }
|
101 |
|
102 | Tui() {
|
103 | const std::shared_ptr<ProgramRun>& run = out.getProgramRun();
|
104 | this->reader = std::make_shared<Reader>(run);
|
105 | this->loaded = true;
|
106 | this->alive = true;
|
107 | updateDB();
|
108 | updater = std::thread([this]() {
|
109 | // Update the display every 30s. Check for input every 0.5s
|
110 | std::chrono::milliseconds interval(30000);
|
111 | auto nextUpdateTime = std::chrono::high_resolution_clock::now();
|
112 | do {
|
113 | std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
114 | if (nextUpdateTime < std::chrono::high_resolution_clock::now()) {
|
115 | runCommand({});
|
116 | nextUpdateTime = std::chrono::high_resolution_clock::now() + interval;
|
117 | }
|
118 | } while (reader->isLive() && !linereader.hasReceivedInput());
|
119 | });
|
120 | }
|
121 |
|
122 | ~Tui() {
|
123 | if (updater.joinable()) {
|
124 | updater.join();
|
125 | }
|
126 | }
|
127 |
|
128 | void runCommand(std::vector<std::string> c) {
|
129 | if (linereader.hasReceivedInput() && c.empty()) {
|
130 | return;
|
131 | }
|
132 | if (!loaded) {
|
133 | std::cout << "Error: File cannot be loaded\n";
|
134 | return;
|
135 | }
|
136 |
|
137 | if (alive) {
|
138 | updateDB();
|
139 | // remake tables to get new data
|
140 | ruleTable = out.getRulTable();
|
141 | relationTable = out.getRelTable();
|
142 |
|
143 | setupTabCompletion();
|
144 | }
|
145 |
|
146 | // If we have not received any input yet in live mode then run top.
|
147 | if ((!linereader.hasReceivedInput() && c.empty())) {
|
148 | // Move up n lines and overwrite the previous top output.
|
149 | std::cout << "\x1b[3D";
|
150 | std::cout << "\x1b[27A";
|
151 | top();
|
152 | std::cout << "\x1b[B> ";
|
153 | } else if (c[0] == "top") {
|
154 | top();
|
155 | } else if (c[0] == "rel") {
|
156 | if (c.size() == 2) {
|
157 | relRul(c[1]);
|
158 | } else if (c.size() == 1) {
|
159 | rel(resultLimit);
|
160 | } else {
|
161 | std::cout << "Invalid parameters to rel command.\n";
|
162 | }
|
163 | } else if (c[0] == "rul") {
|
164 | if (c.size() > 1) {
|
165 | if (c.size() == 3 && c[1] == "id") {
|
166 | std::printf("%7s%2s%s\n\n", "ID", "", "NAME");
|
167 | id(c[2]);
|
168 | } else if (c.size() == 2 && c[1] == "id") {
|
169 | id("0");
|
170 | } else if (c.size() == 2) {
|
171 | verRul(c[1]);
|
172 | } else {
|
173 | std::cout << "Invalid parameters to rul command.\n";
|
174 | }
|
175 | } else {
|
176 | rul(resultLimit);
|
177 | }
|
178 | } else if (c[0] == "graph") {
|
179 | if (c.size() == 3 && c[1].find(".") == std::string::npos) {
|
180 | iterRel(c[1], c[2]);
|
181 | } else if (c.size() == 3 && c[1].at(0) == 'C') {
|
182 | iterRul(c[1], c[2]);
|
183 | } else if (c.size() == 4 && c[1] == "ver" && c[2].at(0) == 'C') {
|
184 | verGraph(c[2], c[3]);
|
185 | } else {
|
186 | std::cout << "Invalid parameters to graph command.\n";
|
187 | }
|
188 | } else if (c[0] == "memory") {
|
189 | memoryUsage();
|
190 | } else if (c[0] == "usage") {
|
191 | if (c.size() > 1) {
|
192 | if (c[1][0] == 'R') {
|
193 | usageRelation(c[1]);
|
194 | } else {
|
195 | usageRule(c[1]);
|
196 | }
|
197 | } else {
|
198 | usage();
|
199 | }
|
200 | } else if (c[0] == "help") {
|
201 | help();
|
202 | } else if (c[0] == "limit") {
|
203 | if (c.size() == 1) {
|
204 | setResultLimit(20000);
|
205 | } else {
|
206 | try {
|
207 | setResultLimit(std::stoul(c[1]));
|
208 | } catch (...) {
|
209 | std::cout << "Invalid parameters to limit command.\n";
|
210 | }
|
211 | }
|
212 | } else if (c[0] == "configuration") {
|
213 | configuration();
|
214 | } else {
|
215 | std::cout << "Unknown command. Use \"help\" for a list of commands.\n";
|
216 | }
|
217 | }
|
218 |
|
219 | /// Return an exit status equal to 0 on success.
|
220 | int runProf() {
|
221 | if (!loaded && !f_name.empty()) {
|
222 | std::cout << "Error: File cannot be loaded\n";
|
223 | return 1;
|
224 | }
|
225 | if (loaded) {
|
226 | std::cout << "SouffleProf\n";
|
227 | top();
|
228 | }
|
229 |
|
230 | linereader.setPrompt("\n> ");
|
231 | setupTabCompletion();
|
232 |
|
233 | while (true) {
|
234 | std::string untrimmedInput = linereader.getInput();
|
235 | std::string input = Tools::trimWhitespace(untrimmedInput);
|
236 |
|
237 | std::cout << std::endl;
|
238 | if (input.empty()) {
|
239 | std::cout << "Unknown command. Type help for a list of commands.\n";
|
240 | continue;
|
241 | }
|
242 |
|
243 | linereader.addHistory(input);
|
244 |
|
245 | std::vector<std::string> c = Tools::split(input, " ");
|
246 |
|
247 | if (c[0] == "q" || c[0] == "quit") {
|
248 | quit();
|
249 | break;
|
250 | } else if (c[0] == "sort") {
|
251 | if (c.size() == 2 && std::stoi(c[1]) < 7) {
|
252 | sortColumn = std::stoi(c[1]);
|
253 | } else {
|
254 | std::cout << "Invalid column, please select a number between 0 and 6.\n";
|
255 | }
|
256 | } else {
|
257 | runCommand(c);
|
258 | }
|
259 | }
|
260 |
|
261 | return 0;
|
262 | }
|
263 |
|
264 | std::stringstream& genJsonTop(std::stringstream& ss) {
|
265 | const std::shared_ptr<ProgramRun>& run = out.getProgramRun();
|
266 |
|
267 | auto beginTime = run->getStarttime();
|
268 | auto endTime = run->getEndtime();
|
269 | ss << R"_({"top":[)_" << (endTime - beginTime).count() / 1000000.0 << "," << run->getTotalSize()
|
270 | << "," << run->getTotalLoadtime().count() / 1000000.0 << ","
|
271 | << run->getTotalSavetime().count() / 1000000.0 << "]";
|
272 | return ss;
|
273 | }
|
274 |
|
275 | std::stringstream& genJsonRelations(std::stringstream& ss, const std::string& name, std::size_t maxRows) {
|
276 | const std::shared_ptr<ProgramRun>& run = out.getProgramRun();
|
277 |
|
278 | auto comma = [&ss](bool& first, const std::string& delimiter = ", ") {
|
279 | if (!first) {
|
280 | ss << delimiter;
|
281 | } else {
|
282 | first = false;
|
283 | }
|
284 | };
|
285 |
|
286 | ss << '"' << name << R"_(":{)_";
|
287 | bool firstRow = true;
|
288 | auto rows = relationTable.getRows();
|
289 | std::stable_sort(rows.begin(), rows.end(), [](std::shared_ptr<Row> left, std::shared_ptr<Row> right) {
|
290 | return (*left)[0]->getDoubleVal() > (*right)[0]->getDoubleVal();
|
291 | });
|
292 | maxRows = std::min(rows.size(), maxRows);
|
293 |
|
294 | for (std::size_t i = 0; i < maxRows; ++i) {
|
295 | comma(firstRow, ",\n");
|
296 |
|
297 | Row& row = *rows[i];
|
298 | ss << '"' << row[6]->toString(0) << R"_(": [)_";
|
299 | ss << '"' << Tools::cleanJsonOut(row[5]->toString(0)) << R"_(", )_";
|
300 | ss << '"' << Tools::cleanJsonOut(row[6]->toString(0)) << R"_(", )_";
|
301 | ss << row[0]->getDoubleVal() << ", ";
|
302 | ss << row[1]->getDoubleVal() << ", ";
|
303 | ss << row[2]->getDoubleVal() << ", ";
|
304 | ss << row[3]->getDoubleVal() << ", ";
|
305 | ss << row[4]->getLongVal() << ", ";
|
306 | ss << row[12]->getLongVal() << ", ";
|
307 | ss << '"' << Tools::cleanJsonOut(row[7]->toString(0)) << R"_(", [)_";
|
308 |
|
309 | bool firstCol = true;
|
310 | for (auto& _rel_row : ruleTable.getRows()) {
|
311 | Row rel_row = *_rel_row;
|
312 | if (rel_row[7]->toString(0) == row[5]->toString(0)) {
|
313 | comma(firstCol);
|
314 | ss << '"' << rel_row[6]->toString(0) << '"';
|
315 | }
|
316 | }
|
317 | ss << "], ";
|
318 | std::vector<std::shared_ptr<Iteration>> iter =
|
319 | run->getRelation(row[5]->toString(0))->getIterations();
|
320 | ss << R"_({"tot_t": [)_";
|
321 | firstCol = true;
|
322 | for (auto& i : iter) {
|
323 | comma(firstCol);
|
324 | ss << i->getRuntime().count();
|
325 | }
|
326 | ss << R"_(], "copy_t": [)_";
|
327 | firstCol = true;
|
328 | for (auto& i : iter) {
|
329 | comma(firstCol);
|
330 | ss << i->getCopytime().count();
|
331 | }
|
332 | ss << R"_(], "tuples": [)_";
|
333 | firstCol = true;
|
334 | for (auto& i : iter) {
|
335 | comma(firstCol);
|
336 | ss << i->size();
|
337 | }
|
338 | ss << "]}]";
|
339 | }
|
340 | ss << "}";
|
341 |
|
342 | return ss;
|
343 | }
|
344 |
|
345 | std::stringstream& genJsonRules(std::stringstream& ss, const std::string& name, std::size_t maxRows) {
|
346 | const std::shared_ptr<ProgramRun>& run = out.getProgramRun();
|
347 |
|
348 | auto comma = [&ss](bool& first, const std::string& delimiter = ", ") {
|
349 | if (!first) {
|
350 | ss << delimiter;
|
351 | } else {
|
352 | first = false;
|
353 | }
|
354 | };
|
355 |
|
356 | ss << '"' << name << R"_(":{)_";
|
357 |
|
358 | bool firstRow = true;
|
359 | auto rows = ruleTable.getRows();
|
360 | std::stable_sort(rows.begin(), rows.end(), [](std::shared_ptr<Row> left, std::shared_ptr<Row> right) {
|
361 | return (*left)[0]->getDoubleVal() > (*right)[0]->getDoubleVal();
|
362 | });
|
363 | maxRows = std::min(rows.size(), maxRows);
|
364 |
|
365 | for (std::size_t i = 0; i < maxRows; ++i) {
|
366 | Row& row = *rows[i];
|
367 |
|
368 | std::vector<std::string> part = Tools::split(row[6]->toString(0), ".");
|
369 | std::string strRel = "R" + part[0].substr(1);
|
370 | Table versionTable = out.getVersions(strRel, row[6]->toString(0));
|
371 |
|
372 | std::string src;
|
373 | if (versionTable.rows.size() > 0) {
|
374 | if (versionTable.rows[0]->cells[9] != nullptr) {
|
375 | src = (*versionTable.rows[0])[9]->toString(0);
|
376 | } else {
|
377 | src = "-";
|
378 | }
|
379 | } else {
|
380 | src = row[10]->toString(-1);
|
381 | }
|
382 | comma(firstRow);
|
383 | ss << "\n ";
|
384 |
|
385 | ss << '"' << row[6]->toString(0) << R"_(": [)_";
|
386 | ss << '"' << Tools::cleanJsonOut(row[5]->toString(0)) << R"_(", )_";
|
387 | ss << '"' << Tools::cleanJsonOut(row[6]->toString(0)) << R"_(", )_";
|
388 | ss << row[0]->getDoubleVal() << ", ";
|
389 | ss << row[1]->getDoubleVal() << ", ";
|
390 | ss << row[2]->getDoubleVal() << ", ";
|
391 | ss << row[4]->getLongVal() << ", ";
|
392 |
|
393 | ss << '"' << src << R"_(", )_";
|
394 | ss << "[";
|
395 |
|
396 | bool has_ver = false;
|
397 | bool firstCol = true;
|
398 | for (auto& _ver_row : versionTable.getRows()) {
|
399 | comma(firstCol);
|
400 | has_ver = true;
|
401 | Row ver_row = *_ver_row;
|
402 | ss << '[';
|
403 | ss << '"' << Tools::cleanJsonOut(ver_row[5]->toString(0)) << R"_(", )_";
|
404 | ss << '"' << Tools::cleanJsonOut(ver_row[6]->toString(0)) << R"_(", )_";
|
405 | ss << ver_row[0]->getDoubleVal() << ", ";
|
406 | ss << ver_row[1]->getDoubleVal() << ", ";
|
407 | ss << ver_row[2]->getDoubleVal() << ", ";
|
408 | ss << ver_row[4]->getLongVal() << ", ";
|
409 | ss << '"' << src << R"_(", )_";
|
410 | ss << ver_row[8]->getLongVal();
|
411 | ss << ']';
|
412 | }
|
413 |
|
414 | ss << "], ";
|
415 |
|
416 | if (row[6]->toString(0).at(0) != 'C') {
|
417 | ss << "{}, {}]";
|
418 | } else {
|
419 | ss << R"_({"tot_t": [)_";
|
420 |
|
421 | std::vector<uint64_t> iteration_tuples;
|
422 | bool firstCol = true;
|
423 | for (auto& i : run->getRelation(row[7]->toString(0))->getIterations()) {
|
424 | bool add = false;
|
425 | std::chrono::microseconds totalTime{};
|
426 | uint64_t totalSize = 0L;
|
427 | for (auto& rul : i->getRules()) {
|
428 | if (rul.second->getId() == row[6]->toString(0)) {
|
429 | totalTime += rul.second->getRuntime();
|
430 |
|
431 | totalSize += rul.second->size();
|
432 | add = true;
|
433 | }
|
434 | }
|
435 | if (add) {
|
436 | comma(firstCol);
|
437 | ss << totalTime.count();
|
438 | iteration_tuples.push_back(totalSize);
|
439 | }
|
440 | }
|
441 | ss << R"_(], "tuples": [)_";
|
442 | firstCol = true;
|
443 | for (auto& i : iteration_tuples) {
|
444 | comma(firstCol);
|
445 | ss << i;
|
446 | }
|
447 |
|
448 | ss << "]}, {";
|
449 |
|
450 | if (has_ver) {
|
451 | ss << R"_("tot_t": [)_";
|
452 |
|
453 | firstCol = true;
|
454 | for (auto& row : versionTable.rows) {
|
455 | comma(firstCol);
|
456 | ss << (*row)[0]->getDoubleVal();
|
457 | }
|
458 | ss << R"_(], "tuples": [)_";
|
459 |
|
460 | firstCol = true;
|
461 | for (auto& row : versionTable.rows) {
|
462 | comma(firstCol);
|
463 | ss << (*row)[4]->getLongVal();
|
464 | }
|
465 | ss << ']';
|
466 | }
|
467 | ss << "}]";
|
468 | }
|
469 | }
|
470 | ss << "\n}";
|
471 | return ss;
|
472 | }
|
473 |
|
474 | std::stringstream& genJsonUsage(std::stringstream& ss) {
|
475 | const std::shared_ptr<ProgramRun>& run = out.getProgramRun();
|
476 |
|
477 | auto comma = [&ss](bool& first, const std::string& delimiter = ", ") {
|
478 | if (!first) {
|
479 | ss << delimiter;
|
480 | } else {
|
481 | first = false;
|
482 | }
|
483 | };
|
484 |
|
485 | std::string source_loc = (*relationTable.getRows()[0])[7]->getStringVal();
|
486 | std::string source_file_loc = Tools::split(source_loc, " ").at(0);
|
487 | std::ifstream source_file(source_file_loc);
|
488 | if (!source_file.is_open()) {
|
489 | std::cout << "Error opening \"" << source_file_loc << "\", creating GUI without source locator."
|
490 | << std::endl;
|
491 | } else {
|
492 | std::string str;
|
493 | ss << R"_("code": [)_";
|
494 | bool firstCol = true;
|
495 | while (getline(source_file, str)) {
|
496 | comma(firstCol, ",\n");
|
497 | ss << '"' << Tools::cleanJsonOut(str) << '"';
|
498 | }
|
499 | ss << "],\n";
|
500 | source_file.close();
|
501 | }
|
502 |
|
503 | // Add usage statistics
|
504 | auto usages = getUsageStats(100);
|
505 | auto beginTime = run->getStarttime();
|
506 |
|
507 | ss << R"_("usage": [)_";
|
508 | bool firstRow = true;
|
509 | Usage previousUsage = *usages.begin();
|
510 | previousUsage.time = beginTime;
|
511 | for (auto usage : usages) {
|
512 | comma(firstRow);
|
513 | ss << '[';
|
514 | ss << (usage.time - beginTime).count() / 1000000.0 << ", ";
|
515 | ss << 100.0 * (usage.usertime - previousUsage.usertime) / (usage.time - previousUsage.time)
|
516 | << ", ";
|
517 | ss << 100.0 * (usage.systemtime - previousUsage.systemtime) / (usage.time - previousUsage.time)
|
518 | << ", ";
|
519 | ss << usage.maxRSS * 1024 << ", ";
|
520 | ss << '"';
|
521 | bool firstCol = true;
|
522 | for (auto& cur : out.getProgramRun()->getRelationsAtTime(previousUsage.time, usage.time)) {
|
523 | comma(firstCol);
|
524 | ss << cur->getName();
|
525 | }
|
526 | ss << '"';
|
527 | ss << ']';
|
528 | previousUsage = usage;
|
529 | }
|
530 | ss << ']';
|
531 | return ss;
|
532 | }
|
533 |
|
534 | std::stringstream& genJsonConfiguration(std::stringstream& ss) {
|
535 | auto comma = [&ss](bool& first, const std::string& delimiter = ", ") {
|
536 | if (!first) {
|
537 | ss << delimiter;
|
538 | } else {
|
539 | first = false;
|
540 | }
|
541 | };
|
542 |
|
543 | // Add configuration key-value pairs
|
544 | ss << R"_("configuration": {)_";
|
545 | bool firstRow = true;
|
546 | for (auto& kvp :
|
547 | ProfileEventSingleton::instance().getDB().getStringMap({"program", "configuration"})) {
|
548 | comma(firstRow);
|
549 | ss << '"' << kvp.first << R"_(": ")_" << Tools::cleanJsonOut(kvp.second) << '"';
|
550 | }
|
551 | ss << '}';
|
552 | return ss;
|
553 | }
|
554 |
|
555 | std::stringstream& genJsonAtoms(std::stringstream& ss) {
|
556 | const std::shared_ptr<ProgramRun>& run = out.getProgramRun();
|
557 |
|
558 | auto comma = [&ss](bool& first, const std::string& delimiter = ", ") {
|
559 | if (!first) {
|
560 | ss << delimiter;
|
561 | } else {
|
562 | first = false;
|
563 | }
|
564 | };
|
565 |
|
566 | ss << R"_("atoms": {)_";
|
567 |
|
568 | bool firstRow = true;
|
569 | for (auto& relation : run->getRelationMap()) {
|
570 | // Get atoms for non-recursive rules
|
571 | for (auto& rule : relation.second->getRuleMap()) {
|
572 | comma(firstRow, ", \n");
|
573 | ss << '"' << rule.second->getId() << R"_(": [)_";
|
574 | bool firstCol = true;
|
575 | for (auto& atom : rule.second->getAtoms()) {
|
576 | comma(firstCol);
|
577 | std::string relationName = atom.identifier;
|
578 | relationName = relationName.substr(0, relationName.find('('));
|
579 | auto* relation = out.getProgramRun()->getRelation(relationName);
|
580 | std::string relationSize = relation == nullptr ? "" : std::to_string(relation->size());
|
581 | ss << '[';
|
582 | ss << '"' << Tools::cleanJsonOut(Tools::cleanString(atom.rule)) << R"_(", )_";
|
583 | ss << '"' << Tools::cleanJsonOut(atom.identifier) << R"_(", )_";
|
584 | ss << relationSize << ", ";
|
585 | ss << atom.frequency << ']';
|
586 | }
|
587 | ss << "]";
|
588 | }
|
589 | // Get atoms for recursive rules
|
590 | for (auto& iteration : relation.second->getIterations()) {
|
591 | for (auto& rule : iteration->getRules()) {
|
592 | comma(firstRow, ", \n");
|
593 | ss << '"' << rule.second->getId() << R"_(": [)_";
|
594 | bool firstCol = true;
|
595 | for (auto& atom : rule.second->getAtoms()) {
|
596 | comma(firstCol);
|
597 | std::string relationName = atom.identifier;
|
598 | relationName = relationName.substr(0, relationName.find('('));
|
599 | auto* relation = out.getProgramRun()->getRelation(relationName);
|
600 | std::string relationSize =
|
601 | relation == nullptr ? "" : std::to_string(relation->size());
|
602 | ss << '[';
|
603 | ss << '"' << Tools::cleanJsonOut(Tools::cleanString(atom.rule)) << R"_(", )_";
|
604 | ss << '"' << Tools::cleanJsonOut(atom.identifier) << R"_(", )_";
|
605 | ss << relationSize << ", ";
|
606 | ss << atom.frequency << ']';
|
607 | }
|
608 | ss << "]";
|
609 | }
|
610 | }
|
611 | }
|
612 |
|
613 | ss << '}';
|
614 | return ss;
|
615 | }
|
616 |
|
617 | std::string genJson() {
|
618 | std::stringstream ss;
|
619 |
|
620 | genJsonTop(ss);
|
621 | ss << ",\n";
|
622 | genJsonRelations(ss, "topRel", 3);
|
623 | ss << ",\n";
|
624 | genJsonRules(ss, "topRul", 3);
|
625 | ss << ",\n";
|
626 | genJsonRelations(ss, "rel", relationTable.rows.size());
|
627 | ss << ",\n";
|
628 | genJsonRules(ss, "rul", ruleTable.rows.size());
|
629 | ss << ",\n";
|
630 | genJsonUsage(ss);
|
631 | ss << ",\n";
|
632 | genJsonConfiguration(ss);
|
633 | ss << ",\n";
|
634 | genJsonAtoms(ss);
|
635 | ss << '\n';
|
636 |
|
637 | ss << "};\n";
|
638 |
|
639 | return ss.str();
|
640 | }
|
641 |
|
642 | /// Return an exit status equal to 0 on success.
|
643 | int outputHtml(std::string filename = "profiler_html/") {
|
644 | std::cout << "SouffleProf\n";
|
645 | std::cout << "Generating HTML files...\n";
|
646 |
|
647 | std::filesystem::path filepath(filename);
|
648 | if (filepath.has_parent_path()) {
|
649 | std::error_code ec;
|
650 | std::filesystem::create_directories(filepath.parent_path(), ec);
|
651 | if (ec != std::error_code{}) {
|
652 | std::cerr << "directory " << filepath.parent_path()
|
653 | << " could not be created. Please create it and try again.\n";
|
654 | return 2;
|
655 | }
|
656 | }
|
657 |
|
658 | if (!filepath.has_filename()) {
|
659 | // create a fresh filename
|
660 | bool notfound = true;
|
661 | unsigned i = 1;
|
662 | while (i < 1000) {
|
663 | std::filesystem::path freshPath = filepath;
|
664 | freshPath /= std::to_string(i);
|
665 | freshPath.replace_extension(".html");
|
666 | if (!std::filesystem::exists(freshPath)) {
|
667 | filepath = freshPath;
|
668 | notfound = false;
|
669 | break;
|
670 | }
|
671 | ++i;
|
672 | }
|
673 | if (notfound) {
|
674 | std::cerr << "Could not generate a fresh file name (1000 tested).\n";
|
675 | return 2;
|
676 | }
|
677 | }
|
678 |
|
679 | std::ofstream outfile(filepath);
|
680 |
|
681 | outfile << HtmlGenerator::getHtml(genJson());
|
682 |
|
683 | std::cout << "file output to: " << filepath << std::endl;
|
684 |
|
685 | return 0;
|
686 | }
|
687 |
|
688 | void quit() {
|
689 | if (updater.joinable()) {
|
690 | updater.join();
|
691 | }
|
692 | }
|
693 |
|
694 | static void help() {
|
695 | std::cout << "\nAvailable profiling commands:" << std::endl;
|
696 | std::printf(" %-30s%-5s %s\n", "rel", "-", "display relation table.");
|
697 | std::printf(" %-30s%-5s %s\n", "rel <relation id>", "-", "display all rules of a given relation.");
|
698 | std::printf(" %-30s%-5s %s\n", "rul", "-", "display rule table");
|
699 | std::printf(" %-30s%-5s %s\n", "rul <rule id>", "-", "display all version of given rule.");
|
700 | std::printf(" %-30s%-5s %s\n", "rul id", "-", "display all rules names and ids.");
|
701 | std::printf(
|
702 | " %-30s%-5s %s\n", "rul id <rule id>", "-", "display the rule name for the given rule id.");
|
703 | std::printf(" %-30s%-5s %s\n", "graph <relation id> <type>", "-",
|
704 | "graph a relation by type: (tot_t/copy_t/tuples).");
|
705 | std::printf(" %-30s%-5s %s\n", "graph <rule id> <type>", "-",
|
706 | "graph recursive(C) rule by type(tot_t/tuples).");
|
707 | std::printf(" %-30s%-5s %s\n", "graph ver <rule id> <type>", "-",
|
708 | "graph recursive(C) rule versions by type(tot_t/copy_t/tuples).");
|
709 | std::printf(" %-30s%-5s %s\n", "top", "-", "display top-level summary of program run.");
|
710 | std::printf(" %-30s%-5s %s\n", "configuration", "-", "display configuration settings for this run.");
|
711 | std::printf(" %-30s%-5s %s\n", "usage [relation id|rule id]", "-",
|
712 | "display CPU usage graphs for a relation or rule.");
|
713 | std::printf(" %-30s%-5s %s\n", "memory", "-", "display memory usage.");
|
714 | std::printf(" %-30s%-5s %s\n", "help", "-", "print this.");
|
715 |
|
716 | std::cout << "\nInteractive mode only commands:" << std::endl;
|
717 | // if (alive) std::printf(" %-30s%-5s %s\n", "stop", "-",
|
718 | // "stop the current live run.");
|
719 | std::printf(" %-30s%-5s %s\n", "limit <row count>", "-", "limit number of results shown.");
|
720 | std::printf(" %-30s%-5s %s\n", "sort <col number>", "-", "sort tables by given column number.");
|
721 | std::printf(" %-30s%-5s %s\n", "q", "-", "exit program.");
|
722 | }
|
723 |
|
724 | void usageRelation(std::string id) {
|
725 | std::vector<std::vector<std::string>> formattedRelationTable =
|
726 | Tools::formatTable(relationTable, precision);
|
727 | std::string name = "";
|
728 | bool found = false;
|
729 | for (auto& row : formattedRelationTable) {
|
730 | if (row[5] == id || row[6] == id) {
|
731 | name = row[5];
|
732 | found = true;
|
733 | break;
|
734 | }
|
735 | }
|
736 | if (!found) {
|
737 | std::cout << "Relation does not exist.\n";
|
738 | return;
|
739 | }
|
740 |
|
741 | const Relation* rel = out.getProgramRun()->getRelation(name);
|
742 | usage(rel->getEndtime(), rel->getStarttime());
|
743 | }
|
744 |
|
745 | void usageRule(std::string id) {
|
746 | std::vector<std::vector<std::string>> formattedRuleTable = Tools::formatTable(ruleTable, precision);
|
747 | std::string relName = "";
|
748 | std::string srcLocator = "";
|
749 | bool found = false;
|
750 | for (auto& row : formattedRuleTable) {
|
751 | if (row[5] == id || row[6] == id) {
|
752 | relName = row[7];
|
753 | srcLocator = row[10];
|
754 | found = true;
|
755 | break;
|
756 | }
|
757 | }
|
758 | if (!found) {
|
759 | std::cout << "Rule does not exist.\n";
|
760 | return;
|
761 | }
|
762 |
|
763 | auto* rel = out.getProgramRun()->getRelation(relName);
|
764 | if (rel == nullptr) {
|
765 | std::cout << "Relation ceased to exist. Odd." << std::endl;
|
766 | return;
|
767 | }
|
768 | if (rel->getRuleMap().count(srcLocator) == 0) {
|
769 | std::cout << "Rule ceased to exist. Odd." << std::endl;
|
770 | return;
|
771 | }
|
772 |
|
773 | auto& rul = rel->getRuleMap().at(srcLocator);
|
774 | usage(rul->getEndtime(), rul->getStarttime());
|
775 | }
|
776 |
|
777 | std::set<Usage> getUsageStats(std::size_t width = std::size_t(-1)) {
|
778 | std::set<Usage> usages;
|
779 | DirectoryEntry* usageStats = as<DirectoryEntry>(
|
780 | ProfileEventSingleton::instance().getDB().lookupEntry({"program", "usage", "timepoint"}));
|
781 | if (usageStats == nullptr || usageStats->getKeys().size() < 2) {
|
782 | return usages;
|
783 | }
|
784 | std::chrono::microseconds endTime{};
|
785 | std::chrono::microseconds startTime{};
|
786 | std::chrono::microseconds timeStep{};
|
787 | // Translate the string ordered text usage stats to a time ordered binary form.
|
788 | std::set<Usage> allUsages;
|
789 | for (auto& currentKey : usageStats->getKeys()) {
|
790 | Usage currentUsage{};
|
791 | uint64_t cur = std::stoul(currentKey);
|
792 | currentUsage.time = std::chrono::duration<uint64_t, std::micro>(cur);
|
793 | cur = as<SizeEntry>(usageStats->readDirectoryEntry(currentKey)->readEntry("systemtime"))
|
794 | ->getSize();
|
795 | currentUsage.systemtime = std::chrono::duration<uint64_t, std::micro>(cur);
|
796 | cur = as<SizeEntry>(usageStats->readDirectoryEntry(currentKey)->readEntry("usertime"))->getSize();
|
797 | currentUsage.usertime = std::chrono::duration<uint64_t, std::micro>(cur);
|
798 | currentUsage.maxRSS =
|
799 | as<SizeEntry>(usageStats->readDirectoryEntry(currentKey)->readEntry("maxRSS"))->getSize();
|
800 |
|
801 | // Duplicate times are possible
|
802 | if (allUsages.find(currentUsage) != allUsages.end()) {
|
803 | auto& existing = *allUsages.find(currentUsage);
|
804 | currentUsage.systemtime = std::max(existing.systemtime, currentUsage.systemtime);
|
805 | currentUsage.usertime = std::max(existing.usertime, currentUsage.usertime);
|
806 | currentUsage.maxRSS = std::max(existing.maxRSS, currentUsage.maxRSS);
|
807 | allUsages.erase(currentUsage);
|
808 | }
|
809 | allUsages.insert(currentUsage);
|
810 | }
|
811 |
|
812 | // cpu times aren't quite recorded in a monotonic way, so skip the invalid ones.
|
813 | for (auto it = ++allUsages.begin(); it != allUsages.end(); ++it) {
|
814 | auto previous = std::prev(it);
|
815 | if (it->usertime < previous->usertime || it->systemtime < previous->systemtime ||
|
816 | it->time == previous->time) {
|
817 | it = allUsages.erase(it);
|
818 | --it;
|
819 | }
|
820 | }
|
821 |
|
822 | // Extract our overall stats
|
823 | startTime = allUsages.begin()->time;
|
824 | endTime = allUsages.rbegin()->time;
|
825 |
|
826 | // If we don't have enough records, just return what we can
|
827 | if (allUsages.size() < width) {
|
828 | return allUsages;
|
829 | }
|
830 |
|
831 | timeStep = (endTime - startTime) / width;
|
832 |
|
833 | // Store the timepoints we need for the graph
|
834 | for (uint32_t i = 1; i <= width; ++i) {
|
835 | auto it = allUsages.upper_bound(Usage{startTime + timeStep * i, 0, {}, {}});
|
836 | if (it != allUsages.begin()) {
|
837 | --it;
|
838 | }
|
839 | usages.insert(*it);
|
840 | }
|
841 |
|
842 | return usages;
|
843 | }
|
844 |
|
845 | void usage(uint32_t height = 20) {
|
846 | usage({}, {}, height);
|
847 | }
|
848 |
|
849 | void usage(std::chrono::microseconds endTime, std::chrono::microseconds startTime, uint32_t height = 20) {
|
850 | uint32_t width = getTermWidth() - 8;
|
851 |
|
852 | std::set<Usage> usages = getUsageStats(width);
|
853 |
|
854 | if (usages.size() < 2) {
|
855 | for (uint8_t i = 0; i < height + 2; ++i) {
|
856 | std::cout << std::endl;
|
857 | }
|
858 | std::cout << "Insufficient data for usage statistics." << std::endl;
|
859 | return;
|
860 | }
|
861 |
|
862 | double maxIntervalUsage = 0;
|
863 |
|
864 | // Extract our overall stats
|
865 | if (startTime.count() == 0) {
|
866 | startTime = usages.begin()->time;
|
867 | }
|
868 | if (endTime.count() == 0) {
|
869 | endTime = usages.rbegin()->time;
|
870 | }
|
871 |
|
872 | if (usages.size() < width) {
|
873 | width = usages.size();
|
874 | }
|
875 |
|
876 | // Find maximum so we can normalise the graph
|
877 | Usage previousUsage{{}, 0, {}, {}};
|
878 | for (auto& currentUsage : usages) {
|
879 | double usageDiff = (currentUsage.systemtime - previousUsage.systemtime + currentUsage.usertime -
|
880 | previousUsage.usertime)
|
881 | .count();
|
882 | usageDiff /= (currentUsage.time - previousUsage.time).count();
|
883 | if (usageDiff > maxIntervalUsage) {
|
884 | maxIntervalUsage = usageDiff;
|
885 | }
|
886 |
|
887 | previousUsage = currentUsage;
|
888 | }
|
889 |
|
890 | double intervalUsagePercent = 100.0 * maxIntervalUsage;
|
891 | std::printf("%11s\n", "cpu total");
|
892 | std::printf("%11s\n", Tools::formatTime(usages.rbegin()->usertime).c_str());
|
893 |
|
894 | // Add columns to the graph
|
895 | // char grid[height][width];
|
896 | std::vector<std::vector<char>> grid;
|
897 | grid.reserve(height);
|
898 | grid.resize(height);
|
899 | for (uint32_t i = 0; i < height; ++i) {
|
900 | grid[i].reserve(width);
|
901 | grid[i].resize(width, ' ');
|
902 | }
|
903 |
|
904 | previousUsage = {{}, 0, {}, {}};
|
905 | uint32_t col = 0;
|
906 | for (const Usage& currentUsage : usages) {
|
907 | uint64_t curHeight = 0;
|
908 | uint64_t curSystemHeight = 0;
|
909 | // Usage may be 0
|
910 | if (maxIntervalUsage != 0) {
|
911 | curHeight = (currentUsage.systemtime - previousUsage.systemtime + currentUsage.usertime -
|
912 | previousUsage.usertime)
|
913 | .count();
|
914 | curHeight /= (currentUsage.time - previousUsage.time).count();
|
915 | curHeight *= height / maxIntervalUsage;
|
916 |
|
917 | curSystemHeight = (currentUsage.systemtime - previousUsage.systemtime).count();
|
918 | curSystemHeight /= (currentUsage.time - previousUsage.time).count();
|
919 | curSystemHeight *= height / maxIntervalUsage;
|
920 | }
|
921 | for (uint32_t row = 0; row < curHeight; ++row) {
|
922 | grid[row][col] = '*';
|
923 | }
|
924 | for (uint32_t row = curHeight - curSystemHeight; row < curHeight; ++row) {
|
925 | grid[row][col] = '+';
|
926 | }
|
927 | previousUsage = currentUsage;
|
928 | ++col;
|
929 | }
|
930 |
|
931 | // Print array
|
932 | for (int32_t row = height - 1; row >= 0; --row) {
|
933 | printf("%6d%% ", uint32_t(intervalUsagePercent * (row + 1) / height));
|
934 | for (uint32_t col = 0; col < width; ++col) {
|
935 | std::cout << grid[row][col];
|
936 | }
|
937 | std::cout << std::endl;
|
938 | }
|
939 | for (uint32_t col = 0; col < 8; ++col) {
|
940 | std::cout << ' ';
|
941 | }
|
942 | for (uint32_t col = 0; col < width; ++col) {
|
943 | std::cout << '-';
|
944 | }
|
945 | std::cout << std::endl;
|
946 | }
|
947 |
|
948 | void memoryUsage(uint32_t height = 20) {
|
949 | memoryUsage({}, {}, height);
|
950 | }
|
951 |
|
952 | void memoryUsage(std::chrono::microseconds /* endTime */, std::chrono::microseconds /* startTime */,
|
953 | uint32_t height = 20) {
|
954 | uint32_t width = getTermWidth() - 8;
|
955 | uint64_t maxMaxRSS = 0;
|
956 |
|
957 | std::set<Usage> usages = getUsageStats(width);
|
958 | // char grid[height][width];
|
959 | std::vector<std::vector<char>> grid;
|
960 | grid.reserve(height);
|
961 | grid.resize(height);
|
962 | for (uint32_t i = 0; i < height; ++i) {
|
963 | grid[i].reserve(width);
|
964 | grid[i].resize(width, ' ');
|
965 | }
|
966 |
|
967 | for (auto& usage : usages) {
|
968 | maxMaxRSS = std::max(maxMaxRSS, usage.maxRSS);
|
969 | }
|
970 | std::size_t col = 0;
|
971 | for (const Usage& currentUsage : usages) {
|
972 | uint64_t curHeight = height * currentUsage.maxRSS / maxMaxRSS;
|
973 | for (uint32_t row = 0; row < curHeight; ++row) {
|
974 | grid[row][col] = '*';
|
975 | }
|
976 | ++col;
|
977 | }
|
978 |
|
979 | // Print array
|
980 | for (int32_t row = height - 1; row >= 0; --row) {
|
981 | printf("%6s ", Tools::formatMemory(maxMaxRSS * (row + 1) / height).c_str());
|
982 | for (uint32_t col = 0; col < width; ++col) {
|
983 | std::cout << grid[row][col];
|
984 | }
|
985 | std::cout << std::endl;
|
986 | }
|
987 | for (uint32_t col = 0; col < 8; ++col) {
|
988 | std::cout << ' ';
|
989 | }
|
990 | for (uint32_t col = 0; col < width; ++col) {
|
991 | std::cout << '-';
|
992 | }
|
993 | std::cout << std::endl;
|
994 | }
|
995 | void setupTabCompletion() {
|
996 | linereader.clearTabCompletion();
|
997 |
|
998 | linereader.appendTabCompletion("rel");
|
999 | linereader.appendTabCompletion("rul");
|
1000 | linereader.appendTabCompletion("rul id");
|
1001 | linereader.appendTabCompletion("graph ");
|
1002 | linereader.appendTabCompletion("top");
|
1003 | linereader.appendTabCompletion("help");
|
1004 | linereader.appendTabCompletion("usage");
|
1005 | linereader.appendTabCompletion("limit ");
|
1006 | linereader.appendTabCompletion("memory");
|
1007 | linereader.appendTabCompletion("configuration");
|
1008 |
|
1009 | // add rel tab completes after the rest so users can see all commands first
|
1010 | for (auto& row : Tools::formatTable(relationTable, precision)) {
|
1011 | linereader.appendTabCompletion("rel " + row[5]);
|
1012 | linereader.appendTabCompletion("graph " + row[5] + " tot_t");
|
1013 | linereader.appendTabCompletion("graph " + row[5] + " copy_t");
|
1014 | linereader.appendTabCompletion("graph " + row[5] + " tuples");
|
1015 | linereader.appendTabCompletion("usage " + row[5]);
|
1016 | }
|
1017 | }
|
1018 |
|
1019 | void configuration() {
|
1020 | std::cout << "Configuration" << '\n';
|
1021 | printf("%30s %s", "Key", "Value\n\n");
|
1022 | for (auto& kvp :
|
1023 | ProfileEventSingleton::instance().getDB().getStringMap({"program", "configuration"})) {
|
1024 | if (kvp.first == "") {
|
1025 | printf("%30s %s\n", "Datalog input file", kvp.second.c_str());
|
1026 | continue;
|
1027 | }
|
1028 | printf("%30s %s\n", kvp.first.c_str(), kvp.second.c_str());
|
1029 | }
|
1030 | std::cout << std::endl;
|
1031 | }
|
1032 |
|
1033 | void top() {
|
1034 | const std::shared_ptr<ProgramRun>& run = out.getProgramRun();
|
1035 | auto* totalRelationsEntry = as<TextEntry>(ProfileEventSingleton::instance().getDB().lookupEntry(
|
1036 | {"program", "configuration", "relationCount"}));
|
1037 | auto* totalRulesEntry = as<TextEntry>(ProfileEventSingleton::instance().getDB().lookupEntry(
|
1038 | {"program", "configuration", "ruleCount"}));
|
1039 | std::size_t totalRelations = 0;
|
1040 | if (totalRelationsEntry != nullptr) {
|
1041 | totalRelations = std::stoul(totalRelationsEntry->getText());
|
1042 | } else {
|
1043 | totalRelations = run->getRelationMap().size();
|
1044 | }
|
1045 | std::size_t totalRules = 0;
|
1046 | if (totalRulesEntry != nullptr) {
|
1047 | totalRules = std::stoul(totalRulesEntry->getText());
|
1048 | } else {
|
1049 | totalRules = ruleTable.getRows().size();
|
1050 | }
|
1051 | std::printf("%11s%10s%10s%10s%10s%20s\n", "runtime", "loadtime", "savetime", "relations", "rules",
|
1052 | "tuples generated");
|
1053 |
|
1054 | std::printf("%11s%10s%10s%10s%10s%14s\n", run->getRuntime().c_str(),
|
1055 | run->formatTime(run->getTotalLoadtime()).c_str(),
|
1056 | run->formatTime(run->getTotalSavetime()).c_str(), run->formatNum(0, totalRelations).c_str(),
|
1057 | run->formatNum(0, totalRules).c_str(),
|
1058 | run->formatNum(precision, run->getTotalSize()).c_str());
|
1059 |
|
1060 | // Progress bar
|
1061 | // Determine number of relations processed
|
1062 | std::size_t processedRelations = run->getRelationMap().size();
|
1063 | std::size_t screenWidth = getTermWidth() - 10;
|
1064 | if (alive && totalRelationsEntry != nullptr) {
|
1065 | std::cout << "Progress ";
|
1066 | for (std::size_t i = 0; i < screenWidth; ++i) {
|
1067 | if (screenWidth * processedRelations / totalRelations > i) {
|
1068 | std::cout << '#';
|
1069 | } else {
|
1070 | std::cout << '_';
|
1071 | }
|
1072 | }
|
1073 | }
|
1074 | std::cout << std::endl;
|
1075 |
|
1076 | std::cout << "Slowest relations to fully evaluate\n";
|
1077 | rel(3, false);
|
1078 | for (std::size_t i = relationTable.getRows().size(); i < 3; ++i) {
|
1079 | std::cout << "\n";
|
1080 | }
|
1081 | std::cout << "Slowest rules to fully evaluate\n";
|
1082 | rul(3, false);
|
1083 | for (std::size_t i = ruleTable.getRows().size(); i < 3; ++i) {
|
1084 | std::cout << "\n";
|
1085 | }
|
1086 |
|
1087 | usage(10);
|
1088 | }
|
1089 |
|
1090 | void setResultLimit(std::size_t limit) {
|
1091 | resultLimit = limit;
|
1092 | }
|
1093 |
|
1094 | void rel(std::size_t limit, bool showLimit = true) {
|
1095 | relationTable.sort(sortColumn);
|
1096 | std::cout << " ----- Relation Table -----\n";
|
1097 | std::printf("%8s%8s%8s%8s%8s%8s%8s%8s%8s%6s %s\n\n", "TOT_T", "NREC_T", "REC_T", "COPY_T", "LOAD_T",
|
1098 | "SAVE_T", "TUPLES", "READS", "TUP/s", "ID", "NAME");
|
1099 | std::size_t count = 0;
|
1100 | for (auto& row : Tools::formatTable(relationTable, precision)) {
|
1101 | if (++count > limit) {
|
1102 | if (showLimit) {
|
1103 | std::cout << (relationTable.getRows().size() - resultLimit) << " rows not shown"
|
1104 | << std::endl;
|
1105 | }
|
1106 | break;
|
1107 | }
|
1108 | std::printf("%8s%8s%8s%8s%8s%8s%8s%8s%8s%6s %s\n", row[0].c_str(), row[1].c_str(), row[2].c_str(),
|
1109 | row[3].c_str(), row[9].c_str(), row[10].c_str(), row[4].c_str(), row[12].c_str(),
|
1110 | row[8].c_str(), row[6].c_str(), row[5].c_str());
|
1111 | }
|
1112 | }
|
1113 |
|
1114 | void rul(std::size_t limit, bool showLimit = true) {
|
1115 | ruleTable.sort(sortColumn);
|
1116 | std::cout << " ----- Rule Table -----\n";
|
1117 | std::printf(
|
1118 | "%8s%8s%8s%8s%8s%8s %s\n\n", "TOT_T", "NREC_T", "REC_T", "TUPLES", "TUP/s", "ID", "RELATION");
|
1119 | std::size_t count = 0;
|
1120 | for (auto& row : Tools::formatTable(ruleTable, precision)) {
|
1121 | if (++count > limit) {
|
1122 | if (showLimit) {
|
1123 | std::cout << (ruleTable.getRows().size() - resultLimit) << " rows not shown" << std::endl;
|
1124 | }
|
1125 | break;
|
1126 | }
|
1127 | std::printf("%8s%8s%8s%8s%8s%8s %s\n", row[0].c_str(), row[1].c_str(), row[2].c_str(),
|
1128 | row[4].c_str(), row[9].c_str(), row[6].c_str(), row[7].c_str());
|
1129 | }
|
1130 | }
|
1131 |
|
1132 | void id(std::string col) {
|
1133 | ruleTable.sort(6);
|
1134 | std::vector<std::vector<std::string>> table = Tools::formatTable(ruleTable, precision);
|
1135 |
|
1136 | if (col == "0") {
|
1137 | std::printf("%7s%2s%s\n\n", "ID", "", "NAME");
|
1138 | for (auto& row : table) {
|
1139 | std::printf("%7s%2s%s\n", row[6].c_str(), "", row[5].c_str());
|
1140 | }
|
1141 | } else {
|
1142 | for (auto& row : table) {
|
1143 | if (row[6] == col) {
|
1144 | std::printf("%7s%2s%s\n", row[6].c_str(), "", row[5].c_str());
|
1145 | }
|
1146 | }
|
1147 | }
|
1148 | }
|
1149 |
|
1150 | void relRul(std::string str) {
|
1151 | ruleTable.sort(sortColumn);
|
1152 |
|
1153 | std::vector<std::vector<std::string>> formattedRuleTable = Tools::formatTable(ruleTable, precision);
|
1154 | std::vector<std::vector<std::string>> formattedRelationTable =
|
1155 | Tools::formatTable(relationTable, precision);
|
1156 |
|
1157 | std::cout << " ----- Rules of a Relation -----\n";
|
1158 | std::printf("%8s%8s%8s%8s%8s %s\n\n", "TOT_T", "NREC_T", "REC_T", "TUPLES", "ID", "NAME");
|
1159 | std::string name = "";
|
1160 | for (auto& row : formattedRelationTable) {
|
1161 | // Test for relation name or relation id
|
1162 | if (row[5] == str || row[6] == str) {
|
1163 | std::printf("%8s%8s%8s%8s%8s %s\n", row[0].c_str(), row[1].c_str(), row[2].c_str(),
|
1164 | row[4].c_str(), row[6].c_str(), row[5].c_str());
|
1165 | name = row[5];
|
1166 | break;
|
1167 | }
|
1168 | }
|
1169 | std::cout << " ---------------------------------------------------------\n";
|
1170 | for (auto& row : formattedRuleTable) {
|
1171 | if (row[7] == name) {
|
1172 | std::printf("%8s%8s%8s%8s%8s %s\n", row[0].c_str(), row[1].c_str(), row[2].c_str(),
|
1173 | row[4].c_str(), row[6].c_str(), row[7].c_str());
|
1174 | }
|
1175 | }
|
1176 | std::string src = "";
|
1177 | const std::shared_ptr<ProgramRun>& run = out.getProgramRun();
|
1178 | if (run->getRelation(name) != nullptr) {
|
1179 | src = run->getRelation(name)->getLocator();
|
1180 | }
|
1181 | std::cout << "\nSrc locator: " << src << "\n\n";
|
1182 | for (auto& row : formattedRuleTable) {
|
1183 | if (row[7] == name) {
|
1184 | std::printf("%7s%2s%s\n", row[6].c_str(), "", row[5].c_str());
|
1185 | }
|
1186 | }
|
1187 | }
|
1188 |
|
1189 | void verRul(std::string str) {
|
1190 | if (str.find(".") == std::string::npos) {
|
1191 | std::cout << "Rule does not exist\n";
|
1192 | return;
|
1193 | }
|
1194 | std::vector<std::string> part = Tools::split(str, ".");
|
1195 | std::string strRel = "R" + part[0].substr(1);
|
1196 |
|
1197 | Table versionTable = out.getVersions(strRel, str);
|
1198 | versionTable.sort(sortColumn);
|
1199 |
|
1200 | ruleTable.sort(sortColumn); // why isnt it sorted in the original java?!?
|
1201 |
|
1202 | std::vector<std::vector<std::string>> formattedRuleTable = Tools::formatTable(ruleTable, precision);
|
1203 |
|
1204 | bool found = false;
|
1205 | std::string ruleName;
|
1206 | std::string srcLocator;
|
1207 | // Check that the rule exists, and print it out if so.
|
1208 | for (auto& row : formattedRuleTable) {
|
1209 | if (row[6] == str) {
|
1210 | std::cout << row[5] << std::endl;
|
1211 | found = true;
|
1212 | ruleName = row[5];
|
1213 | srcLocator = row[10];
|
1214 | }
|
1215 | }
|
1216 |
|
1217 | // If the rule exists, print out the source locator.
|
1218 | if (found) {
|
1219 | if (versionTable.rows.size() > 0) {
|
1220 | if (versionTable.rows[0]->cells[9] != nullptr) {
|
1221 | std::cout << "Src locator-: " << (*versionTable.rows[0])[9]->getStringVal() << "\n\n";
|
1222 | } else {
|
1223 | std::cout << "Src locator-: -\n\n";
|
1224 | }
|
1225 | } else if (formattedRuleTable.size() > 0) {
|
1226 | std::cout << "Src locator-: " << formattedRuleTable[0][10] << "\n\n";
|
1227 | }
|
1228 | }
|
1229 |
|
1230 | // Print out the versions of this rule.
|
1231 | std::cout << " ----- Rule Versions Table -----\n";
|
1232 | std::printf("%8s%8s%8s%16s%6s\n\n", "TOT_T", "NREC_T", "REC_T", "TUPLES", "VER");
|
1233 | for (auto& row : formattedRuleTable) {
|
1234 | if (row[6] == str) {
|
1235 | std::printf("%8s%8s%8s%16s%6s\n", row[0].c_str(), row[1].c_str(), row[2].c_str(),
|
1236 | row[4].c_str(), "");
|
1237 | }
|
1238 | }
|
1239 | std::cout << " ---------------------------------------------\n";
|
1240 | for (auto& _row : versionTable.rows) {
|
1241 | Row row = *_row;
|
1242 |
|
1243 | std::printf("%8s%8s%8s%16s%6s\n", row[0]->toString(precision).c_str(),
|
1244 | row[1]->toString(precision).c_str(), row[2]->toString(precision).c_str(),
|
1245 | row[4]->toString(precision).c_str(), row[8]->toString(precision).c_str());
|
1246 | Table atom_table = out.getVersionAtoms(strRel, srcLocator, row[8]->getLongVal());
|
1247 | verAtoms(atom_table);
|
1248 | }
|
1249 |
|
1250 | if (!versionTable.rows.empty()) {
|
1251 | return;
|
1252 | }
|
1253 |
|
1254 | Table atom_table = out.getAtomTable(strRel, str);
|
1255 | verAtoms(atom_table, ruleName);
|
1256 | }
|
1257 |
|
1258 | void iterRel(std::string c, std::string col) {
|
1259 | const std::shared_ptr<ProgramRun>& run = out.getProgramRun();
|
1260 | std::vector<std::vector<std::string>> table = Tools::formatTable(relationTable, -1);
|
1261 | std::vector<std::shared_ptr<Iteration>> iter;
|
1262 | for (auto& row : table) {
|
1263 | if (row[6] == c) {
|
1264 | std::printf("%4s%2s%s\n\n", row[6].c_str(), "", row[5].c_str());
|
1265 | iter = run->getRelation(row[5])->getIterations();
|
1266 | if (col == "tot_t") {
|
1267 | std::vector<std::chrono::microseconds> list;
|
1268 | for (auto& i : iter) {
|
1269 | list.emplace_back(i->getRuntime());
|
1270 | }
|
1271 | std::printf("%4s %s\n\n", "NO", "RUNTIME");
|
1272 | graphByTime(list);
|
1273 | } else if (col == "copy_t") {
|
1274 | std::vector<std::chrono::microseconds> list;
|
1275 | for (auto& i : iter) {
|
1276 | list.emplace_back(i->getCopytime());
|
1277 | }
|
1278 | std::printf("%4s %s\n\n", "NO", "COPYTIME");
|
1279 | graphByTime(list);
|
1280 | } else if (col == "tuples") {
|
1281 | std::vector<std::size_t> list;
|
1282 | for (auto& i : iter) {
|
1283 | list.emplace_back(i->size());
|
1284 | }
|
1285 | std::printf("%4s %s\n\n", "NO", "TUPLES");
|
1286 | graphBySize(list);
|
1287 | }
|
1288 | return;
|
1289 | }
|
1290 | }
|
1291 | for (auto& row : table) {
|
1292 | if (row[5] == c) {
|
1293 | std::printf("%4s%2s%s\n\n", row[6].c_str(), "", row[5].c_str());
|
1294 | const std::shared_ptr<ProgramRun>& run = out.getProgramRun();
|
1295 | iter = run->getRelation(row[5])->getIterations();
|
1296 | if (col == "tot_t") {
|
1297 | std::vector<std::chrono::microseconds> list;
|
1298 | for (auto& i : iter) {
|
1299 | list.emplace_back(i->getRuntime());
|
1300 | }
|
1301 | std::printf("%4s %s\n\n", "NO", "RUNTIME");
|
1302 | graphByTime(list);
|
1303 | } else if (col == "copy_t") {
|
1304 | std::vector<std::chrono::microseconds> list;
|
1305 | for (auto& i : iter) {
|
1306 | list.emplace_back(i->getCopytime());
|
1307 | }
|
1308 | std::printf("%4s %s\n\n", "NO", "COPYTIME");
|
1309 | graphByTime(list);
|
1310 | } else if (col == "tuples") {
|
1311 | std::vector<std::size_t> list;
|
1312 | for (auto& i : iter) {
|
1313 | list.emplace_back(i->size());
|
1314 | }
|
1315 | std::printf("%4s %s\n\n", "NO", "TUPLES");
|
1316 | graphBySize(list);
|
1317 | }
|
1318 | return;
|
1319 | }
|
1320 | }
|
1321 | }
|
1322 |
|
1323 | void iterRul(std::string c, std::string col) {
|
1324 | std::vector<std::vector<std::string>> table = Tools::formatTable(ruleTable, precision);
|
1325 | std::vector<std::shared_ptr<Iteration>> iter;
|
1326 | for (auto& row : table) {
|
1327 | if (row[6] == c) {
|
1328 | std::printf("%6s%2s%s\n\n", row[6].c_str(), "", row[5].c_str());
|
1329 | const std::shared_ptr<ProgramRun>& run = out.getProgramRun();
|
1330 | iter = run->getRelation(row[7])->getIterations();
|
1331 | if (col == "tot_t") {
|
1332 | std::vector<std::chrono::microseconds> list;
|
1333 | for (auto& i : iter) {
|
1334 | bool add = false;
|
1335 | std::chrono::microseconds totalTime{};
|
1336 | for (auto& rul : i->getRules()) {
|
1337 | if (rul.second->getId() == c) {
|
1338 | totalTime += rul.second->getRuntime();
|
1339 | add = true;
|
1340 | }
|
1341 | }
|
1342 | if (add) {
|
1343 | list.emplace_back(totalTime);
|
1344 | }
|
1345 | }
|
1346 | std::printf("%4s %s\n\n", "NO", "RUNTIME");
|
1347 | graphByTime(list);
|
1348 | } else if (col == "tuples") {
|
1349 | std::vector<std::size_t> list;
|
1350 | for (auto& i : iter) {
|
1351 | bool add = false;
|
1352 | std::size_t totalSize = 0L;
|
1353 | for (auto& rul : i->getRules()) {
|
1354 | if (rul.second->getId() == c) {
|
1355 | totalSize += rul.second->size();
|
1356 | add = true;
|
1357 | }
|
1358 | }
|
1359 | if (add) {
|
1360 | list.emplace_back(totalSize);
|
1361 | }
|
1362 | }
|
1363 | std::printf("%4s %s\n\n", "NO", "TUPLES");
|
1364 | graphBySize(list);
|
1365 | }
|
1366 | break;
|
1367 | }
|
1368 | }
|
1369 | }
|
1370 |
|
1371 | void verGraph(std::string c, std::string col) {
|
1372 | if (c.find('.') == std::string::npos) {
|
1373 | std::cout << "Rule does not exist";
|
1374 | return;
|
1375 | }
|
1376 |
|
1377 | std::vector<std::string> part = Tools::split(c, ".");
|
1378 | std::string strRel = "R" + part[0].substr(1);
|
1379 |
|
1380 | Table versionTable = out.getVersions(strRel, c);
|
1381 | std::printf("%6s%2s%s\n\n", (*versionTable.rows[0])[6]->toString(0).c_str(), "",
|
1382 | (*versionTable.rows[0])[5]->toString(0).c_str());
|
1383 | if (col == "tot_t") {
|
1384 | std::vector<std::chrono::microseconds> list;
|
1385 | for (auto& row : versionTable.rows) {
|
1386 | list.emplace_back((*row)[0]->getTimeVal());
|
1387 | }
|
1388 | std::printf("%4s %s\n\n", "NO", "RUNTIME");
|
1389 | graphByTime(list);
|
1390 | } else if (col == "copy_t") {
|
1391 | std::vector<std::chrono::microseconds> list;
|
1392 | for (auto& row : versionTable.rows) {
|
1393 | list.emplace_back((*row)[3]->getTimeVal());
|
1394 | }
|
1395 | std::printf("%4s %s\n\n", "NO", "COPYTIME");
|
1396 | graphByTime(list);
|
1397 | } else if (col == "tuples") {
|
1398 | std::vector<std::size_t> list;
|
1399 | for (auto& row : versionTable.rows) {
|
1400 | list.emplace_back((*row)[4]->getLongVal());
|
1401 | }
|
1402 | std::printf("%4s %s\n\n", "NO", "TUPLES");
|
1403 | graphBySize(list);
|
1404 | }
|
1405 | }
|
1406 |
|
1407 | void graphByTime(std::vector<std::chrono::microseconds> list) {
|
1408 | std::chrono::microseconds max{};
|
1409 | for (auto& d : list) {
|
1410 | if (d > max) {
|
1411 | max = d;
|
1412 | }
|
1413 | }
|
1414 |
|
1415 | std::sort(list.begin(), list.end());
|
1416 | std::reverse(list.begin(), list.end());
|
1417 | int i = 0;
|
1418 | for (auto& d : list) {
|
1419 | uint32_t len = 67.0 * d.count() / max.count();
|
1420 | std::string bar = "";
|
1421 | for (uint32_t j = 0; j < len; j++) {
|
1422 | bar += "*";
|
1423 | }
|
1424 |
|
1425 | std::printf("%4d %10.8f | %s\n", i++, (d.count() / 1000000.0), bar.c_str());
|
1426 | }
|
1427 | }
|
1428 |
|
1429 | void graphBySize(std::vector<std::size_t> list) {
|
1430 | std::size_t max = 0;
|
1431 | for (auto& l : list) {
|
1432 | if (l > max) {
|
1433 | max = l;
|
1434 | }
|
1435 | }
|
1436 | std::sort(list.begin(), list.end());
|
1437 | std::reverse(list.begin(), list.end());
|
1438 | uint32_t i = 0;
|
1439 | for (auto& l : list) {
|
1440 | std::size_t len = max == 0 ? 0 : 64.0 * l / max;
|
1441 | std::string bar = "";
|
1442 | for (uint32_t j = 0; j < len; j++) {
|
1443 | bar += "*";
|
1444 | }
|
1445 |
|
1446 | std::printf("%4d %8s | %s\n", i++, Tools::formatNum(precision, l).c_str(), bar.c_str());
|
1447 | }
|
1448 | }
|
1449 |
|
1450 | protected:
|
1451 | void verAtoms(Table& atomTable, const std::string& ruleName = "") {
|
1452 | // If there are no subrules then just print out any atoms found
|
1453 | // If we do find subrules, then label atoms with their subrules.
|
1454 | if (atomTable.rows.empty()) {
|
1455 | return;
|
1456 | }
|
1457 | bool firstRun = true;
|
1458 | std::string lastRule = ruleName;
|
1459 | for (auto& _row : atomTable.rows) {
|
1460 | Row& row = *_row;
|
1461 | std::string rule = row[0]->toString(precision);
|
1462 | if (rule != lastRule) {
|
1463 | lastRule = rule;
|
1464 | std::cout << " " << row[0]->toString(precision) << std::endl;
|
1465 | firstRun = true;
|
1466 | }
|
1467 | if (firstRun) {
|
1468 | std::printf(" %-16s%-16s%s\n", "FREQ", "RELSIZE", "ATOM");
|
1469 | firstRun = false;
|
1470 | }
|
1471 | std::string relationName = row[1]->getStringVal();
|
1472 | relationName = relationName.substr(0, relationName.find('('));
|
1473 | auto* relation = out.getProgramRun()->getRelation(relationName);
|
1474 | std::string relationSize = relation == nullptr ? "--" : std::to_string(relation->size());
|
1475 | std::printf(" %-16s%-16s%s\n", row[3]->toString(precision).c_str(), relationSize.c_str(),
|
1476 | row[1]->getStringVal().c_str());
|
1477 | }
|
1478 | std::cout << '\n';
|
1479 | }
|
1480 | void updateDB() {
|
1481 | reader->processFile();
|
1482 | ruleTable = out.getRulTable();
|
1483 | relationTable = out.getRelTable();
|
1484 | }
|
1485 |
|
1486 | uint32_t getTermWidth() {
|
1487 | #ifdef _MSC_VER
|
1488 | return 80;
|
1489 | #else
|
1490 | struct winsize w {};
|
1491 | ioctl(0, TIOCGWINSZ, &w);
|
1492 | uint32_t width = w.ws_col > 0 ? w.ws_col : 80;
|
1493 | return width;
|
1494 | #endif
|
1495 | }
|
1496 | };
|
1497 |
|
1498 | } // namespace profile
|
1499 | } // namespace souffle
|