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