1 #include "conductor.hh"
2 #include "tcp_server.hh"
3 #include "receiver_thread.hh"
4 #include <iostream>
5 #include <thread>
6 #include <QTreeView>
7 #include <QGridLayout>
8 #include <QPushButton>
9 #include <QDebug>
10 #include <QFile>
11 #include <QFileDialog>
12 #include <QJsonDocument>
13 #include <QJsonObject>
14 #include <QApplication>
15 #include "../cpp-integration/message.hpp"
16
17 #include "execution.hh"
18 #include "tree_builder.hh"
19 #include "execution_list.hh"
20 #include "execution_window.hh"
21
22 #include "tree/node_tree.hh"
23
24 #include "analysis/merge_window.hh"
25 #include "analysis/tree_merger.hh"
26
27 #include "utils/std_ext.hh"
28 #include "utils/string_utils.hh"
29 #include "utils/tree_utils.hh"
30 #include "utils/path_utils.hh"
31
32 #include "pixel_views/pt_canvas.hh"
33
34 #include <random>
35
36 #include <QProgressDialog>
37
38 #include "name_map.hh"
39 #include "db_handler.hh"
40
41 namespace cpprofiler
42 {
43
Conductor(Options opt,QWidget * parent)44 Conductor::Conductor(Options opt, QWidget* parent) : QMainWindow(parent), options_(opt)
45 {
46
47 setWindowTitle("CP-Profiler");
48
49 // readSettings();
50
51 auto layout = new QGridLayout();
52
53 {
54 auto window = new QWidget();
55 setCentralWidget(window);
56 window->setLayout(layout);
57 }
58
59 execution_list_.reset(new ExecutionList);
60 layout->addWidget(execution_list_->getWidget());
61
62 auto showButton = new QPushButton("Show Tree");
63 layout->addWidget(showButton);
64 connect(showButton, &QPushButton::clicked, [this]() {
65 for (auto execution : execution_list_->getSelected())
66 {
67 showTraditionalView(execution);
68 }
69 });
70
71 auto mergeButton = new QPushButton("Merge Trees");
72 layout->addWidget(mergeButton);
73
74 connect(mergeButton, &QPushButton::clicked, [this]() {
75 const auto selected = execution_list_->getSelected();
76
77 if (selected.size() == 2)
78 {
79 mergeTrees(selected[0], selected[1]);
80 }
81 else
82 {
83 print("select exactly two executions");
84 }
85 });
86
87 auto saveButton = new QPushButton("Save Execution");
88 layout->addWidget(saveButton);
89
90 /// TODO: make it clear that only the first one is saved
91 connect(saveButton, &QPushButton::clicked, [this]() {
92 for (auto e : execution_list_->getSelected())
93 {
94 saveExecution(e);
95 break;
96 }
97 });
98
99 auto loadButton = new QPushButton("Load Execution");
100 layout->addWidget(loadButton);
101
102 connect(loadButton, &QPushButton::clicked, [this]() {
103 const auto fileName = QFileDialog::getOpenFileName(this, "Open Execution").toStdString();
104
105 if (fileName == "")
106 return;
107
108 const auto eid = getNextExecId();
109
110 auto ex = db_handler::load_execution(fileName.c_str(), eid);
111
112 if (!ex)
113 {
114 print("could not load the execution");
115 }
116 else
117 {
118 const auto ex_id = addNewExecution(ex);
119 print("New execution from a database, ex_id: {}", ex_id);
120 }
121 });
122
123 server_.reset(new TcpServer([this](intptr_t socketDesc) {
124 {
125 /// Initiate a receiver thread
126 auto receiver = new ReceiverThread(socketDesc, settings_);
127 /// Delete the receiver one the thread is finished
128 connect(receiver, &QThread::finished, receiver, &QObject::deleteLater);
129 /// Handle the start message in this connector
130 connect(receiver, &ReceiverThread::notifyStart, [this, receiver](const std::string &ex_name, int ex_id, bool restarts) {
131 handleStart(receiver, ex_name, ex_id, restarts);
132 });
133
134 receiver->start();
135 }
136 }));
137
138 listen_port_ = DEFAULT_PORT;
139
140 // See if the default port is available
141 auto res = server_->listen(QHostAddress::Any, listen_port_);
142 if (!res)
143 {
144 // If not, try any port
145 server_->listen(QHostAddress::Any, 0);
146 listen_port_ = server_->serverPort();
147 }
148
149 QString listen_message;
150 QTextStream ts(&listen_message);
151 ts << "Listening on port "
152 << QString::number(listen_port_)
153 << ".";
154 auto portLabel = new QLabel(listen_message);
155 layout->addWidget(portLabel);
156
157 std::cerr << "Ready to listen on: " << listen_port_ << std::endl;
158 }
159
getRandomExID()160 static int getRandomExID()
161 {
162 std::mt19937 rng;
163 rng.seed(std::random_device()());
164 std::uniform_int_distribution<std::mt19937::result_type> dist(100);
165
166 return dist(rng);
167 }
168
onExecutionDone(Execution * e)169 void Conductor::onExecutionDone(Execution *e)
170 {
171 e->tree().setDone();
172
173 if (options_.save_search_path != "")
174 {
175 print("saving search to: {}", options_.save_search_path);
176 saveSearch(e, options_.save_search_path.c_str());
177 QApplication::quit();
178 }
179
180 if (options_.save_execution_db != "")
181 {
182 print("saving execution to db: {}", options_.save_execution_db);
183 db_handler::save_execution(e, options_.save_execution_db.c_str());
184
185 if(options_.save_pixel_tree_path != "") {
186 print("Saving pixel tree to file: {}", options_.save_pixel_tree_path);
187 savePixelTree(e, options_.save_pixel_tree_path.c_str(), options_.pixel_tree_compression);
188 }
189
190 QApplication::quit();
191 }
192 }
193
getNextExecId() const194 int Conductor::getNextExecId() const
195 {
196 int eid = getRandomExID();
197 while (executions_.find(eid) != executions_.end())
198 {
199 eid = getRandomExID();
200 }
201 return eid;
202 }
203
setMetaData(int exec_id,const std::string & group_name,const std::string & exec_name,std::shared_ptr<NameMap> nm)204 void Conductor::setMetaData(int exec_id, const std::string &group_name,
205 const std::string &exec_name,
206 std::shared_ptr<NameMap> nm)
207 {
208 exec_meta_.insert({exec_id, {group_name, exec_name, nm}});
209
210 qDebug() << "exec_id:" << exec_id;
211 qDebug() << "gr_name:" << group_name.c_str();
212 qDebug() << "ex_name:" << exec_name.c_str();
213 }
214
getListenPort() const215 int Conductor::getListenPort() const
216 {
217 return static_cast<int>(listen_port_);
218 }
219
220 Conductor::~Conductor() = default;
221
handleStart(ReceiverThread * receiver,const std::string & ex_name,int ex_id,bool restarts)222 void Conductor::handleStart(ReceiverThread *receiver, const std::string &ex_name, int ex_id, bool restarts)
223 {
224
225 auto res = executions_.find(ex_id);
226
227 if (res == executions_.end() || ex_id == 0)
228 {
229
230 /// Note: metadata from MiniZinc IDE overrides that provided by the solver
231 std::string ex_name_used = ex_name;
232
233 const bool ide_used = (exec_meta_.find(ex_id) != exec_meta_.end());
234
235 if (ide_used)
236 {
237 print("already know metadata for this ex_id!");
238 ex_name_used = exec_meta_[ex_id].ex_name;
239 }
240
241 /// needs a new execution
242 auto ex = addNewExecution(ex_name_used, ex_id, restarts);
243
244 /// construct a name map
245 if (ide_used)
246 {
247 ex->setNameMap(exec_meta_[ex_id].name_map);
248 print("using name map for {}", ex_id);
249 }
250 else if (options_.paths != "" && options_.mzn != "")
251 {
252 auto nm = std::make_shared<NameMap>();
253 auto success = nm->initialize(options_.paths, options_.mzn);
254 if (success)
255 {
256 ex->setNameMap(nm);
257 }
258 }
259
260 /// The builder should only be created for a new execution
261 auto builderThread = new QThread();
262 auto builder = new TreeBuilder(*ex);
263
264 builders_[ex_id] = builder;
265 builder->moveToThread(builderThread);
266
267 // onExecutionDone must be called on the same thread as the conductor
268 connect(builder, &TreeBuilder::buildingDone, this, [this, ex]() {
269 onExecutionDone(ex);
270 });
271
272 /// is this the right time to delete the builder thread?
273 connect(builderThread, &QThread::finished, builderThread, &QObject::deleteLater);
274
275 builderThread->start();
276 }
277
278 /// obtain the builder aready assigned to this execution
279 ///(either just now or by another connection)
280 auto builder = builders_[ex_id];
281
282 connect(receiver, &ReceiverThread::newNode,
283 builder, &TreeBuilder::handleNode);
284
285 connect(receiver, &ReceiverThread::doneReceiving,
286 builder, &TreeBuilder::finishBuilding);
287 }
288
addNewExecution(std::shared_ptr<Execution> ex)289 int Conductor::addNewExecution(std::shared_ptr<Execution> ex)
290 {
291
292 auto ex_id = getRandomExID();
293
294 executions_[ex_id] = ex;
295 execution_list_->addExecution(*ex);
296
297 return ex_id;
298 }
299
addNewExecution(const std::string & ex_name,int ex_id,bool restarts)300 Execution *Conductor::addNewExecution(const std::string &ex_name, int ex_id, bool restarts)
301 {
302
303 if (ex_id == 0)
304 {
305 ex_id = getRandomExID();
306 }
307
308 auto ex = std::make_shared<Execution>(ex_name, ex_id, restarts);
309
310 print("EXECUTION_ID: {}", ex_id);
311
312 executions_[ex_id] = ex;
313 execution_list_->addExecution(*ex);
314
315 const bool auto_show = true;
316 if (auto_show && options_.save_search_path == "" && options_.save_execution_db == "")
317 {
318 showTraditionalView(ex.get());
319 }
320
321 return ex.get();
322 }
323
getExecutionWindow(Execution * e)324 ExecutionWindow &Conductor::getExecutionWindow(Execution *e)
325 {
326 auto maybe_view = execution_windows_.find(e);
327
328 /// create new one if doesn't already exist
329 if (maybe_view == execution_windows_.end())
330 {
331 execution_windows_[e] = new ExecutionWindow(*e, this);
332
333 const auto ex_window = execution_windows_[e];
334
335 connect(ex_window, &ExecutionWindow::needToSaveSearch, [this, e]() {
336 saveSearch(e);
337 });
338
339 connect(ex_window, &ExecutionWindow::nogoodsClicked, [this, e](std::vector<NodeID> ns) {
340 emit computeHeatMap(e->id(), ns);
341 });
342 }
343
344 return *execution_windows_.at(e);
345 }
346
mergeTrees(Execution * e1,Execution * e2)347 void Conductor::mergeTrees(Execution *e1, Execution *e2)
348 {
349
350 /// create new tree
351
352 // QProgressDialog dialog;
353
354 auto tree = std::make_shared<tree::NodeTree>();
355 auto result = std::make_shared<analysis::MergeResult>();
356
357 /// mapping from merged ids to original ids
358 auto orig_locs = std::make_shared<std::vector<analysis::OriginalLoc>>();
359
360 /// Note: TreeMerger will delete itself when finished
361 auto merger = new analysis::TreeMerger(*e1, *e2, tree, result, orig_locs);
362
363 connect(merger, &analysis::TreeMerger::finished, this,
364 [this, e1, e2, tree, result]() {
365 auto window = new analysis::MergeWindow(*e1, *e2, tree, result, this);
366 emit showMergeWindow(*window);
367 window->show();
368 });
369
370 merger->start();
371 }
372
runNogoodAnalysis(Execution * e1,Execution * e2)373 void Conductor::runNogoodAnalysis(Execution *e1, Execution *e2)
374 {
375
376 auto tree = std::make_shared<tree::NodeTree>();
377 auto result = std::make_shared<analysis::MergeResult>();
378
379 auto orig_locs = std::make_shared<std::vector<analysis::OriginalLoc>>();
380
381 /// Note: TreeMerger will delete itself when finished
382 auto merger = new analysis::TreeMerger(*e1, *e2, tree, result, orig_locs);
383
384 connect(merger, &analysis::TreeMerger::finished, this,
385 [this, e1, e2, tree, result]() {
386 auto window = new analysis::MergeWindow(*e1, *e2, tree, result, this);
387 emit showMergeWindow(*window);
388 window->show();
389 window->runNogoodAnalysis();
390 });
391
392 merger->start();
393 }
394
savePixelTree(Execution * e,const char * path,int compression_factor) const395 void Conductor::savePixelTree(Execution *e, const char* path, int compression_factor) const {
396 const auto &nt = e->tree();
397 pixel_view::PtCanvas pc(nt);
398 auto pi = pc.get_pimage();
399 pc.setCompression(compression_factor);
400 int width = pi->pixel_size()*pc.totalSlices();
401 int height = pi->pixel_size()*nt.node_stats().maxDepth();
402 pi->resize({width,height});
403 pc.redrawAll(true);
404 pi->raw_image().save(path);
405 }
406
saveSearch(Execution * e,const char * path) const407 void Conductor::saveSearch(Execution *e, const char *path) const
408 {
409
410 const auto &nt = e->tree();
411
412 const auto order = utils::pre_order(nt);
413
414 QFile file(path);
415 if (!file.open(QFile::WriteOnly | QFile::Truncate))
416 {
417 print("Error: could not open \"{}\" to save search", path);
418 return;
419 }
420
421 QTextStream file_stream(&file);
422
423 for (auto nid : order)
424 {
425
426 /// Making sure undefined/skipped nodes are not logged
427 {
428 const auto status = nt.getStatus(nid);
429 if (status == tree::NodeStatus::SKIPPED || status == tree::NodeStatus::UNDETERMINED)
430 continue;
431 }
432
433 // Note: not every child is logged (SKIPPED and UNDET are not)
434 int kids_logged = 0;
435
436 /// Note: this temporary stream is used so that children can be
437 /// traversed first, counted, but logged after their parent
438 std::stringstream children_stream;
439
440 const auto kids = nt.childrenCount(nid);
441
442 for (auto alt = 0; alt < kids; alt++)
443 {
444
445 const auto kid = nt.getChild(nid, alt);
446
447 /// Making sure undefined/skipped children are not logged
448 const auto status = nt.getStatus(kid);
449 if (status == tree::NodeStatus::SKIPPED || status == tree::NodeStatus::UNDETERMINED)
450 continue;
451
452 ++kids_logged;
453 /// TODO: use original names in labels
454 const auto label = nt.getLabel(kid);
455
456 children_stream << " " << kid << " " << label;
457 }
458
459 file_stream << nid << " " << kids_logged;
460
461 /// Unexplored node on the left branch (search timed out)
462 if ((kids == 0) && (nt.getStatus(nid) == tree::NodeStatus::BRANCH))
463 {
464 file_stream << " stop";
465 }
466
467 file_stream << children_stream.str().c_str() << '\n';
468 }
469 }
470
saveSearch(Execution * e) const471 void Conductor::saveSearch(Execution *e) const
472 {
473
474 const auto file_path = QFileDialog::getSaveFileName(nullptr, "Save Search To a File").toStdString();
475
476 saveSearch(e, file_path.c_str());
477 }
478
saveExecution(Execution * e)479 void Conductor::saveExecution(Execution *e)
480 {
481
482 const auto file_path = QFileDialog::getSaveFileName(nullptr, "Save Execution To a File").toStdString();
483
484 db_handler::save_execution(e, file_path.c_str());
485
486 qDebug() << "execution saved";
487 }
488
readSettings()489 void Conductor::readSettings()
490 {
491
492 QFile file("settings.json");
493
494 if (!file.exists())
495 {
496 qDebug() << "settings.json not found";
497 return;
498 }
499
500 file.open(QIODevice::ReadWrite | QIODevice::Text);
501
502 auto data = file.readAll();
503
504 auto json_doc = QJsonDocument::fromJson(data);
505
506 if (json_doc.isEmpty())
507 {
508 qDebug() << "settings.json is empty";
509 return;
510 }
511
512 if (json_doc.isObject())
513 {
514
515 auto json_obj = json_doc.object();
516
517 settings_.receiver_delay = json_obj["receiver_delay"].toInt();
518 }
519
520 qDebug() << "settings read";
521 }
522
getHeatMapUrl(const NameMap & nm,const std::unordered_map<int,int> & con_counts,int max_count)523 static std::string getHeatMapUrl(const NameMap &nm,
524 const std::unordered_map<int, int> &con_counts,
525 int max_count)
526 {
527 /// get heat map
528
529 std::unordered_map<std::string, int> loc_intensity;
530
531 for (const auto& it : con_counts)
532 {
533 const auto con = std::to_string(it.first);
534 const auto count = it.second;
535
536 const auto &path = nm.getPath(con);
537
538 const auto path_head_elements = utils::getPathPair(path, true).model_level;
539
540 if (path_head_elements.empty())
541 continue;
542
543 const auto path_head = path_head_elements.back();
544
545 const auto location_etc = utils::split(path_head, utils::minor_sep);
546
547 // print("path: {}", path);
548
549 // for (auto e : new_loc) {
550 // print("element: {}", e);
551 // }
552
553 /// path plus four ints
554 if (location_etc.size() < 5)
555 continue;
556
557 std::vector<std::string> new_loc(location_etc.begin(), location_etc.begin() + 5);
558
559 int val = static_cast<int>(std::floor(count * (255.0 / max_count)));
560
561 const auto loc_str = utils::join(new_loc, utils::minor_sep);
562
563 loc_intensity[loc_str] = std::max(loc_intensity[loc_str], val);
564 }
565
566 /// highlight url
567 std::stringstream url;
568 url << "highlight://?";
569
570 for (auto it : loc_intensity)
571 url << it.first << utils::minor_sep << it.second << ";";
572
573 return url.str();
574 }
575
computeHeatMap(ExecID eid,std::vector<NodeID> ns)576 void Conductor::computeHeatMap(ExecID eid, std::vector<NodeID> ns)
577 {
578
579 auto it = exec_meta_.find(eid);
580
581 if (it == exec_meta_.end())
582 {
583 print("No metadata for eid {} (ExecMeta)", eid);
584 return;
585 }
586
587 /// check if namemap is there
588 const auto nm = it->second.name_map;
589
590 if (!nm)
591 {
592 print("no name map for eid: {}", eid);
593 return;
594 }
595
596 const auto exec = executions_.at(eid);
597
598 const auto &sd = exec->solver_data();
599
600 std::unordered_map<int, int> con_counts;
601
602 for (const auto& n : ns)
603 {
604 const auto *cs = sd.getContribConstraints(n);
605
606 if (!cs)
607 continue;
608
609 for (int con_id : *cs)
610 {
611 con_counts[con_id]++;
612 }
613 }
614
615 int max_count = 0;
616 for (const auto& p : con_counts)
617 {
618 if (p.second > max_count)
619 {
620 max_count = p.second;
621 }
622 }
623
624 const auto url = getHeatMapUrl(*nm, con_counts, max_count);
625
626 if (url.empty())
627 return;
628
629 std::stringstream label;
630
631 for (const auto n : ns)
632 {
633 label << std::to_string(n) << ' ';
634 }
635
636 emit showNogood(url.c_str(), label.str().c_str(), false);
637 }
638
639 } // namespace cpprofiler
640
641 namespace cpprofiler
642 {
643
showTraditionalView(Execution * e)644 void Conductor::showTraditionalView(Execution *e)
645 {
646
647 auto &window = getExecutionWindow(e);
648
649 emit showExecutionWindow(window);
650 window.show();
651 }
652 } // namespace cpprofiler
653