1 /*
2 This file is part of KCachegrind.
3
4 SPDX-FileCopyrightText: 2007-2016 Josef Weidendorfer <Josef.Weidendorfer@gmx.de>
5
6 SPDX-License-Identifier: GPL-2.0-only
7 */
8
9 /*
10 * Callgraph View
11 */
12
13 #include "callgraphview.h"
14
15 #include <stdlib.h>
16 #include <math.h>
17
18 #include <QApplication>
19 #include <QDebug>
20 #include <QDesktopServices>
21 #include <QFile>
22 #include <QFileDialog>
23 #include <QTemporaryFile>
24 #include <QTextStream>
25 #include <QTransform>
26 #include <QPair>
27 #include <QPainter>
28 #include <QStyle>
29 #include <QScrollBar>
30 #include <QResizeEvent>
31 #include <QMouseEvent>
32 #include <QFocusEvent>
33 #include <QKeyEvent>
34 #include <QStyleOptionGraphicsItem>
35 #include <QContextMenuEvent>
36 #include <QList>
37 #include <QPixmap>
38 #include <QScreen>
39 #include <QProcess>
40 #include <QMenu>
41
42
43 #include "config.h"
44 #include "globalguiconfig.h"
45 #include "listutils.h"
46
47
48 #define DEBUG_GRAPH 0
49
50 // CallGraphView defaults
51
52 #define DEFAULT_FUNCLIMIT .05
53 #define DEFAULT_CALLLIMIT 1.
54 #define DEFAULT_MAXCALLER 2
55 #define DEFAULT_MAXCALLEE -1
56 #define DEFAULT_SHOWSKIPPED false
57 #define DEFAULT_EXPANDCYCLES false
58 #define DEFAULT_CLUSTERGROUPS false
59 #define DEFAULT_DETAILLEVEL 1
60 #define DEFAULT_LAYOUT GraphOptions::TopDown
61 #define DEFAULT_ZOOMPOS Auto
62
63
64 // LessThen functors as helpers for sorting of graph edges
65 // for keyboard navigation. Sorting is done according to
66 // the angle at which a edge spline goes out or in of a function.
67
68 // Sort angles of outgoing edges (edge seen as attached to the caller)
69 class CallerGraphEdgeLessThan
70 {
71 public:
operator ()(const GraphEdge * ge1,const GraphEdge * ge2) const72 bool operator()(const GraphEdge* ge1, const GraphEdge* ge2) const
73 {
74 const CanvasEdge* ce1 = ge1->canvasEdge();
75 const CanvasEdge* ce2 = ge2->canvasEdge();
76
77 // sort invisible edges (ie. without matching CanvasEdge) in front
78 if (!ce1 && !ce2) {
79 // strict ordering required for std::sort
80 return (ge1 < ge2);
81 }
82 if (!ce1) return true;
83 if (!ce2) return false;
84
85 QPolygon p1 = ce1->controlPoints();
86 QPolygon p2 = ce2->controlPoints();
87 QPoint d1 = p1.point(1) - p1.point(0);
88 QPoint d2 = p2.point(1) - p2.point(0);
89 double angle1 = atan2(double(d1.y()), double(d1.x()));
90 double angle2 = atan2(double(d2.y()), double(d2.x()));
91
92 return (angle1 < angle2);
93 }
94 };
95
96 // Sort angles of ingoing edges (edge seen as attached to the callee)
97 class CalleeGraphEdgeLessThan
98 {
99 public:
operator ()(const GraphEdge * ge1,const GraphEdge * ge2) const100 bool operator()(const GraphEdge* ge1, const GraphEdge* ge2) const
101 {
102 const CanvasEdge* ce1 = ge1->canvasEdge();
103 const CanvasEdge* ce2 = ge2->canvasEdge();
104
105 // sort invisible edges (ie. without matching CanvasEdge) in front
106 if (!ce1) return true;
107 if (!ce2) return false;
108
109 QPolygon p1 = ce1->controlPoints();
110 QPolygon p2 = ce2->controlPoints();
111 QPoint d1 = p1.point(p1.count()-2) - p1.point(p1.count()-1);
112 QPoint d2 = p2.point(p2.count()-2) - p2.point(p2.count()-1);
113 double angle1 = atan2(double(d1.y()), double(d1.x()));
114 double angle2 = atan2(double(d2.y()), double(d2.x()));
115
116 // for ingoing edges sort according to descending angles
117 return (angle2 < angle1);
118 }
119 };
120
121
122
123 //
124 // GraphNode
125 //
126
GraphNode()127 GraphNode::GraphNode()
128 {
129 _f=nullptr;
130 self = incl = 0;
131 _cn = nullptr;
132
133 _visible = false;
134 _lastCallerIndex = _lastCalleeIndex = -1;
135
136 _lastFromCaller = true;
137 }
138
clearEdges()139 void GraphNode::clearEdges()
140 {
141 callees.clear();
142 callers.clear();
143 }
144
145 CallerGraphEdgeLessThan callerGraphEdgeLessThan;
146 CalleeGraphEdgeLessThan calleeGraphEdgeLessThan;
147
sortEdges()148 void GraphNode::sortEdges()
149 {
150 std::sort(callers.begin(), callers.end(), callerGraphEdgeLessThan);
151 std::sort(callees.begin(), callees.end(), calleeGraphEdgeLessThan);
152 }
153
addCallee(GraphEdge * e)154 void GraphNode::addCallee(GraphEdge* e)
155 {
156 if (e)
157 callees.append(e);
158 }
159
addCaller(GraphEdge * e)160 void GraphNode::addCaller(GraphEdge* e)
161 {
162 if (e)
163 callers.append(e);
164 }
165
addUniqueCallee(GraphEdge * e)166 void GraphNode::addUniqueCallee(GraphEdge* e)
167 {
168 if (e && (callees.count(e) == 0))
169 callees.append(e);
170 }
171
addUniqueCaller(GraphEdge * e)172 void GraphNode::addUniqueCaller(GraphEdge* e)
173 {
174 if (e && (callers.count(e) == 0))
175 callers.append(e);
176 }
177
removeEdge(GraphEdge * e)178 void GraphNode::removeEdge(GraphEdge* e)
179 {
180 callers.removeAll(e);
181 callees.removeAll(e);
182 }
183
calleeCostSum()184 double GraphNode::calleeCostSum()
185 {
186 double sum = 0.0;
187
188 foreach(GraphEdge* e, callees)
189 sum += e->cost;
190
191 return sum;
192 }
193
calleeCountSum()194 double GraphNode::calleeCountSum()
195 {
196 double sum = 0.0;
197
198 foreach(GraphEdge* e, callees)
199 sum += e->count;
200
201 return sum;
202 }
203
callerCostSum()204 double GraphNode::callerCostSum()
205 {
206 double sum = 0.0;
207
208 foreach(GraphEdge* e, callers)
209 sum += e->cost;
210
211 return sum;
212 }
213
callerCountSum()214 double GraphNode::callerCountSum()
215 {
216 double sum = 0.0;
217
218 foreach(GraphEdge* e, callers)
219 sum += e->count;
220
221 return sum;
222 }
223
224
visibleCaller()225 TraceCall* GraphNode::visibleCaller()
226 {
227 if (0)
228 qDebug("GraphNode::visibleCaller %s: last %d, count %d",
229 qPrintable(_f->prettyName()), _lastCallerIndex, (int) callers.count());
230
231 // can not use at(): index can be -1 (out of bounds), result is 0 then
232 GraphEdge* e = callers.value(_lastCallerIndex);
233 if (e && !e->isVisible())
234 e = nullptr;
235 if (!e) {
236 double maxCost = 0.0;
237 GraphEdge* maxEdge = nullptr;
238 for(int i = 0; i<callers.size(); i++) {
239 e = callers[i];
240 if (e->isVisible() && (e->cost > maxCost)) {
241 maxCost = e->cost;
242 maxEdge = e;
243 _lastCallerIndex = i;
244 }
245 }
246 e = maxEdge;
247 }
248 return e ? e->call() : nullptr;
249 }
250
visibleCallee()251 TraceCall* GraphNode::visibleCallee()
252 {
253 if (0)
254 qDebug("GraphNode::visibleCallee %s: last %d, count %d",
255 qPrintable(_f->prettyName()), _lastCalleeIndex, (int) callees.count());
256
257 GraphEdge* e = callees.value(_lastCalleeIndex);
258 if (e && !e->isVisible())
259 e = nullptr;
260
261 if (!e) {
262 double maxCost = 0.0;
263 GraphEdge* maxEdge = nullptr;
264 for(int i = 0; i<callees.size(); i++) {
265 e = callees[i];
266 if (e->isVisible() && (e->cost > maxCost)) {
267 maxCost = e->cost;
268 maxEdge = e;
269 _lastCalleeIndex = i;
270 }
271 }
272 e = maxEdge;
273 }
274 return e ? e->call() : nullptr;
275 }
276
setCallee(GraphEdge * e)277 void GraphNode::setCallee(GraphEdge* e)
278 {
279 _lastCalleeIndex = callees.indexOf(e);
280 _lastFromCaller = false;
281 }
282
setCaller(GraphEdge * e)283 void GraphNode::setCaller(GraphEdge* e)
284 {
285 _lastCallerIndex = callers.indexOf(e);
286 _lastFromCaller = true;
287 }
288
nextVisible()289 TraceFunction* GraphNode::nextVisible()
290 {
291 TraceCall* c;
292
293 if (_lastFromCaller) {
294 c = nextVisibleCaller();
295 if (c)
296 return c->called(true);
297 c = nextVisibleCallee();
298 if (c)
299 return c->caller(true);
300 } else {
301 c = nextVisibleCallee();
302 if (c)
303 return c->caller(true);
304 c = nextVisibleCaller();
305 if (c)
306 return c->called(true);
307 }
308 return nullptr;
309 }
310
priorVisible()311 TraceFunction* GraphNode::priorVisible()
312 {
313 TraceCall* c;
314
315 if (_lastFromCaller) {
316 c = priorVisibleCaller();
317 if (c)
318 return c->called(true);
319 c = priorVisibleCallee();
320 if (c)
321 return c->caller(true);
322 } else {
323 c = priorVisibleCallee();
324 if (c)
325 return c->caller(true);
326 c = priorVisibleCaller();
327 if (c)
328 return c->called(true);
329 }
330 return nullptr;
331 }
332
nextVisibleCaller(GraphEdge * e)333 TraceCall* GraphNode::nextVisibleCaller(GraphEdge* e)
334 {
335 int idx = e ? callers.indexOf(e) : _lastCallerIndex;
336 idx++;
337 while(idx < callers.size()) {
338 if (callers[idx]->isVisible()) {
339 _lastCallerIndex = idx;
340 return callers[idx]->call();
341 }
342 idx++;
343 }
344 return nullptr;
345 }
346
nextVisibleCallee(GraphEdge * e)347 TraceCall* GraphNode::nextVisibleCallee(GraphEdge* e)
348 {
349 int idx = e ? callees.indexOf(e) : _lastCalleeIndex;
350 idx++;
351 while(idx < callees.size()) {
352 if (callees[idx]->isVisible()) {
353 _lastCalleeIndex = idx;
354 return callees[idx]->call();
355 }
356 idx++;
357 }
358 return nullptr;
359 }
360
priorVisibleCaller(GraphEdge * e)361 TraceCall* GraphNode::priorVisibleCaller(GraphEdge* e)
362 {
363 int idx = e ? callers.indexOf(e) : _lastCallerIndex;
364
365 idx = (idx<0) ? callers.size()-1 : idx-1;
366 while(idx >= 0) {
367 if (callers[idx]->isVisible()) {
368 _lastCallerIndex = idx;
369 return callers[idx]->call();
370 }
371 idx--;
372 }
373 return nullptr;
374 }
375
priorVisibleCallee(GraphEdge * e)376 TraceCall* GraphNode::priorVisibleCallee(GraphEdge* e)
377 {
378 int idx = e ? callees.indexOf(e) : _lastCalleeIndex;
379
380 idx = (idx<0) ? callees.size()-1 : idx-1;
381 while(idx >= 0) {
382 if (callees[idx]->isVisible()) {
383 _lastCalleeIndex = idx;
384 return callees[idx]->call();
385 }
386 idx--;
387 }
388 return nullptr;
389 }
390
391
392 //
393 // GraphEdge
394 //
395
GraphEdge()396 GraphEdge::GraphEdge()
397 {
398 _c=nullptr;
399 _from = _to = nullptr;
400 _fromNode = _toNode = nullptr;
401 cost = count = 0;
402 _ce = nullptr;
403
404 _visible = false;
405 _lastFromCaller = true;
406 }
407
prettyName()408 QString GraphEdge::prettyName()
409 {
410 if (_c)
411 return _c->prettyName();
412
413 if (_from)
414 return QObject::tr("Call(s) from %1").arg(_from->prettyName());
415
416 if (_to)
417 return QObject::tr("Call(s) to %1").arg(_to->prettyName());
418
419 return QObject::tr("(unknown call)");
420 }
421
visibleCaller()422 TraceFunction* GraphEdge::visibleCaller()
423 {
424 if (_from) {
425 _lastFromCaller = true;
426 if (_fromNode)
427 _fromNode->setCallee(this);
428 return _from;
429 }
430 return nullptr;
431 }
432
visibleCallee()433 TraceFunction* GraphEdge::visibleCallee()
434 {
435 if (_to) {
436 _lastFromCaller = false;
437 if (_toNode)
438 _toNode->setCaller(this);
439 return _to;
440 }
441 return nullptr;
442 }
443
nextVisible()444 TraceCall* GraphEdge::nextVisible()
445 {
446 TraceCall* res = nullptr;
447
448 if (_lastFromCaller && _fromNode) {
449 res = _fromNode->nextVisibleCallee(this);
450 if (!res && _toNode)
451 res = _toNode->nextVisibleCaller(this);
452 } else if (_toNode) {
453 res = _toNode->nextVisibleCaller(this);
454 if (!res && _fromNode)
455 res = _fromNode->nextVisibleCallee(this);
456 }
457 return res;
458 }
459
priorVisible()460 TraceCall* GraphEdge::priorVisible()
461 {
462 TraceCall* res = nullptr;
463
464 if (_lastFromCaller && _fromNode) {
465 res = _fromNode->priorVisibleCallee(this);
466 if (!res && _toNode)
467 res = _toNode->priorVisibleCaller(this);
468 } else if (_toNode) {
469 res = _toNode->priorVisibleCaller(this);
470 if (!res && _fromNode)
471 res = _fromNode->priorVisibleCallee(this);
472 }
473 return res;
474 }
475
476
477
478 //
479 // GraphOptions
480 //
481
layoutString(Layout l)482 QString GraphOptions::layoutString(Layout l)
483 {
484 if (l == Circular)
485 return QStringLiteral("Circular");
486 if (l == LeftRight)
487 return QStringLiteral("LeftRight");
488 return QStringLiteral("TopDown");
489 }
490
layout(QString s)491 GraphOptions::Layout GraphOptions::layout(QString s)
492 {
493 if (s == QLatin1String("Circular"))
494 return Circular;
495 if (s == QLatin1String("LeftRight"))
496 return LeftRight;
497 return TopDown;
498 }
499
500
501 //
502 // StorableGraphOptions
503 //
504
StorableGraphOptions()505 StorableGraphOptions::StorableGraphOptions()
506 {
507 // default options
508 _funcLimit = DEFAULT_FUNCLIMIT;
509 _callLimit = DEFAULT_CALLLIMIT;
510 _maxCallerDepth = DEFAULT_MAXCALLER;
511 _maxCalleeDepth = DEFAULT_MAXCALLEE;
512 _showSkipped = DEFAULT_SHOWSKIPPED;
513 _expandCycles = DEFAULT_EXPANDCYCLES;
514 _detailLevel = DEFAULT_DETAILLEVEL;
515 _layout = DEFAULT_LAYOUT;
516 }
517
518
519
520
521 //
522 // GraphExporter
523 //
524
GraphExporter()525 GraphExporter::GraphExporter()
526 {
527 _go = this;
528 _tmpFile = nullptr;
529 _item = nullptr;
530 reset(nullptr, nullptr, nullptr, ProfileContext::InvalidType, QString());
531 }
532
GraphExporter(TraceData * d,TraceFunction * f,EventType * ct,ProfileContext::Type gt,QString filename)533 GraphExporter::GraphExporter(TraceData* d, TraceFunction* f,
534 EventType* ct, ProfileContext::Type gt,
535 QString filename)
536 {
537 _go = this;
538 _tmpFile = nullptr;
539 _item = nullptr;
540 reset(d, f, ct, gt, filename);
541 }
542
~GraphExporter()543 GraphExporter::~GraphExporter()
544 {
545 if (_item && _tmpFile) {
546 #if DEBUG_GRAPH
547 _tmpFile->setAutoRemove(true);
548 #endif
549 delete _tmpFile;
550 }
551 }
552
553
reset(TraceData *,CostItem * i,EventType * ct,ProfileContext::Type gt,QString filename)554 void GraphExporter::reset(TraceData*, CostItem* i, EventType* ct,
555 ProfileContext::Type gt, QString filename)
556 {
557 _graphCreated = false;
558 _nodeMap.clear();
559 _edgeMap.clear();
560
561 if (_item && _tmpFile) {
562 _tmpFile->setAutoRemove(true);
563 delete _tmpFile;
564 }
565
566 if (i) {
567 switch (i->type()) {
568 case ProfileContext::Function:
569 case ProfileContext::FunctionCycle:
570 case ProfileContext::Call:
571 break;
572 default:
573 i = nullptr;
574 }
575 }
576
577 _item = i;
578 _eventType = ct;
579 _groupType = gt;
580 if (!i)
581 return;
582
583 if (filename.isEmpty()) {
584 _tmpFile = new QTemporaryFile();
585 //_tmpFile->setSuffix(".dot");
586 _tmpFile->setAutoRemove(false);
587 _tmpFile->open();
588 _dotName = _tmpFile->fileName();
589 _useBox = true;
590 } else {
591 _tmpFile = nullptr;
592 _dotName = filename;
593 _useBox = false;
594 }
595 }
596
597
598
setGraphOptions(GraphOptions * go)599 void GraphExporter::setGraphOptions(GraphOptions* go)
600 {
601 if (go == nullptr)
602 go = this;
603 _go = go;
604 }
605
createGraph()606 void GraphExporter::createGraph()
607 {
608 if (!_item)
609 return;
610 if (_graphCreated)
611 return;
612 _graphCreated = true;
613
614 if ((_item->type() == ProfileContext::Function) ||(_item->type()
615 == ProfileContext::FunctionCycle)) {
616 TraceFunction* f = (TraceFunction*) _item;
617
618 double incl = f->inclusive()->subCost(_eventType);
619 _realFuncLimit = incl * _go->funcLimit();
620 _realCallLimit = _realFuncLimit * _go->callLimit();
621
622 buildGraph(f, 0, true, 1.0); // down to callees
623
624 // set costs of function back to 0, as it will be added again
625 GraphNode& n = _nodeMap[f];
626 n.self = n.incl = 0.0;
627
628 buildGraph(f, 0, false, 1.0); // up to callers
629 } else {
630 TraceCall* c = (TraceCall*) _item;
631
632 double incl = c->subCost(_eventType);
633 _realFuncLimit = incl * _go->funcLimit();
634 _realCallLimit = _realFuncLimit * _go->callLimit();
635
636 // create edge
637 TraceFunction *caller, *called;
638 caller = c->caller(false);
639 called = c->called(false);
640 QPair<TraceFunction*,TraceFunction*> p(caller, called);
641 GraphEdge& e = _edgeMap[p];
642 e.setCall(c);
643 e.setCaller(p.first);
644 e.setCallee(p.second);
645 e.cost = c->subCost(_eventType);
646 e.count = c->callCount();
647
648 SubCost s = called->inclusive()->subCost(_eventType);
649 buildGraph(called, 0, true, e.cost / s); // down to callees
650 s = caller->inclusive()->subCost(_eventType);
651 buildGraph(caller, 0, false, e.cost / s); // up to callers
652 }
653 }
654
655
writeDot(QIODevice * device)656 bool GraphExporter::writeDot(QIODevice* device)
657 {
658 if (!_item)
659 return false;
660
661 QFile* file = nullptr;
662 QTextStream* stream = nullptr;
663
664 if (device)
665 stream = new QTextStream(device);
666 else {
667 if (_tmpFile)
668 stream = new QTextStream(_tmpFile);
669 else {
670 file = new QFile(_dotName);
671 if ( !file->open(QIODevice::WriteOnly ) ) {
672 qDebug() << "Can not write dot file '"<< _dotName << "'";
673 delete file;
674 return false;
675 }
676 stream = new QTextStream(file);
677 }
678 }
679
680 if (!_graphCreated)
681 createGraph();
682
683 /* Generate dot format...
684 * When used for the CallGraphView (in contrast to "Export Callgraph..."),
685 * the labels are only dummy placeholders to reserve space for our own
686 * drawings.
687 */
688
689 *stream << "digraph \"callgraph\" {\n";
690
691 if (_go->layout() == LeftRight) {
692 *stream << QStringLiteral(" rankdir=LR;\n");
693 } else if (_go->layout() == Circular) {
694 TraceFunction *f = nullptr;
695 switch (_item->type()) {
696 case ProfileContext::Function:
697 case ProfileContext::FunctionCycle:
698 f = (TraceFunction*) _item;
699 break;
700 case ProfileContext::Call:
701 f = ((TraceCall*)_item)->caller(true);
702 break;
703 default:
704 break;
705 }
706 if (f)
707 *stream << QStringLiteral(" center=F%1;\n").arg((qptrdiff)f, 0, 16);
708 *stream << QStringLiteral(" overlap=false;\n splines=true;\n");
709 }
710
711 // for clustering
712 QMap<TraceCostItem*,QList<GraphNode*> > nLists;
713
714 GraphNodeMap::Iterator nit;
715 for (nit = _nodeMap.begin(); nit != _nodeMap.end(); ++nit ) {
716 GraphNode& n = *nit;
717
718 if (n.incl <= _realFuncLimit)
719 continue;
720
721 // for clustering: get cost item group of function
722 TraceCostItem* g;
723 TraceFunction* f = n.function();
724 switch (_groupType) {
725 case ProfileContext::Object:
726 g = f->object();
727 break;
728 case ProfileContext::Class:
729 g = f->cls();
730 break;
731 case ProfileContext::File:
732 g = f->file();
733 break;
734 case ProfileContext::FunctionCycle:
735 g = f->cycle();
736 break;
737 default:
738 g = nullptr;
739 break;
740 }
741 nLists[g].append(&n);
742 }
743
744 QMap<TraceCostItem*,QList<GraphNode*> >::Iterator lit;
745 int cluster = 0;
746 for (lit = nLists.begin(); lit != nLists.end(); ++lit, cluster++) {
747 QList<GraphNode*>& l = lit.value();
748 TraceCostItem* i = lit.key();
749
750 if (_go->clusterGroups() && i) {
751 QString iabr = GlobalConfig::shortenSymbol(i->prettyName());
752 // escape quotation marks in symbols to avoid invalid dot syntax
753 iabr.replace("\"", "\\\"");
754 *stream << QStringLiteral("subgraph \"cluster%1\" { label=\"%2\";\n")
755 .arg(cluster).arg(iabr);
756 }
757
758 foreach(GraphNode* np, l) {
759 TraceFunction* f = np->function();
760
761 QString abr = GlobalConfig::shortenSymbol(f->prettyName());
762 // escape quotation marks to avoid invalid dot syntax
763 abr.replace("\"", "\\\"");
764 *stream << QStringLiteral(" F%1 [").arg((qptrdiff)f, 0, 16);
765 if (_useBox) {
766 // we want a minimal size for cost display
767 if ((int)abr.length() < 8) abr = abr + QString(8 - abr.length(),'_');
768
769 // make label 3 lines for CallGraphView
770 *stream << QStringLiteral("shape=box,label=\"** %1 **\\n**\\n%2\"];\n")
771 .arg(abr)
772 .arg(SubCost(np->incl).pretty());
773 } else
774 *stream << QStringLiteral("label=\"%1\\n%2\"];\n")
775 .arg(abr)
776 .arg(SubCost(np->incl).pretty());
777 }
778
779 if (_go->clusterGroups() && i)
780 *stream << QStringLiteral("}\n");
781 }
782
783 GraphEdgeMap::Iterator eit;
784 for (eit = _edgeMap.begin(); eit != _edgeMap.end(); ++eit ) {
785 GraphEdge& e = *eit;
786
787 if (e.cost < _realCallLimit)
788 continue;
789 if (!_go->expandCycles()) {
790 // do not show inner cycle calls
791 if (e.call()->inCycle()>0)
792 continue;
793 }
794
795 GraphNode& from = _nodeMap[e.from()];
796 GraphNode& to = _nodeMap[e.to()];
797
798 e.setCallerNode(&from);
799 e.setCalleeNode(&to);
800
801 if ((from.incl <= _realFuncLimit) ||(to.incl <= _realFuncLimit))
802 continue;
803
804 // remove dumped edges from n.callers/n.callees
805 from.removeEdge(&e);
806 to.removeEdge(&e);
807
808 *stream << QStringLiteral(" F%1 -> F%2 [weight=%3")
809 .arg((qptrdiff)e.from(), 0, 16)
810 .arg((qptrdiff)e.to(), 0, 16)
811 .arg((long)log(log(e.cost)));
812
813 if (_go->detailLevel() ==1) {
814 *stream << QStringLiteral(",label=\"%1 (%2x)\"")
815 .arg(SubCost(e.cost).pretty())
816 .arg(SubCost(e.count).pretty());
817 }
818 else if (_go->detailLevel() ==2)
819 *stream << QStringLiteral(",label=\"%3\\n%4 x\"")
820 .arg(SubCost(e.cost).pretty())
821 .arg(SubCost(e.count).pretty());
822
823 *stream << QStringLiteral("];\n");
824 }
825
826 if (_go->showSkipped()) {
827
828 // Create sum-edges for skipped edges
829 GraphEdge* e;
830 double costSum, countSum;
831 for (nit = _nodeMap.begin(); nit != _nodeMap.end(); ++nit ) {
832 GraphNode& n = *nit;
833 if (n.incl <= _realFuncLimit)
834 continue;
835
836 // add edge for all skipped callers if cost sum is high enough
837 costSum = n.callerCostSum();
838 countSum = n.callerCountSum();
839 if (costSum > _realCallLimit) {
840
841 QPair<TraceFunction*,TraceFunction*> p(nullptr, n.function());
842 e = &(_edgeMap[p]);
843 e->setCallee(p.second);
844 e->cost = costSum;
845 e->count = countSum;
846
847 *stream << QStringLiteral(" R%1 [shape=point,label=\"\"];\n")
848 .arg((qptrdiff)n.function(), 0, 16);
849 *stream << QStringLiteral(" R%1 -> F%2 [label=\"%3\\n%4 x\",weight=%5];\n")
850 .arg((qptrdiff)n.function(), 0, 16)
851 .arg((qptrdiff)n.function(), 0, 16)
852 .arg(SubCost(costSum).pretty())
853 .arg(SubCost(countSum).pretty())
854 .arg((int)log(costSum));
855 }
856
857 // add edge for all skipped callees if cost sum is high enough
858 costSum = n.calleeCostSum();
859 countSum = n.calleeCountSum();
860 if (costSum > _realCallLimit) {
861
862 QPair<TraceFunction*,TraceFunction*> p(n.function(), nullptr);
863 e = &(_edgeMap[p]);
864 e->setCaller(p.first);
865 e->cost = costSum;
866 e->count = countSum;
867
868 *stream << QStringLiteral(" S%1 [shape=point,label=\"\"];\n")
869 .arg((qptrdiff)n.function(), 0, 16);
870 *stream << QStringLiteral(" F%1 -> S%2 [label=\"%3\\n%4 x\",weight=%5];\n")
871 .arg((qptrdiff)n.function(), 0, 16)
872 .arg((qptrdiff)n.function(), 0, 16)
873 .arg(SubCost(costSum).pretty())
874 .arg(SubCost(countSum).pretty())
875 .arg((int)log(costSum));
876 }
877 }
878 }
879
880 // clear edges here completely.
881 // Visible edges are inserted again on parsing in CallGraphView::refresh
882 for (nit = _nodeMap.begin(); nit != _nodeMap.end(); ++nit ) {
883 GraphNode& n = *nit;
884 n.clearEdges();
885 }
886
887 *stream << "}\n";
888
889 if (!device) {
890 if (_tmpFile) {
891 stream->flush();
892 _tmpFile->seek(0);
893 } else {
894 file->close();
895 delete file;
896 }
897 }
898 delete stream;
899 return true;
900 }
901
savePrompt(QWidget * parent,TraceData * data,TraceFunction * function,EventType * eventType,ProfileContext::Type groupType,CallGraphView * cgv)902 bool GraphExporter::savePrompt(QWidget *parent, TraceData *data,
903 TraceFunction *function, EventType *eventType,
904 ProfileContext::Type groupType,
905 CallGraphView* cgv)
906 {
907 // More formats can be added; it's a matter of adding the filter and if
908 QFileDialog saveDialog(parent, QObject::tr("Export Graph"));
909 saveDialog.setMimeTypeFilters( {"text/vnd.graphviz", "application/pdf", "application/postscript"} );
910 saveDialog.setFileMode(QFileDialog::AnyFile);
911 saveDialog.setAcceptMode(QFileDialog::AcceptSave);
912
913 if (saveDialog.exec()) {
914 QString intendedName = saveDialog.selectedFiles().first();
915 if (intendedName.isNull() || intendedName.isEmpty())
916 return false;
917 bool wrote = false;
918 QString dotName, dotRenderType;
919 QTemporaryFile maybeTemp;
920 maybeTemp.open();
921 const QString mime = saveDialog.selectedMimeTypeFilter();
922 if (mime == "text/vnd.graphviz") {
923 dotName = intendedName;
924 dotRenderType = "";
925 }
926 else if (mime == "application/pdf") {
927 dotName = maybeTemp.fileName();
928 dotRenderType = "-Tpdf";
929 }
930 else if (mime == "application/postscript") {
931 dotName = maybeTemp.fileName();
932 dotRenderType = "-Tps";
933 }
934 GraphExporter ge(data, function, eventType, groupType, dotName);
935 if (cgv != nullptr)
936 ge.setGraphOptions(cgv);
937 wrote = ge.writeDot();
938 if (wrote && mime != "text/vnd.graphviz") {
939 QProcess proc;
940 proc.setStandardOutputFile(intendedName, QFile::Truncate);
941 proc.start("dot", { dotRenderType, dotName }, QProcess::ReadWrite);
942 proc.waitForFinished();
943 wrote = proc.exitStatus() == QProcess::NormalExit;
944
945 // Open in the default app, except for GraphViz files. The .dot
946 // association is usually for Microsoft Word templates and will
947 // open a word processor instead, which isn't what we want.
948 if (wrote) {
949 QDesktopServices::openUrl(QUrl::fromLocalFile(intendedName));
950 }
951 }
952
953 return wrote;
954 }
955
956 return false;
957 }
958
sortEdges()959 void GraphExporter::sortEdges()
960 {
961 GraphNodeMap::Iterator nit;
962 for (nit = _nodeMap.begin(); nit != _nodeMap.end(); ++nit ) {
963 GraphNode& n = *nit;
964 n.sortEdges();
965 }
966 }
967
toFunc(QString s)968 TraceFunction* GraphExporter::toFunc(QString s)
969 {
970 if (s[0] != 'F')
971 return nullptr;
972 bool ok;
973 TraceFunction* f = (TraceFunction*) s.mid(1).toULongLong(&ok, 16);
974 if (!ok)
975 return nullptr;
976
977 return f;
978 }
979
node(TraceFunction * f)980 GraphNode* GraphExporter::node(TraceFunction* f)
981 {
982 if (!f)
983 return nullptr;
984
985 GraphNodeMap::Iterator it = _nodeMap.find(f);
986 if (it == _nodeMap.end())
987 return nullptr;
988
989 return &(*it);
990 }
991
edge(TraceFunction * f1,TraceFunction * f2)992 GraphEdge* GraphExporter::edge(TraceFunction* f1, TraceFunction* f2)
993 {
994 GraphEdgeMap::Iterator it = _edgeMap.find(qMakePair(f1, f2));
995 if (it == _edgeMap.end())
996 return nullptr;
997
998 return &(*it);
999 }
1000
1001 /**
1002 * We do a DFS and do not stop on already visited nodes/edges,
1003 * but add up costs. We only stop if limits/max depth is reached.
1004 *
1005 * For a node/edge, it can happen that the first time visited the
1006 * cost will below the limit, so the search is stopped.
1007 * If on a further visit of the node/edge the limit is reached,
1008 * we use the whole node/edge cost and continue search.
1009 */
buildGraph(TraceFunction * f,int depth,bool toCallees,double factor)1010 void GraphExporter::buildGraph(TraceFunction* f, int depth, bool toCallees,
1011 double factor)
1012 {
1013 #if DEBUG_GRAPH
1014 qDebug() << "buildGraph(" << f->prettyName() << "," << d << "," << factor
1015 << ") [to " << (toCallees ? "Callees":"Callers") << "]";
1016 #endif
1017
1018 double oldIncl = 0.0;
1019 GraphNode& n = _nodeMap[f];
1020 if (n.function() == nullptr) {
1021 n.setFunction(f);
1022 } else
1023 oldIncl = n.incl;
1024
1025 double incl = f->inclusive()->subCost(_eventType) * factor;
1026 n.incl += incl;
1027 n.self += f->subCost(_eventType) * factor;
1028 if (0)
1029 qDebug(" Added Incl. %f, now %f", incl, n.incl);
1030
1031 // A negative depth limit means "unlimited"
1032 int maxDepth = toCallees ? _go->maxCalleeDepth()
1033 : _go->maxCallerDepth();
1034 // Never go beyond a depth of 100
1035 if ((maxDepth < 0) || (maxDepth>100)) maxDepth = 100;
1036 if (depth >= maxDepth) {
1037 if (0)
1038 qDebug(" Cutoff, max depth reached");
1039 return;
1040 }
1041
1042 // if we just reached the limit by summing, do a DFS
1043 // from here with full incl. cost because of previous cutoffs
1044 if ((n.incl >= _realFuncLimit) && (oldIncl < _realFuncLimit))
1045 incl = n.incl;
1046
1047 if (f->cycle()) {
1048 // for cycles members, we never stop on first visit, but always on 2nd
1049 // note: a 2nd visit never should happen, as we do not follow inner-cycle
1050 // calls
1051 if (oldIncl > 0.0) {
1052 if (0)
1053 qDebug(" Cutoff, 2nd visit to Cycle Member");
1054 // and takeback cost addition, as it is added twice
1055 n.incl = oldIncl;
1056 n.self -= f->subCost(_eventType) * factor;
1057 return;
1058 }
1059 } else if (incl <= _realFuncLimit) {
1060 if (0)
1061 qDebug(" Cutoff, below limit");
1062 return;
1063 }
1064
1065 TraceFunction* f2;
1066
1067 // on entering a cycle, only go the FunctionCycle
1068 TraceCallList l = toCallees ? f->callings(false) : f->callers(false);
1069
1070 foreach(TraceCall* call, l) {
1071
1072 f2 = toCallees ? call->called(false) : call->caller(false);
1073
1074 double count = call->callCount() * factor;
1075 double cost = call->subCost(_eventType) * factor;
1076
1077 // ignore function calls with absolute cost < 3 per call
1078 // No: This would skip a lot of functions e.g. with L2 cache misses
1079 // if (count>0.0 && (cost/count < 3)) continue;
1080
1081 double oldCost = 0.0;
1082 QPair<TraceFunction*,TraceFunction*> p(toCallees ? f : f2,
1083 toCallees ? f2 : f);
1084 GraphEdge& e = _edgeMap[p];
1085 if (e.call() == nullptr) {
1086 e.setCall(call);
1087 e.setCaller(p.first);
1088 e.setCallee(p.second);
1089 } else
1090 oldCost = e.cost;
1091
1092 e.cost += cost;
1093 e.count += count;
1094 if (0)
1095 qDebug(" Edge to %s, added cost %f, now %f",
1096 qPrintable(f2->prettyName()), cost, e.cost);
1097
1098 // if this call goes into a FunctionCycle, we also show the real call
1099 if (f2->cycle() == f2) {
1100 TraceFunction* realF;
1101 realF = toCallees ? call->called(true) : call->caller(true);
1102 QPair<TraceFunction*,TraceFunction*>
1103 realP(toCallees ? f : realF, toCallees ? realF : f);
1104 GraphEdge& e = _edgeMap[realP];
1105 if (e.call() == nullptr) {
1106 e.setCall(call);
1107 e.setCaller(realP.first);
1108 e.setCallee(realP.second);
1109 }
1110 e.cost += cost;
1111 e.count += count;
1112 }
1113
1114 // - do not do a DFS on calls in recursion/cycle
1115 if (call->inCycle()>0)
1116 continue;
1117 if (call->isRecursion())
1118 continue;
1119
1120 if (toCallees)
1121 n.addUniqueCallee(&e);
1122 else
1123 n.addUniqueCaller(&e);
1124
1125 // if we just reached the call limit (=func limit by summing, do a DFS
1126 // from here with full incl. cost because of previous cutoffs
1127 if ((e.cost >= _realCallLimit) && (oldCost < _realCallLimit))
1128 cost = e.cost;
1129 if ((cost <= 0) || (cost <= _realCallLimit)) {
1130 if (0)
1131 qDebug(" Edge Cutoff, limit not reached");
1132 continue;
1133 }
1134
1135 SubCost s;
1136 if (call->inCycle())
1137 s = f2->cycle()->inclusive()->subCost(_eventType);
1138 else
1139 s = f2->inclusive()->subCost(_eventType);
1140 SubCost v = call->subCost(_eventType);
1141
1142 // Never recurse if s or v is 0 (can happen with bogus input)
1143 if ((v == 0) || (s== 0)) continue;
1144
1145 buildGraph(f2, depth+1, toCallees, factor * v / s);
1146 }
1147 }
1148
1149 //
1150 // PannerView
1151 //
PanningView(QWidget * parent)1152 PanningView::PanningView(QWidget * parent)
1153 : QGraphicsView(parent)
1154 {
1155 _movingZoomRect = false;
1156
1157 // FIXME: Why does this not work?
1158 viewport()->setFocusPolicy(Qt::NoFocus);
1159 }
1160
setZoomRect(const QRectF & r)1161 void PanningView::setZoomRect(const QRectF& r)
1162 {
1163 _zoomRect = r;
1164 viewport()->update();
1165 }
1166
drawForeground(QPainter * p,const QRectF &)1167 void PanningView::drawForeground(QPainter * p, const QRectF&)
1168 {
1169 if (!_zoomRect.isValid())
1170 return;
1171
1172 QColor red(Qt::red);
1173 QPen pen(red.darker());
1174 pen.setWidthF(2.0 / transform().m11());
1175 p->setPen(pen);
1176
1177 QColor c(red.darker());
1178 c.setAlphaF(0.05);
1179 p->setBrush(QBrush(c));
1180
1181 p->drawRect(QRectF(_zoomRect.x(), _zoomRect.y(),
1182 _zoomRect.width()-1, _zoomRect.height()-1));
1183 }
1184
mousePressEvent(QMouseEvent * e)1185 void PanningView::mousePressEvent(QMouseEvent* e)
1186 {
1187 QPointF sPos = mapToScene(e->pos());
1188
1189 if (_zoomRect.isValid()) {
1190 if (!_zoomRect.contains(sPos))
1191 emit zoomRectMoved(sPos.x() - _zoomRect.center().x(),
1192 sPos.y() - _zoomRect.center().y());
1193
1194 _movingZoomRect = true;
1195 _lastPos = sPos;
1196 }
1197 }
1198
mouseMoveEvent(QMouseEvent * e)1199 void PanningView::mouseMoveEvent(QMouseEvent* e)
1200 {
1201 QPointF sPos = mapToScene(e->pos());
1202 if (_movingZoomRect) {
1203 emit zoomRectMoved(sPos.x() - _lastPos.x(),
1204 sPos.y() - _lastPos.y());
1205 _lastPos = sPos;
1206 }
1207 }
1208
mouseReleaseEvent(QMouseEvent *)1209 void PanningView::mouseReleaseEvent(QMouseEvent*)
1210 {
1211 _movingZoomRect = false;
1212 emit zoomRectMoveFinished();
1213 }
1214
1215
1216
1217
1218
1219 //
1220 // CanvasNode
1221 //
1222
CanvasNode(CallGraphView * v,GraphNode * n,int x,int y,int w,int h)1223 CanvasNode::CanvasNode(CallGraphView* v, GraphNode* n, int x, int y, int w,
1224 int h) :
1225 QGraphicsRectItem(QRect(x, y, w, h)), _node(n), _view(v)
1226 {
1227 setPosition(0, DrawParams::TopCenter);
1228 setPosition(1, DrawParams::BottomCenter);
1229
1230 updateGroup();
1231
1232 if (!_node || !_view)
1233 return;
1234
1235 if (_node->function())
1236 setText(0, _node->function()->prettyName());
1237
1238 ProfileCostArray* totalCost;
1239 if (GlobalConfig::showExpanded()) {
1240 if (_view->activeFunction()) {
1241 if (_view->activeFunction()->cycle())
1242 totalCost = _view->activeFunction()->cycle()->inclusive();
1243 else
1244 totalCost = _view->activeFunction()->inclusive();
1245 } else
1246 totalCost = (ProfileCostArray*) _view->activeItem();
1247 } else
1248 totalCost = ((TraceItemView*)_view)->data();
1249 double total = totalCost->subCost(_view->eventType());
1250 double inclP = 100.0 * n->incl/ total;
1251 if (GlobalConfig::showPercentage())
1252 setText(1, QStringLiteral("%1 %")
1253 .arg(inclP, 0, 'f', GlobalConfig::percentPrecision()));
1254 else
1255 setText(1, SubCost(n->incl).pretty());
1256 setPixmap(1, percentagePixmap(25, 10, (int)(inclP+.5), Qt::blue, true));
1257
1258 setToolTip(QStringLiteral("%1 (%2)").arg(text(0)).arg(text(1)));
1259 }
1260
setSelected(bool s)1261 void CanvasNode::setSelected(bool s)
1262 {
1263 StoredDrawParams::setSelected(s);
1264 update();
1265 }
1266
updateGroup()1267 void CanvasNode::updateGroup()
1268 {
1269 if (!_view || !_node)
1270 return;
1271
1272 QColor c = GlobalGUIConfig::functionColor(_view->groupType(),
1273 _node->function());
1274 setBackColor(c);
1275 update();
1276 }
1277
paint(QPainter * p,const QStyleOptionGraphicsItem * option,QWidget *)1278 void CanvasNode::paint(QPainter* p,
1279 const QStyleOptionGraphicsItem* option,
1280 QWidget*)
1281 {
1282 QRect r = rect().toRect(), origRect = r;
1283
1284 r.setRect(r.x()+1, r.y()+1, r.width()-2, r.height()-2);
1285
1286 RectDrawing d(r);
1287 d.drawBack(p, this);
1288 r.setRect(r.x()+2, r.y()+2, r.width()-4, r.height()-4);
1289
1290 #if 0
1291 if (StoredDrawParams::selected() && _view->hasFocus()) {
1292 _view->style().drawPrimitive( QStyle::PE_FocusRect, &p, r,
1293 _view->colorGroup());
1294 }
1295 #endif
1296
1297 // draw afterwards to always get a frame even when zoomed
1298 p->setPen(StoredDrawParams::selected() ? Qt::red : Qt::black);
1299 p->drawRect(QRect(origRect.x(), origRect.y(), origRect.width()-1,
1300 origRect.height()-1));
1301
1302 #if QT_VERSION >= 0x040600
1303 if (option->levelOfDetailFromTransform(p->transform()) < .5)
1304 return;
1305 #else
1306 if (option->levelOfDetail < .5)
1307 return;
1308 #endif
1309
1310 d.setRect(r);
1311 d.drawField(p, 0, this);
1312 d.drawField(p, 1, this);
1313 }
1314
1315
1316 //
1317 // CanvasEdgeLabel
1318 //
1319
CanvasEdgeLabel(CallGraphView * v,CanvasEdge * ce,int x,int y,int w,int h)1320 CanvasEdgeLabel::CanvasEdgeLabel(CallGraphView* v, CanvasEdge* ce, int x,
1321 int y, int w, int h) :
1322 QGraphicsRectItem(QRect(x, y, w, h)), _ce(ce), _view(v), _percentage(0.0)
1323 {
1324 GraphEdge* e = ce->edge();
1325 if (!e)
1326 return;
1327
1328 setPosition(1, DrawParams::BottomCenter);
1329 ProfileCostArray* totalCost;
1330 if (GlobalConfig::showExpanded()) {
1331 if (_view->activeFunction()) {
1332 if (_view->activeFunction()->cycle())
1333 totalCost = _view->activeFunction()->cycle()->inclusive();
1334 else
1335 totalCost = _view->activeFunction()->inclusive();
1336 } else
1337 totalCost = (ProfileCostArray*) _view->activeItem();
1338 } else
1339 totalCost = ((TraceItemView*)_view)->data();
1340 double total = totalCost->subCost(_view->eventType());
1341 double inclP = 100.0 * e->cost/ total;
1342 if (GlobalConfig::showPercentage())
1343 setText(1, QStringLiteral("%1 %")
1344 .arg(inclP, 0, 'f', GlobalConfig::percentPrecision()));
1345 else
1346 setText(1, SubCost(e->cost).pretty());
1347
1348 int pixPos = 1;
1349 if (((TraceItemView*)_view)->data()->maxCallCount() > 0) {
1350 setPosition(0, DrawParams::TopCenter);
1351 SubCost count((e->count < 1.0) ? 1.0 : e->count);
1352 setText(0, QStringLiteral("%1 x").arg(count.pretty()));
1353 pixPos = 0;
1354 setToolTip(QStringLiteral("%1 (%2)").arg(text(0)).arg(text(1)));
1355 }
1356 setPixmap(pixPos, percentagePixmap(25, 10, (int)(inclP+.5), Qt::blue, true));
1357
1358 _percentage = inclP;
1359 if (_percentage > 100.0) _percentage = 100.0;
1360
1361 if (e->call() && (e->call()->isRecursion() || e->call()->inCycle())) {
1362 QFontMetrics fm(font());
1363 QPixmap p = QIcon::fromTheme(QStringLiteral("edit-undo")).pixmap(fm.height());
1364 setPixmap(pixPos, p); // replace percentage pixmap
1365 }
1366 }
1367
paint(QPainter * p,const QStyleOptionGraphicsItem * option,QWidget *)1368 void CanvasEdgeLabel::paint(QPainter* p,
1369 const QStyleOptionGraphicsItem* option, QWidget*)
1370 {
1371 // draw nothing in PanningView
1372 #if QT_VERSION >= 0x040600
1373 if (option->levelOfDetailFromTransform(p->transform()) < .5)
1374 return;
1375 #else
1376 if (option->levelOfDetail < .5)
1377 return;
1378 #endif
1379
1380 QRect r = rect().toRect();
1381
1382 RectDrawing d(r);
1383 d.drawField(p, 0, this);
1384 d.drawField(p, 1, this);
1385 }
1386
1387
1388
1389 //
1390 // CanvasEdgeArrow
1391
CanvasEdgeArrow(CanvasEdge * ce)1392 CanvasEdgeArrow::CanvasEdgeArrow(CanvasEdge* ce)
1393 : _ce(ce)
1394 {}
1395
paint(QPainter * p,const QStyleOptionGraphicsItem *,QWidget *)1396 void CanvasEdgeArrow::paint(QPainter* p,
1397 const QStyleOptionGraphicsItem *, QWidget *)
1398 {
1399 p->setRenderHint(QPainter::Antialiasing);
1400 p->setBrush(_ce->isSelected() ? Qt::red : Qt::black);
1401 p->drawPolygon(polygon(), Qt::OddEvenFill);
1402 }
1403
1404
1405 //
1406 // CanvasEdge
1407 //
1408
CanvasEdge(GraphEdge * e)1409 CanvasEdge::CanvasEdge(GraphEdge* e) :
1410 _edge(e)
1411 {
1412 _label = nullptr;
1413 _arrow = nullptr;
1414 _thickness = 0;
1415
1416 setFlag(QGraphicsItem::ItemIsSelectable);
1417 }
1418
setLabel(CanvasEdgeLabel * l)1419 void CanvasEdge::setLabel(CanvasEdgeLabel* l)
1420 {
1421 _label = l;
1422
1423 if (l) {
1424 QString tip = QStringLiteral("%1 (%2)").arg(l->text(0)).arg(l->text(1));
1425
1426 setToolTip(tip);
1427 if (_arrow) _arrow->setToolTip(tip);
1428
1429 _thickness = log(l->percentage());
1430 if (_thickness < .9) _thickness = .9;
1431 }
1432 }
1433
setArrow(CanvasEdgeArrow * a)1434 void CanvasEdge::setArrow(CanvasEdgeArrow* a)
1435 {
1436 _arrow = a;
1437
1438 if (a && _label) a->setToolTip(QStringLiteral("%1 (%2)")
1439 .arg(_label->text(0)).arg(_label->text(1)));
1440 }
1441
setSelected(bool s)1442 void CanvasEdge::setSelected(bool s)
1443 {
1444 QGraphicsItem::setSelected(s);
1445 update();
1446 }
1447
setControlPoints(const QPolygon & pa)1448 void CanvasEdge::setControlPoints(const QPolygon& pa)
1449 {
1450 _points = pa;
1451
1452 QPainterPath path;
1453 path.moveTo(pa[0]);
1454 for (int i = 1; i < pa.size(); i += 3)
1455 path.cubicTo(pa[i], pa[(i + 1) % pa.size()], pa[(i + 2) % pa.size()]);
1456
1457 setPath(path);
1458 }
1459
1460
paint(QPainter * p,const QStyleOptionGraphicsItem * option,QWidget *)1461 void CanvasEdge::paint(QPainter* p,
1462 const QStyleOptionGraphicsItem* option, QWidget*)
1463 {
1464 p->setRenderHint(QPainter::Antialiasing);
1465
1466 qreal levelOfDetail;
1467 #if QT_VERSION >= 0x040600
1468 levelOfDetail = option->levelOfDetailFromTransform(p->transform());
1469 #else
1470 levelOfDetail = option->levelOfDetail;
1471 #endif
1472
1473 QPen mypen = pen();
1474 mypen.setWidthF(1.0/levelOfDetail * _thickness);
1475 p->setPen(mypen);
1476 p->drawPath(path());
1477
1478 if (isSelected()) {
1479 mypen.setColor(Qt::red);
1480 mypen.setWidthF(1.0/levelOfDetail * _thickness/2.0);
1481 p->setPen(mypen);
1482 p->drawPath(path());
1483 }
1484 }
1485
1486
1487 //
1488 // CanvasFrame
1489 //
1490
1491 QPixmap* CanvasFrame::_p = nullptr;
1492
CanvasFrame(CanvasNode * n)1493 CanvasFrame::CanvasFrame(CanvasNode* n)
1494 {
1495 if (!_p) {
1496
1497 int d = 5;
1498 float v1 = 130.0f, v2 = 10.0f, v = v1, f = 1.03f;
1499
1500 // calculate pix size
1501 QRect r(0, 0, 30, 30);
1502 while (v>v2) {
1503 r.setRect(r.x()-d, r.y()-d, r.width()+2*d, r.height()+2*d);
1504 v /= f;
1505 }
1506
1507 _p = new QPixmap(r.size());
1508 _p->fill(Qt::white);
1509 QPainter p(_p);
1510 p.setPen(Qt::NoPen);
1511
1512 r.translate(-r.x(), -r.y());
1513
1514 while (v<v1) {
1515 v *= f;
1516 p.setBrush(QColor(265-(int)v, 265-(int)v, 265-(int)v));
1517
1518 p.drawRect(QRect(r.x(), r.y(), r.width(), d));
1519 p.drawRect(QRect(r.x(), r.bottom()-d, r.width(), d));
1520 p.drawRect(QRect(r.x(), r.y()+d, d, r.height()-2*d));
1521 p.drawRect(QRect(r.right()-d, r.y()+d, d, r.height()-2*d));
1522
1523 r.setRect(r.x()+d, r.y()+d, r.width()-2*d, r.height()-2*d);
1524 }
1525 }
1526
1527 setRect(QRectF(n->rect().center().x() - _p->width()/2,
1528 n->rect().center().y() - _p->height()/2, _p->width(), _p->height()) );
1529 }
1530
1531
paint(QPainter * p,const QStyleOptionGraphicsItem * option,QWidget *)1532 void CanvasFrame::paint(QPainter* p,
1533 const QStyleOptionGraphicsItem* option, QWidget*)
1534 {
1535 qreal levelOfDetail;
1536 #if QT_VERSION >= 0x040600
1537 levelOfDetail = option->levelOfDetailFromTransform(p->transform());
1538 #else
1539 levelOfDetail = option->levelOfDetail;
1540 #endif
1541 if (levelOfDetail < .5) {
1542 QRadialGradient g(rect().center(), rect().width()/3);
1543 g.setColorAt(0.0, Qt::gray);
1544 g.setColorAt(1.0, Qt::white);
1545
1546 p->setBrush(QBrush(g));
1547 p->setPen(Qt::NoPen);
1548 p->drawRect(rect());
1549 return;
1550 }
1551
1552 p->drawPixmap(int( rect().x()),int( rect().y()), *_p );
1553 }
1554
1555
1556
1557 //
1558 // CallGraphView
1559 //
CallGraphView(TraceItemView * parentView,QWidget * parent,const QString & name)1560 CallGraphView::CallGraphView(TraceItemView* parentView, QWidget* parent,
1561 const QString& name) :
1562 QGraphicsView(parent), TraceItemView(parentView)
1563 {
1564 setObjectName(name);
1565 _zoomPosition = DEFAULT_ZOOMPOS;
1566 _lastAutoPosition = TopLeft;
1567
1568 _scene = nullptr;
1569 _xMargin = _yMargin = 0;
1570 _panningView = new PanningView(this);
1571 _panningZoom = 1;
1572 _selectedNode = nullptr;
1573 _selectedEdge = nullptr;
1574 _isMoving = false;
1575
1576 _exporter.setGraphOptions(this);
1577
1578 _panningView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
1579 _panningView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
1580 _panningView->raise();
1581 _panningView->hide();
1582
1583 setFocusPolicy(Qt::StrongFocus);
1584 setAttribute(Qt::WA_NoSystemBackground, true);
1585
1586 connect(_panningView, &PanningView::zoomRectMoved, this, &CallGraphView::zoomRectMoved);
1587 connect(_panningView, &PanningView::zoomRectMoveFinished, this, &CallGraphView::zoomRectMoveFinished);
1588
1589 this->setWhatsThis(whatsThis() );
1590
1591 // tooltips...
1592 //_tip = new CallGraphTip(this);
1593
1594 _renderProcess = nullptr;
1595 _prevSelectedNode = nullptr;
1596 connect(&_renderTimer, &QTimer::timeout,
1597 this, &CallGraphView::showRenderWarning);
1598 }
1599
~CallGraphView()1600 CallGraphView::~CallGraphView()
1601 {
1602 clear();
1603 delete _panningView;
1604 }
1605
whatsThis() const1606 QString CallGraphView::whatsThis() const
1607 {
1608 return tr("<b>Call Graph around active Function</b>"
1609 "<p>Depending on configuration, this view shows "
1610 "the call graph environment of the active function. "
1611 "Note: the shown cost is <b>only</b> the cost which is "
1612 "spent while the active function was actually running; "
1613 "i.e. the cost shown for main() - if it is visible - should "
1614 "be the same as the cost of the active function, as that is "
1615 "the part of inclusive cost of main() spent while the active "
1616 "function was running.</p>"
1617 "<p>For cycles, blue call arrows indicate that this is an "
1618 "artificial call added for correct drawing which "
1619 "actually never happened.</p>"
1620 "<p>If the graph is larger than the widget area, an overview "
1621 "panner is shown in one edge. "
1622 "There are similar visualization options to the "
1623 "Call Treemap; the selected function is highlighted.</p>");
1624 }
1625
updateSizes(QSize s)1626 void CallGraphView::updateSizes(QSize s)
1627 {
1628 if (!_scene)
1629 return;
1630
1631 if (s == QSize(0, 0))
1632 s = size();
1633
1634 // the part of the scene that should be visible
1635 int cWidth = (int)_scene->width() - 2*_xMargin + 100;
1636 int cHeight = (int)_scene->height() - 2*_yMargin + 100;
1637
1638 // hide birds eye view if no overview needed
1639 if (!_data || !_activeItem ||
1640 ((cWidth < s.width()) && (cHeight < s.height())) ) {
1641 _panningView->hide();
1642 return;
1643 }
1644
1645 // first, assume use of 1/3 of width/height (possible larger)
1646 double zoom = .33 * s.width() / cWidth;
1647 if (zoom * cHeight < .33 * s.height())
1648 zoom = .33 * s.height() / cHeight;
1649
1650 // fit to widget size
1651 if (cWidth * zoom > s.width())
1652 zoom = s.width() / (double)cWidth;
1653 if (cHeight * zoom > s.height())
1654 zoom = s.height() / (double)cHeight;
1655
1656 // scale to never use full height/width
1657 zoom = zoom * 3/4;
1658
1659 // at most a zoom of 1/3
1660 if (zoom > .33)
1661 zoom = .33;
1662
1663 if (zoom != _panningZoom) {
1664 _panningZoom = zoom;
1665 if (0)
1666 qDebug("Canvas Size: %fx%f, Content: %dx%d, Zoom: %f",
1667 _scene->width(), _scene->height(), cWidth, cHeight, zoom);
1668
1669 QTransform m;
1670 _panningView->setTransform(m.scale(zoom, zoom));
1671
1672 // make it a little bigger to compensate for widget frame
1673 _panningView->resize(int(cWidth * zoom) + 4, int(cHeight * zoom) + 4);
1674
1675 // update ZoomRect in panningView
1676 scrollContentsBy(0, 0);
1677 }
1678
1679 _panningView->centerOn(_scene->width()/2, _scene->height()/2);
1680
1681 int cvW = _panningView->width();
1682 int cvH = _panningView->height();
1683 int x = width()- cvW - verticalScrollBar()->width() -2;
1684 int y = height()-cvH - horizontalScrollBar()->height() -2;
1685 QPoint oldZoomPos = _panningView->pos();
1686 QPoint newZoomPos = QPoint(0, 0);
1687 ZoomPosition zp = _zoomPosition;
1688 if (zp == Auto) {
1689 int tlCols = items(QRect(0,0, cvW,cvH)).count();
1690 int trCols = items(QRect(x,0, cvW,cvH)).count();
1691 int blCols = items(QRect(0,y, cvW,cvH)).count();
1692 int brCols = items(QRect(x,y, cvW,cvH)).count();
1693 int minCols = tlCols;
1694
1695 zp = _lastAutoPosition;
1696 switch (zp) {
1697 case TopRight:
1698 minCols = trCols;
1699 break;
1700 case BottomLeft:
1701 minCols = blCols;
1702 break;
1703 case BottomRight:
1704 minCols = brCols;
1705 break;
1706 default:
1707 case TopLeft:
1708 minCols = tlCols;
1709 break;
1710 }
1711
1712 if (minCols > tlCols) {
1713 minCols = tlCols;
1714 zp = TopLeft;
1715 }
1716 if (minCols > trCols) {
1717 minCols = trCols;
1718 zp = TopRight;
1719 }
1720 if (minCols > blCols) {
1721 minCols = blCols;
1722 zp = BottomLeft;
1723 }
1724 if (minCols > brCols) {
1725 minCols = brCols;
1726 zp = BottomRight;
1727 }
1728
1729 _lastAutoPosition = zp;
1730 }
1731
1732 switch (zp) {
1733 case TopLeft:
1734 newZoomPos = QPoint(0, 0);
1735 break;
1736 case TopRight:
1737 newZoomPos = QPoint(x, 0);
1738 break;
1739 case BottomLeft:
1740 newZoomPos = QPoint(0, y);
1741 break;
1742 case BottomRight:
1743 newZoomPos = QPoint(x, y);
1744 break;
1745 default:
1746 break;
1747 }
1748
1749 if (newZoomPos != oldZoomPos)
1750 _panningView->move(newZoomPos);
1751
1752 if (zp == Hide)
1753 _panningView->hide();
1754 else
1755 _panningView->show();
1756 }
1757
focusInEvent(QFocusEvent *)1758 void CallGraphView::focusInEvent(QFocusEvent*)
1759 {
1760 if (!_scene) return;
1761
1762 if (_selectedNode && _selectedNode->canvasNode()) {
1763 _selectedNode->canvasNode()->setSelected(true); // requests item update
1764 _scene->update();
1765 }
1766 }
1767
focusOutEvent(QFocusEvent * e)1768 void CallGraphView::focusOutEvent(QFocusEvent* e)
1769 {
1770 // trigger updates as in focusInEvent
1771 focusInEvent(e);
1772 }
1773
keyPressEvent(QKeyEvent * e)1774 void CallGraphView::keyPressEvent(QKeyEvent* e)
1775 {
1776 if (!_scene) {
1777 e->ignore();
1778 return;
1779 }
1780
1781 if ((e->key() == Qt::Key_Return) ||(e->key() == Qt::Key_Space)) {
1782 if (_selectedNode)
1783 activated(_selectedNode->function());
1784 else if (_selectedEdge && _selectedEdge->call())
1785 activated(_selectedEdge->call());
1786 return;
1787 }
1788
1789 // move selected node/edge
1790 if (!(e->modifiers() & (Qt::ShiftModifier | Qt::ControlModifier))
1791 &&(_selectedNode || _selectedEdge)&&((e->key() == Qt::Key_Up)
1792 ||(e->key() == Qt::Key_Down)||(e->key() == Qt::Key_Left)||(e->key()
1793 == Qt::Key_Right))) {
1794
1795 TraceFunction* f = nullptr;
1796 TraceCall* c = nullptr;
1797
1798 // rotate arrow key meaning for LeftRight layout
1799 int key = e->key();
1800 if (_layout == LeftRight) {
1801 switch (key) {
1802 case Qt::Key_Up:
1803 key = Qt::Key_Left;
1804 break;
1805 case Qt::Key_Down:
1806 key = Qt::Key_Right;
1807 break;
1808 case Qt::Key_Left:
1809 key = Qt::Key_Up;
1810 break;
1811 case Qt::Key_Right:
1812 key = Qt::Key_Down;
1813 break;
1814 default:
1815 break;
1816 }
1817 }
1818
1819 if (_selectedNode) {
1820 if (key == Qt::Key_Up)
1821 c = _selectedNode->visibleCaller();
1822 if (key == Qt::Key_Down)
1823 c = _selectedNode->visibleCallee();
1824 if (key == Qt::Key_Right)
1825 f = _selectedNode->nextVisible();
1826 if (key == Qt::Key_Left)
1827 f = _selectedNode->priorVisible();
1828 } else if (_selectedEdge) {
1829 if (key == Qt::Key_Up)
1830 f = _selectedEdge->visibleCaller();
1831 if (key == Qt::Key_Down)
1832 f = _selectedEdge->visibleCallee();
1833 if (key == Qt::Key_Right)
1834 c = _selectedEdge->nextVisible();
1835 if (key == Qt::Key_Left)
1836 c = _selectedEdge->priorVisible();
1837 }
1838
1839 if (c)
1840 selected(c);
1841 if (f)
1842 selected(f);
1843 return;
1844 }
1845
1846 // move canvas...
1847 QPointF center = mapToScene(viewport()->rect().center());
1848 if (e->key() == Qt::Key_Home)
1849 centerOn(center + QPointF(-_scene->width(), 0));
1850 else if (e->key() == Qt::Key_End)
1851 centerOn(center + QPointF(_scene->width(), 0));
1852 else if (e->key() == Qt::Key_PageUp) {
1853 QPointF dy = mapToScene(0, height()) - mapToScene(0, 0);
1854 centerOn(center + QPointF(-dy.x()/2, -dy.y()/2));
1855 } else if (e->key() == Qt::Key_PageDown) {
1856 QPointF dy = mapToScene(0, height()) - mapToScene(0, 0);
1857 centerOn(center + QPointF(dy.x()/2, dy.y()/2));
1858 } else if (e->key() == Qt::Key_Left) {
1859 QPointF dx = mapToScene(width(), 0) - mapToScene(0, 0);
1860 centerOn(center + QPointF(-dx.x()/10, -dx.y()/10));
1861 } else if (e->key() == Qt::Key_Right) {
1862 QPointF dx = mapToScene(width(), 0) - mapToScene(0, 0);
1863 centerOn(center + QPointF(dx.x()/10, dx.y()/10));
1864 } else if (e->key() == Qt::Key_Down) {
1865 QPointF dy = mapToScene(0, height()) - mapToScene(0, 0);
1866 centerOn(center + QPointF(dy.x()/10, dy.y()/10));
1867 } else if (e->key() == Qt::Key_Up) {
1868 QPointF dy = mapToScene(0, height()) - mapToScene(0, 0);
1869 centerOn(center + QPointF(-dy.x()/10, -dy.y()/10));
1870 } else
1871 e->ignore();
1872 }
1873
resizeEvent(QResizeEvent * e)1874 void CallGraphView::resizeEvent(QResizeEvent* e)
1875 {
1876 QGraphicsView::resizeEvent(e);
1877 if (_scene)
1878 updateSizes(e->size());
1879 }
1880
canShow(CostItem * i)1881 CostItem* CallGraphView::canShow(CostItem* i)
1882 {
1883 if (i) {
1884 switch (i->type()) {
1885 case ProfileContext::Function:
1886 case ProfileContext::FunctionCycle:
1887 case ProfileContext::Call:
1888 return i;
1889 default:
1890 break;
1891 }
1892 }
1893 return nullptr;
1894 }
1895
doUpdate(int changeType,bool)1896 void CallGraphView::doUpdate(int changeType, bool)
1897 {
1898 // Special case ?
1899 if (changeType == eventType2Changed)
1900 return;
1901
1902 if (changeType == selectedItemChanged) {
1903 if (!_scene)
1904 return;
1905
1906 if (!_selectedItem)
1907 return;
1908
1909 GraphNode* n = nullptr;
1910 GraphEdge* e = nullptr;
1911 if ((_selectedItem->type() == ProfileContext::Function)
1912 ||(_selectedItem->type() == ProfileContext::FunctionCycle)) {
1913 n = _exporter.node((TraceFunction*)_selectedItem);
1914 if (n == _selectedNode)
1915 return;
1916 } else if (_selectedItem->type() == ProfileContext::Call) {
1917 TraceCall* c = (TraceCall*)_selectedItem;
1918 e = _exporter.edge(c->caller(false), c->called(false));
1919 if (e == _selectedEdge)
1920 return;
1921 }
1922
1923 // unselect any selected item
1924 if (_selectedNode && _selectedNode->canvasNode()) {
1925 _selectedNode->canvasNode()->setSelected(false);
1926 }
1927 _selectedNode = nullptr;
1928 if (_selectedEdge && _selectedEdge->canvasEdge()) {
1929 _selectedEdge->canvasEdge()->setSelected(false);
1930 }
1931 _selectedEdge = nullptr;
1932
1933 // select
1934 CanvasNode* sNode = nullptr;
1935 if (n && n->canvasNode()) {
1936 _selectedNode = n;
1937 _selectedNode->canvasNode()->setSelected(true);
1938
1939 if (!_isMoving)
1940 sNode = _selectedNode->canvasNode();
1941 }
1942 if (e && e->canvasEdge()) {
1943 _selectedEdge = e;
1944 _selectedEdge->canvasEdge()->setSelected(true);
1945
1946 #if 0 // do not change position when selecting edge
1947 if (!_isMoving) {
1948 if (_selectedEdge->fromNode())
1949 sNode = _selectedEdge->fromNode()->canvasNode();
1950 if (!sNode && _selectedEdge->toNode())
1951 sNode = _selectedEdge->toNode()->canvasNode();
1952 }
1953 #endif
1954 }
1955 if (sNode)
1956 ensureVisible(sNode);
1957
1958 _scene->update();
1959 return;
1960 }
1961
1962 if (changeType == groupTypeChanged) {
1963 if (!_scene)
1964 return;
1965
1966 if (_clusterGroups) {
1967 refresh();
1968 return;
1969 }
1970
1971 QList<QGraphicsItem *> l = _scene->items();
1972 for (int i = 0; i < l.size(); ++i)
1973 if (l[i]->type() == CANVAS_NODE)
1974 ((CanvasNode*)l[i])->updateGroup();
1975
1976 _scene->update();
1977 return;
1978 }
1979
1980 if (changeType & dataChanged) {
1981 // invalidate old selection and graph part
1982 _exporter.reset(_data, _activeItem, _eventType, _groupType);
1983 _selectedNode = nullptr;
1984 _selectedEdge = nullptr;
1985 }
1986
1987 refresh();
1988 }
1989
clear()1990 void CallGraphView::clear()
1991 {
1992 if (!_scene)
1993 return;
1994
1995 _panningView->setScene(nullptr);
1996 setScene(nullptr);
1997 delete _scene;
1998 _scene = nullptr;
1999 }
2000
showText(QString s)2001 void CallGraphView::showText(QString s)
2002 {
2003 clear();
2004 _renderTimer.stop();
2005
2006 _scene = new QGraphicsScene;
2007
2008 _scene->addSimpleText(s);
2009 centerOn(0, 0);
2010 setScene(_scene);
2011 _scene->update();
2012 _panningView->hide();
2013 }
2014
showRenderWarning()2015 void CallGraphView::showRenderWarning()
2016 {
2017 QString s;
2018
2019 if (_renderProcess)
2020 s = tr("Warning: a long lasting graph layouting is in progress.\n"
2021 "Reduce node/edge limits for speedup.\n");
2022 else
2023 s = tr("Layouting stopped.\n");
2024
2025 s.append(tr("The call graph has %1 nodes and %2 edges.\n")
2026 .arg(_exporter.nodeCount()).arg(_exporter.edgeCount()));
2027
2028 showText(s);
2029 }
2030
showRenderError(QString s)2031 void CallGraphView::showRenderError(QString s)
2032 {
2033 QString err;
2034 err = tr("No graph available because the layouting process failed.\n");
2035 if (_renderProcess)
2036 err += tr("Trying to run the following command did not work:\n"
2037 "'%1'\n").arg(_renderProcessCmdLine);
2038 err += tr("Please check that 'dot' is installed (package GraphViz).");
2039
2040 if (!s.isEmpty())
2041 err += QStringLiteral("\n\n%1").arg(s);
2042
2043 showText(err);
2044 }
2045
stopRendering()2046 void CallGraphView::stopRendering()
2047 {
2048 if (!_renderProcess)
2049 return;
2050
2051 qDebug("CallGraphView::stopRendering: Killing QProcess %p",
2052 _renderProcess);
2053
2054 _renderProcess->kill();
2055
2056 // forget about this process, not interesting any longer
2057 _renderProcess->deleteLater();
2058 _renderProcess = nullptr;
2059 _unparsedOutput = QString();
2060
2061 _renderTimer.setSingleShot(true);
2062 _renderTimer.start(200);
2063 }
2064
refresh()2065 void CallGraphView::refresh()
2066 {
2067 // trigger start of new layouting via 'dot'
2068 if (_renderProcess)
2069 stopRendering();
2070
2071 // we want to keep a selected node item at the same global position
2072 _prevSelectedNode = _selectedNode;
2073 _prevSelectedPos = QPoint(-1, -1);
2074 if (_selectedNode) {
2075 QPointF center = _selectedNode->canvasNode()->rect().center();
2076 _prevSelectedPos = mapFromScene(center);
2077 }
2078
2079 if (!_data || !_activeItem) {
2080 showText(tr("No item activated for which to "
2081 "draw the call graph."));
2082 return;
2083 }
2084
2085 ProfileContext::Type t = _activeItem->type();
2086 switch (t) {
2087 case ProfileContext::Function:
2088 case ProfileContext::FunctionCycle:
2089 case ProfileContext::Call:
2090 break;
2091 default:
2092 showText(tr("No call graph can be drawn for "
2093 "the active item."));
2094 return;
2095 }
2096
2097 if (1)
2098 qDebug() << "CallGraphView::refresh";
2099
2100 _selectedNode = nullptr;
2101 _selectedEdge = nullptr;
2102
2103 /*
2104 * Call 'dot' asynchronously in the background with the aim to
2105 * - have responsive GUI while layout task runs (potentially long!)
2106 * - notify user about a long run, using a timer
2107 * - kill long running 'dot' processes when another layout is
2108 * requested, as old data is not needed any more
2109 *
2110 * Even after killing a process, the QProcess needs some time
2111 * to make sure the process is destroyed; also, stdout data
2112 * still can be delivered after killing. Thus, there can/should be
2113 * multiple QProcess's at one time.
2114 * The QProcess we currently wait for data from is <_renderProcess>
2115 * Signals from other QProcesses are ignored with the exception of
2116 * the finished() signal, which triggers QProcess destruction.
2117 */
2118 QString renderProgram;
2119 QStringList renderArgs;
2120 if (_layout == GraphOptions::Circular)
2121 renderProgram = QStringLiteral("twopi");
2122 else
2123 renderProgram = QStringLiteral("dot");
2124 renderArgs << QStringLiteral("-Tplain");
2125
2126 _unparsedOutput = QString();
2127
2128 // display warning if layouting takes > 1s
2129 _renderTimer.setSingleShot(true);
2130 _renderTimer.start(1000);
2131
2132 _renderProcess = new QProcess(this);
2133 connect(_renderProcess, &QProcess::readyReadStandardOutput,
2134 this, &CallGraphView::readDotOutput);
2135 connect(_renderProcess, SIGNAL(error(QProcess::ProcessError)),
2136 this, SLOT(dotError()));
2137 connect(_renderProcess, SIGNAL(finished(int,QProcess::ExitStatus)),
2138 this, SLOT(dotExited()));
2139
2140 _renderProcessCmdLine = renderProgram + QLatin1Char(' ') + renderArgs.join(QLatin1Char(' '));
2141 qDebug("CallGraphView::refresh: Starting process %p, '%s'",
2142 _renderProcess, qPrintable(_renderProcessCmdLine));
2143
2144 // _renderProcess can be set to 0 on error after start().
2145 // thus, we use a local copy afterwards
2146 QProcess* p = _renderProcess;
2147 p->start(renderProgram, renderArgs);
2148 _exporter.reset(_data, _activeItem, _eventType, _groupType);
2149 _exporter.writeDot(p);
2150 p->closeWriteChannel();
2151 }
2152
readDotOutput()2153 void CallGraphView::readDotOutput()
2154 {
2155 QProcess* p = qobject_cast<QProcess*>(sender());
2156 qDebug("CallGraphView::readDotOutput: QProcess %p", p);
2157
2158 // signal from old/uninteresting process?
2159 if ((_renderProcess == nullptr) || (p != _renderProcess)) {
2160 p->deleteLater();
2161 return;
2162 }
2163
2164 _unparsedOutput.append(QString::fromLocal8Bit(_renderProcess->readAllStandardOutput()));
2165 }
2166
dotError()2167 void CallGraphView::dotError()
2168 {
2169 QProcess* p = qobject_cast<QProcess*>(sender());
2170 qDebug("CallGraphView::dotError: Got %d from QProcess %p",
2171 p->error(), p);
2172
2173 // signal from old/uninteresting process?
2174 if ((_renderProcess == nullptr) || (p != _renderProcess)) {
2175 p->deleteLater();
2176 return;
2177 }
2178
2179 showRenderError(QString::fromLocal8Bit(_renderProcess->readAllStandardError()));
2180
2181 // not interesting any longer
2182 _renderProcess->deleteLater();
2183 _renderProcess = nullptr;
2184 }
2185
2186
dotExited()2187 void CallGraphView::dotExited()
2188 {
2189 QProcess* p = qobject_cast<QProcess*>(sender());
2190 qDebug("CallGraphView::dotExited: QProcess %p", p);
2191
2192 // signal from old/uninteresting process?
2193 if ((_renderProcess == nullptr) || (p != _renderProcess)) {
2194 p->deleteLater();
2195 return;
2196 }
2197
2198 _unparsedOutput.append(QString::fromLocal8Bit(_renderProcess->readAllStandardOutput()));
2199 _renderProcess->deleteLater();
2200 _renderProcess = nullptr;
2201
2202 QString line, cmd;
2203 CanvasNode *rItem;
2204 QGraphicsEllipseItem* eItem;
2205 CanvasEdge* sItem;
2206 CanvasEdgeLabel* lItem;
2207 QTextStream* dotStream;
2208 double scale = 1.0, scaleX = 1.0, scaleY = 1.0;
2209 double dotWidth = 0, dotHeight = 0;
2210 GraphNode* activeNode = nullptr;
2211 GraphEdge* activeEdge = nullptr;
2212
2213 _renderTimer.stop();
2214 viewport()->setUpdatesEnabled(false);
2215 clear();
2216 dotStream = new QTextStream(&_unparsedOutput, QIODevice::ReadOnly);
2217
2218 // First pass to adjust coordinate scaling by node height given from dot
2219 // Normal detail level (=1) should be 3 lines using general KDE font
2220 double nodeHeight = 0.0;
2221 while(1) {
2222 line = dotStream->readLine();
2223 if (line.isNull()) break;
2224 if (line.isEmpty()) continue;
2225 QTextStream lineStream(&line, QIODevice::ReadOnly);
2226 lineStream >> cmd;
2227 if (cmd != QLatin1String("node")) continue;
2228 QString s, h;
2229 lineStream >> s /*name*/ >> s /*x*/ >> s /*y*/ >> s /*width*/ >> h /*height*/;
2230 nodeHeight = h.toDouble();
2231 break;
2232 }
2233 if (nodeHeight > 0.0) {
2234 scaleY = (8 + (1 + 2 * _detailLevel) * fontMetrics().height()) / nodeHeight;
2235 scaleX = 80;
2236 }
2237 dotStream->seek(0);
2238 int lineno = 0;
2239 while (1) {
2240 line = dotStream->readLine();
2241 if (line.isNull())
2242 break;
2243
2244 lineno++;
2245 if (line.isEmpty())
2246 continue;
2247
2248 QTextStream lineStream(&line, QIODevice::ReadOnly);
2249 lineStream >> cmd;
2250
2251 if (0)
2252 qDebug("%s:%d - line '%s', cmd '%s'",
2253 qPrintable(_exporter.filename()),
2254 lineno, qPrintable(line), qPrintable(cmd));
2255
2256 if (cmd == QLatin1String("stop"))
2257 break;
2258
2259 if (cmd == QLatin1String("graph")) {
2260 QString dotWidthString, dotHeightString;
2261 // scale will not be used
2262 lineStream >> scale >> dotWidthString >> dotHeightString;
2263 dotWidth = dotWidthString.toDouble();
2264 dotHeight = dotHeightString.toDouble();
2265
2266 if (!_scene) {
2267 int w = (int)(scaleX * dotWidth);
2268 int h = (int)(scaleY * dotHeight);
2269
2270 // We use as minimum canvas size the desktop size.
2271 // Otherwise, the canvas would have to be resized on widget resize.
2272 _xMargin = 50;
2273 if (w < QApplication::primaryScreen()->size().width())
2274 _xMargin += (QApplication::primaryScreen()->size().width()-w)/2;
2275
2276 _yMargin = 50;
2277 if (h < QApplication::primaryScreen()->size().height())
2278 _yMargin += (QApplication::primaryScreen()->size().height()-h)/2;
2279
2280 _scene = new QGraphicsScene( 0.0, 0.0,
2281 qreal(w+2*_xMargin), qreal(h+2*_yMargin));
2282 // Change background color for call graph from default system color to
2283 // white. It has to blend into the gradient for the selected function.
2284 _scene->setBackgroundBrush(Qt::white);
2285
2286 #if DEBUG_GRAPH
2287 qDebug() << qPrintable(_exporter.filename()) << ":" << lineno
2288 << " - graph (" << dotWidth << " x " << dotHeight
2289 << ") => (" << w << " x " << h << ")";
2290 #endif
2291 } else
2292 qDebug() << "Ignoring 2nd 'graph' from dot ("
2293 << _exporter.filename() << ":"<< lineno << ")";
2294 continue;
2295 }
2296
2297 if ((cmd != QLatin1String("node")) && (cmd != QLatin1String("edge"))) {
2298 qDebug() << "Ignoring unknown command '"<< cmd
2299 << "' from dot ("<< _exporter.filename() << ":"<< lineno
2300 << ")";
2301 continue;
2302 }
2303
2304 if (_scene == nullptr) {
2305 qDebug() << "Ignoring '"<< cmd
2306 << "' without 'graph' from dot ("<< _exporter.filename()
2307 << ":"<< lineno << ")";
2308 continue;
2309 }
2310
2311 if (cmd == QLatin1String("node")) {
2312 // x, y are centered in node
2313 QString nodeName, nodeX, nodeY, nodeWidth, nodeHeight;
2314 double x, y, width, height;
2315 lineStream >> nodeName >> nodeX >> nodeY >> nodeWidth
2316 >> nodeHeight;
2317 x = nodeX.toDouble();
2318 y = nodeY.toDouble();
2319 width = nodeWidth.toDouble();
2320 height = nodeHeight.toDouble();
2321
2322 GraphNode* n = _exporter.node(_exporter.toFunc(nodeName));
2323
2324 int xx = (int)(scaleX * x + _xMargin);
2325 int yy = (int)(scaleY * (dotHeight - y)+ _yMargin);
2326 int w = (int)(scaleX * width);
2327 int h = (int)(scaleY * height);
2328
2329 #if DEBUG_GRAPH
2330 qDebug() << _exporter.filename() << ":" << lineno
2331 << " - node '" << nodeName << "' ( "
2332 << x << "/" << y << " - "
2333 << width << "x" << height << " ) => ("
2334 << xx-w/2 << "/" << yy-h/2 << " - "
2335 << w << "x" << h << ")" << endl;
2336 #endif
2337
2338 // Unnamed nodes with collapsed edges (with 'R' and 'S')
2339 if (nodeName[0] == 'R'|| nodeName[0] == 'S') {
2340 w = 10, h = 10;
2341 eItem = new QGraphicsEllipseItem( QRectF(xx-w/2, yy-h/2, w, h) );
2342 _scene->addItem(eItem);
2343 eItem->setBrush(Qt::gray);
2344 eItem->setZValue(1.0);
2345 eItem->show();
2346 continue;
2347 }
2348
2349 if (!n) {
2350 qDebug("Warning: Unknown function '%s' ?!",
2351 qPrintable(nodeName));
2352 continue;
2353 }
2354 n->setVisible(true);
2355
2356 rItem = new CanvasNode(this, n, xx-w/2, yy-h/2, w, h);
2357 // limit symbol space to a maximal number of lines depending on detail level
2358 if (_detailLevel>0) rItem->setMaxLines(0, 2*_detailLevel);
2359 _scene->addItem(rItem);
2360 n->setCanvasNode(rItem);
2361
2362 if (n) {
2363 if (n->function() == activeItem())
2364 activeNode = n;
2365 if (n->function() == selectedItem())
2366 _selectedNode = n;
2367 rItem->setSelected(n == _selectedNode);
2368 }
2369
2370 rItem->setZValue(1.0);
2371 rItem->show();
2372
2373 continue;
2374 }
2375
2376 // edge
2377
2378 QString node1Name, node2Name, label, edgeX, edgeY;
2379 double x, y;
2380 QPolygon poly;
2381 int points, i;
2382 lineStream >> node1Name >> node2Name >> points;
2383
2384 GraphEdge* e = _exporter.edge(_exporter.toFunc(node1Name),
2385 _exporter.toFunc(node2Name));
2386 if (!e) {
2387 qDebug() << "Unknown edge '"<< node1Name << "'-'"<< node2Name
2388 << "' from dot ("<< _exporter.filename() << ":"<< lineno
2389 << ")";
2390 continue;
2391 }
2392 e->setVisible(true);
2393 if (e->fromNode())
2394 e->fromNode()->addCallee(e);
2395 if (e->toNode())
2396 e->toNode()->addCaller(e);
2397
2398 if (0)
2399 qDebug(" Edge with %d points:", points);
2400
2401 poly.resize(points);
2402 for (i=0; i<points; ++i) {
2403 if (lineStream.atEnd())
2404 break;
2405 lineStream >> edgeX >> edgeY;
2406 x = edgeX.toDouble();
2407 y = edgeY.toDouble();
2408
2409 int xx = (int)(scaleX * x + _xMargin);
2410 int yy = (int)(scaleY * (dotHeight - y)+ _yMargin);
2411
2412 if (0)
2413 qDebug(" P %d: ( %f / %f ) => ( %d / %d)", i, x, y, xx, yy);
2414
2415 poly.setPoint(i, xx, yy);
2416 }
2417 if (i < points) {
2418 qDebug("CallGraphView: Can not read %d spline points (%s:%d)",
2419 points, qPrintable(_exporter.filename()), lineno);
2420 continue;
2421 }
2422
2423 // calls into/out of cycles are special: make them blue
2424 QColor arrowColor = Qt::black;
2425 TraceFunction* caller = e->fromNode() ? e->fromNode()->function() : nullptr;
2426 TraceFunction* called = e->toNode() ? e->toNode()->function() : nullptr;
2427 if ( (caller && (caller->cycle() == caller)) ||
2428 (called && (called->cycle() == called)) ) arrowColor = Qt::blue;
2429
2430 sItem = new CanvasEdge(e);
2431 _scene->addItem(sItem);
2432 e->setCanvasEdge(sItem);
2433 sItem->setControlPoints(poly);
2434 // width of pen will be adjusted in CanvasEdge::paint()
2435 sItem->setPen(QPen(arrowColor));
2436 sItem->setZValue(0.5);
2437 sItem->show();
2438
2439 if (e->call() == selectedItem())
2440 _selectedEdge = e;
2441 if (e->call() == activeItem())
2442 activeEdge = e;
2443 sItem->setSelected(e == _selectedEdge);
2444
2445 // Arrow head
2446 QPoint arrowDir;
2447 int indexHead = -1;
2448
2449 // check if head is at start of spline...
2450 // this is needed because dot always gives points from top to bottom
2451 CanvasNode* fromNode = e->fromNode() ? e->fromNode()->canvasNode() : nullptr;
2452 if (fromNode) {
2453 QPointF toCenter = fromNode->rect().center();
2454 qreal dx0 = poly.point(0).x() - toCenter.x();
2455 qreal dy0 = poly.point(0).y() - toCenter.y();
2456 qreal dx1 = poly.point(points-1).x() - toCenter.x();
2457 qreal dy1 = poly.point(points-1).y() - toCenter.y();
2458 if (dx0*dx0+dy0*dy0 > dx1*dx1+dy1*dy1) {
2459 // start of spline is nearer to call target node
2460 indexHead=-1;
2461 while (arrowDir.isNull() && (indexHead<points-2)) {
2462 indexHead++;
2463 arrowDir = poly.point(indexHead) - poly.point(indexHead+1);
2464 }
2465 }
2466 }
2467
2468 if (arrowDir.isNull()) {
2469 indexHead = points;
2470 // sometimes the last spline points from dot are the same...
2471 while (arrowDir.isNull() && (indexHead>1)) {
2472 indexHead--;
2473 arrowDir = poly.point(indexHead) - poly.point(indexHead-1);
2474 }
2475 }
2476
2477 if (!arrowDir.isNull()) {
2478 // arrow around pa.point(indexHead) with direction arrowDir
2479 arrowDir *= 10.0/sqrt(double(arrowDir.x()*arrowDir.x() +
2480 arrowDir.y()*arrowDir.y()));
2481
2482 QPolygonF a;
2483 a << QPointF(poly.point(indexHead) + arrowDir);
2484 a << QPointF(poly.point(indexHead) + QPoint(arrowDir.y()/2,
2485 -arrowDir.x()/2));
2486 a << QPointF(poly.point(indexHead) + QPoint(-arrowDir.y()/2,
2487 arrowDir.x()/2));
2488
2489 if (0)
2490 qDebug(" Arrow: ( %f/%f, %f/%f, %f/%f)", a[0].x(), a[0].y(),
2491 a[1].x(), a[1].y(), a[2].x(), a[1].y());
2492
2493 CanvasEdgeArrow* aItem = new CanvasEdgeArrow(sItem);
2494 _scene->addItem(aItem);
2495 aItem->setPolygon(a);
2496 aItem->setBrush(arrowColor);
2497 aItem->setZValue(1.5);
2498 aItem->show();
2499
2500 sItem->setArrow(aItem);
2501 }
2502
2503 if (lineStream.atEnd())
2504 continue;
2505
2506 // parse quoted label
2507 QChar c;
2508 lineStream >> c;
2509 while (c.isSpace())
2510 lineStream >> c;
2511 if (c != '\"') {
2512 lineStream >> label;
2513 label = c + label;
2514 } else {
2515 lineStream >> c;
2516 while (!c.isNull() && (c != '\"')) {
2517 //if (c == '\\') lineStream >> c;
2518
2519 label += c;
2520 lineStream >> c;
2521 }
2522 }
2523 lineStream >> edgeX >> edgeY;
2524 x = edgeX.toDouble();
2525 y = edgeY.toDouble();
2526
2527 int xx = (int)(scaleX * x + _xMargin);
2528 int yy = (int)(scaleY * (dotHeight - y)+ _yMargin);
2529
2530 if (0)
2531 qDebug(" Label '%s': ( %f / %f ) => ( %d / %d)",
2532 qPrintable(label), x, y, xx, yy);
2533
2534 // Fixed Dimensions for Label: 100 x 40
2535 int w = 100;
2536 int h = _detailLevel * 20;
2537 lItem = new CanvasEdgeLabel(this, sItem, xx-w/2, yy-h/2, w, h);
2538 _scene->addItem(lItem);
2539 // edge labels above nodes
2540 lItem->setZValue(1.5);
2541 sItem->setLabel(lItem);
2542 if (h>0)
2543 lItem->show();
2544
2545 }
2546 delete dotStream;
2547
2548 // for keyboard navigation
2549 _exporter.sortEdges();
2550
2551 if (!_scene) {
2552 _scene = new QGraphicsScene;
2553
2554 QString s = tr("Error running the graph layouting tool.\n");
2555 s += tr("Please check that 'dot' is installed (package GraphViz).");
2556 _scene->addSimpleText(s);
2557 centerOn(0, 0);
2558 } else if (!activeNode && !activeEdge) {
2559 QString s = tr("There is no call graph available for function\n"
2560 "\t'%1'\n"
2561 "because it has no cost of the selected event type.")
2562 .arg(_activeItem->name());
2563 _scene->addSimpleText(s);
2564 centerOn(0, 0);
2565 }
2566
2567 _panningView->setScene(_scene);
2568 setScene(_scene);
2569
2570 // if we do not have a selection, or the old selection is not
2571 // in visible graph, make active function selected for this view
2572 if ((!_selectedNode || !_selectedNode->canvasNode()) &&
2573 (!_selectedEdge || !_selectedEdge->canvasEdge())) {
2574 if (activeNode) {
2575 _selectedNode = activeNode;
2576 _selectedNode->canvasNode()->setSelected(true);
2577 } else if (activeEdge) {
2578 _selectedEdge = activeEdge;
2579 _selectedEdge->canvasEdge()->setSelected(true);
2580 }
2581 }
2582
2583 CanvasNode* sNode = nullptr;
2584 if (_selectedNode)
2585 sNode = _selectedNode->canvasNode();
2586 else if (_selectedEdge) {
2587 if (_selectedEdge->fromNode())
2588 sNode = _selectedEdge->fromNode()->canvasNode();
2589 if (!sNode && _selectedEdge->toNode())
2590 sNode = _selectedEdge->toNode()->canvasNode();
2591 }
2592
2593 if (sNode) {
2594 if (_prevSelectedNode) {
2595 QPointF prevPos = mapToScene(_prevSelectedPos);
2596 if (rect().contains(_prevSelectedPos)) {
2597 QPointF wCenter = mapToScene(viewport()->rect().center());
2598 centerOn(sNode->rect().center() +
2599 wCenter - prevPos);
2600 }
2601 else
2602 ensureVisible(sNode);
2603 } else
2604 centerOn(sNode);
2605 }
2606
2607 if (activeNode) {
2608 CanvasNode* cn = activeNode->canvasNode();
2609 CanvasFrame* f = new CanvasFrame(cn);
2610 _scene->addItem(f);
2611 f->setPos(cn->pos());
2612 f->setZValue(-1);
2613 }
2614
2615 _panningZoom = 0;
2616 updateSizes();
2617
2618 _scene->update();
2619 viewport()->setUpdatesEnabled(true);
2620
2621 delete _renderProcess;
2622 _renderProcess = nullptr;
2623 }
2624
2625
2626 // Called by QAbstractScrollArea to notify about scrollbar changes
scrollContentsBy(int dx,int dy)2627 void CallGraphView::scrollContentsBy(int dx, int dy)
2628 {
2629 // call QGraphicsView implementation
2630 QGraphicsView::scrollContentsBy(dx, dy);
2631
2632 QPointF topLeft = mapToScene(QPoint(0, 0));
2633 QPointF bottomRight = mapToScene(QPoint(width(), height()));
2634
2635 QRectF z(topLeft, bottomRight);
2636 if (0)
2637 qDebug("CallGraphView::scrollContentsBy(dx %d, dy %d) - to (%f,%f - %f,%f)",
2638 dx, dy, topLeft.x(), topLeft.y(),
2639 bottomRight.x(), bottomRight.y());
2640 _panningView->setZoomRect(z);
2641 }
2642
zoomRectMoved(qreal dx,qreal dy)2643 void CallGraphView::zoomRectMoved(qreal dx, qreal dy)
2644 {
2645 //FIXME if (leftMargin()>0) dx = 0;
2646 //FIXME if (topMargin()>0) dy = 0;
2647
2648 QScrollBar *hBar = horizontalScrollBar();
2649 QScrollBar *vBar = verticalScrollBar();
2650 hBar->setValue(hBar->value() + int(dx));
2651 vBar->setValue(vBar->value() + int(dy));
2652 }
2653
zoomRectMoveFinished()2654 void CallGraphView::zoomRectMoveFinished()
2655 {
2656 if (_zoomPosition == Auto)
2657 updateSizes();
2658 }
2659
mousePressEvent(QMouseEvent * e)2660 void CallGraphView::mousePressEvent(QMouseEvent* e)
2661 {
2662 // clicking on the viewport sets focus
2663 setFocus();
2664
2665 // activate scroll mode on left click
2666 if (e->button() == Qt::LeftButton) _isMoving = true;
2667
2668 QGraphicsItem* i = itemAt(e->pos());
2669 if (i) {
2670
2671 if (i->type() == CANVAS_NODE) {
2672 GraphNode* n = ((CanvasNode*)i)->node();
2673 if (0)
2674 qDebug("CallGraphView: Got Node '%s'",
2675 qPrintable(n->function()->prettyName()));
2676
2677 selected(n->function());
2678 }
2679
2680 // redirect from label / arrow to edge
2681 if (i->type() == CANVAS_EDGELABEL)
2682 i = ((CanvasEdgeLabel*)i)->canvasEdge();
2683 if (i->type() == CANVAS_EDGEARROW)
2684 i = ((CanvasEdgeArrow*)i)->canvasEdge();
2685
2686 if (i->type() == CANVAS_EDGE) {
2687 GraphEdge* e = ((CanvasEdge*)i)->edge();
2688 if (0)
2689 qDebug("CallGraphView: Got Edge '%s'",
2690 qPrintable(e->prettyName()));
2691
2692 if (e->call())
2693 selected(e->call());
2694 }
2695 }
2696 _lastPos = e->pos();
2697 }
2698
mouseMoveEvent(QMouseEvent * e)2699 void CallGraphView::mouseMoveEvent(QMouseEvent* e)
2700 {
2701 if (_isMoving) {
2702 QPoint delta = e->pos() - _lastPos;
2703
2704 QScrollBar *hBar = horizontalScrollBar();
2705 QScrollBar *vBar = verticalScrollBar();
2706 hBar->setValue(hBar->value() - delta.x());
2707 vBar->setValue(vBar->value() - delta.y());
2708
2709 _lastPos = e->pos();
2710 }
2711 }
2712
mouseReleaseEvent(QMouseEvent *)2713 void CallGraphView::mouseReleaseEvent(QMouseEvent*)
2714 {
2715 _isMoving = false;
2716 if (_zoomPosition == Auto)
2717 updateSizes();
2718 }
2719
mouseDoubleClickEvent(QMouseEvent * e)2720 void CallGraphView::mouseDoubleClickEvent(QMouseEvent* e)
2721 {
2722 QGraphicsItem* i = itemAt(e->pos());
2723 if (i == nullptr)
2724 return;
2725
2726 if (i->type() == CANVAS_NODE) {
2727 GraphNode* n = ((CanvasNode*)i)->node();
2728 if (0)
2729 qDebug("CallGraphView: Double Clicked on Node '%s'",
2730 qPrintable(n->function()->prettyName()));
2731
2732 activated(n->function());
2733 }
2734
2735 // redirect from label / arrow to edge
2736 if (i->type() == CANVAS_EDGELABEL)
2737 i = ((CanvasEdgeLabel*)i)->canvasEdge();
2738 if (i->type() == CANVAS_EDGEARROW)
2739 i = ((CanvasEdgeArrow*)i)->canvasEdge();
2740
2741 if (i->type() == CANVAS_EDGE) {
2742 GraphEdge* e = ((CanvasEdge*)i)->edge();
2743 if (e->call()) {
2744 if (0)
2745 qDebug("CallGraphView: Double Clicked On Edge '%s'",
2746 qPrintable(e->call()->prettyName()));
2747
2748 activated(e->call());
2749 }
2750 }
2751 }
2752
2753 // helper functions for context menu:
2754 // submenu builders and trigger handlers
2755
addCallerDepthAction(QMenu * m,QString s,int d)2756 QAction* CallGraphView::addCallerDepthAction(QMenu* m, QString s, int d)
2757 {
2758 QAction* a;
2759
2760 a = m->addAction(s);
2761 a->setData(d);
2762 a->setCheckable(true);
2763 a->setChecked(_maxCallerDepth == d);
2764
2765 return a;
2766 }
2767
addCallerDepthMenu(QMenu * menu)2768 QMenu* CallGraphView::addCallerDepthMenu(QMenu* menu)
2769 {
2770 QAction* a;
2771 QMenu* m;
2772
2773 m = menu->addMenu(tr("Caller Depth"));
2774 a = addCallerDepthAction(m, tr("Unlimited"), -1);
2775 a->setEnabled(_funcLimit>0.005);
2776 m->addSeparator();
2777 addCallerDepthAction(m, tr("Depth 0", "None"), 0);
2778 addCallerDepthAction(m, tr("max. 1"), 1);
2779 addCallerDepthAction(m, tr("max. 2"), 2);
2780 addCallerDepthAction(m, tr("max. 5"), 5);
2781 addCallerDepthAction(m, tr("max. 10"), 10);
2782 addCallerDepthAction(m, tr("max. 15"), 15);
2783
2784 connect(m, &QMenu::triggered,
2785 this, &CallGraphView::callerDepthTriggered );
2786
2787 return m;
2788 }
2789
callerDepthTriggered(QAction * a)2790 void CallGraphView::callerDepthTriggered(QAction* a)
2791 {
2792 _maxCallerDepth = a->data().toInt(nullptr);
2793 refresh();
2794 }
2795
addCalleeDepthAction(QMenu * m,QString s,int d)2796 QAction* CallGraphView::addCalleeDepthAction(QMenu* m, QString s, int d)
2797 {
2798 QAction* a;
2799
2800 a = m->addAction(s);
2801 a->setData(d);
2802 a->setCheckable(true);
2803 a->setChecked(_maxCalleeDepth == d);
2804
2805 return a;
2806 }
2807
addCalleeDepthMenu(QMenu * menu)2808 QMenu* CallGraphView::addCalleeDepthMenu(QMenu* menu)
2809 {
2810 QAction* a;
2811 QMenu* m;
2812
2813 m = menu->addMenu(tr("Callee Depth"));
2814 a = addCalleeDepthAction(m, tr("Unlimited"), -1);
2815 a->setEnabled(_funcLimit>0.005);
2816 m->addSeparator();
2817 addCalleeDepthAction(m, tr("Depth 0", "None"), 0);
2818 addCalleeDepthAction(m, tr("max. 1"), 1);
2819 addCalleeDepthAction(m, tr("max. 2"), 2);
2820 addCalleeDepthAction(m, tr("max. 5"), 5);
2821 addCalleeDepthAction(m, tr("max. 10"), 10);
2822 addCalleeDepthAction(m, tr("max. 15"), 15);
2823
2824 connect(m, &QMenu::triggered,
2825 this, &CallGraphView::calleeDepthTriggered );
2826
2827 return m;
2828 }
2829
calleeDepthTriggered(QAction * a)2830 void CallGraphView::calleeDepthTriggered(QAction* a)
2831 {
2832 _maxCalleeDepth = a->data().toInt(nullptr);
2833 refresh();
2834 }
2835
addNodeLimitAction(QMenu * m,QString s,double l)2836 QAction* CallGraphView::addNodeLimitAction(QMenu* m, QString s, double l)
2837 {
2838 QAction* a;
2839
2840 a = m->addAction(s);
2841 a->setData(l);
2842 a->setCheckable(true);
2843 a->setChecked(_funcLimit == l);
2844
2845 return a;
2846 }
2847
addNodeLimitMenu(QMenu * menu)2848 QMenu* CallGraphView::addNodeLimitMenu(QMenu* menu)
2849 {
2850 QAction* a;
2851 QMenu* m;
2852
2853 m = menu->addMenu(tr("Min. Node Cost"));
2854 a = addNodeLimitAction(m, tr("No Minimum"), 0.0);
2855 // Unlimited node cost easily produces huge graphs such that 'dot'
2856 // would need a long time to layout. For responsiveness, we only allow
2857 // for unlimited node cost if a caller and callee depth limit is set.
2858 a->setEnabled((_maxCallerDepth>=0) && (_maxCalleeDepth>=0));
2859 m->addSeparator();
2860 addNodeLimitAction(m, tr("50 %"), .5);
2861 addNodeLimitAction(m, tr("20 %"), .2);
2862 addNodeLimitAction(m, tr("10 %"), .1);
2863 addNodeLimitAction(m, tr("5 %"), .05);
2864 addNodeLimitAction(m, tr("2 %"), .02);
2865 addNodeLimitAction(m, tr("1 %"), .01);
2866
2867 connect(m, &QMenu::triggered,
2868 this, &CallGraphView::nodeLimitTriggered );
2869
2870 return m;
2871 }
2872
nodeLimitTriggered(QAction * a)2873 void CallGraphView::nodeLimitTriggered(QAction* a)
2874 {
2875 _funcLimit = a->data().toDouble(nullptr);
2876 refresh();
2877 }
2878
addCallLimitAction(QMenu * m,QString s,double l)2879 QAction* CallGraphView::addCallLimitAction(QMenu* m, QString s, double l)
2880 {
2881 QAction* a;
2882
2883 a = m->addAction(s);
2884 a->setData(l);
2885 a->setCheckable(true);
2886 a->setChecked(_callLimit == l);
2887
2888 return a;
2889 }
2890
addCallLimitMenu(QMenu * menu)2891 QMenu* CallGraphView::addCallLimitMenu(QMenu* menu)
2892 {
2893 QMenu* m;
2894
2895 m = menu->addMenu(tr("Min. Call Cost"));
2896 addCallLimitAction(m, tr("Same as Node"), 1.0);
2897 // xgettext: no-c-format
2898 addCallLimitAction(m, tr("50 % of Node"), .5);
2899 // xgettext: no-c-format
2900 addCallLimitAction(m, tr("20 % of Node"), .2);
2901 // xgettext: no-c-format
2902 addCallLimitAction(m, tr("10 % of Node"), .1);
2903
2904 connect(m, &QMenu::triggered,
2905 this, &CallGraphView::callLimitTriggered );
2906
2907 return m;
2908 }
2909
callLimitTriggered(QAction * a)2910 void CallGraphView::callLimitTriggered(QAction* a)
2911 {
2912 _callLimit = a->data().toDouble(nullptr);
2913 refresh();
2914 }
2915
addZoomPosAction(QMenu * m,QString s,CallGraphView::ZoomPosition p)2916 QAction* CallGraphView::addZoomPosAction(QMenu* m, QString s,
2917 CallGraphView::ZoomPosition p)
2918 {
2919 QAction* a;
2920
2921 a = m->addAction(s);
2922 a->setData((int)p);
2923 a->setCheckable(true);
2924 a->setChecked(_zoomPosition == p);
2925
2926 return a;
2927 }
2928
addZoomPosMenu(QMenu * menu)2929 QMenu* CallGraphView::addZoomPosMenu(QMenu* menu)
2930 {
2931 QMenu* m = menu->addMenu(tr("Birds-eye View"));
2932 addZoomPosAction(m, tr("Top Left"), TopLeft);
2933 addZoomPosAction(m, tr("Top Right"), TopRight);
2934 addZoomPosAction(m, tr("Bottom Left"), BottomLeft);
2935 addZoomPosAction(m, tr("Bottom Right"), BottomRight);
2936 addZoomPosAction(m, tr("Automatic"), Auto);
2937 addZoomPosAction(m, tr("Hide"), Hide);
2938
2939 connect(m, &QMenu::triggered,
2940 this, &CallGraphView::zoomPosTriggered );
2941
2942 return m;
2943 }
2944
2945
zoomPosTriggered(QAction * a)2946 void CallGraphView::zoomPosTriggered(QAction* a)
2947 {
2948 _zoomPosition = (ZoomPosition) a->data().toInt(nullptr);
2949 updateSizes();
2950 }
2951
addLayoutAction(QMenu * m,QString s,GraphOptions::Layout l)2952 QAction* CallGraphView::addLayoutAction(QMenu* m, QString s,
2953 GraphOptions::Layout l)
2954 {
2955 QAction* a;
2956
2957 a = m->addAction(s);
2958 a->setData((int)l);
2959 a->setCheckable(true);
2960 a->setChecked(_layout == l);
2961
2962 return a;
2963 }
2964
addLayoutMenu(QMenu * menu)2965 QMenu* CallGraphView::addLayoutMenu(QMenu* menu)
2966 {
2967 QMenu* m = menu->addMenu(tr("Layout"));
2968 addLayoutAction(m, tr("Top to Down"), TopDown);
2969 addLayoutAction(m, tr("Left to Right"), LeftRight);
2970 addLayoutAction(m, tr("Circular"), Circular);
2971
2972 connect(m, &QMenu::triggered,
2973 this, &CallGraphView::layoutTriggered );
2974
2975 return m;
2976 }
2977
2978
layoutTriggered(QAction * a)2979 void CallGraphView::layoutTriggered(QAction* a)
2980 {
2981 _layout = (Layout) a->data().toInt(nullptr);
2982 refresh();
2983 }
2984
2985
contextMenuEvent(QContextMenuEvent * e)2986 void CallGraphView::contextMenuEvent(QContextMenuEvent* e)
2987 {
2988 _isMoving = false;
2989
2990 QGraphicsItem* i = itemAt(e->pos());
2991
2992 QMenu popup;
2993 TraceFunction *f = nullptr, *cycle = nullptr;
2994 TraceCall* c = nullptr;
2995
2996 QAction* activateFunction = nullptr;
2997 QAction* activateCycle = nullptr;
2998 QAction* activateCall = nullptr;
2999 if (i) {
3000 if (i->type() == CANVAS_NODE) {
3001 GraphNode* n = ((CanvasNode*)i)->node();
3002 if (0)
3003 qDebug("CallGraphView: Menu on Node '%s'",
3004 qPrintable(n->function()->prettyName()));
3005
3006 f = n->function();
3007 cycle = f->cycle();
3008
3009 QString name = f->prettyName();
3010 QString menuStr = tr("Go to '%1'")
3011 .arg(GlobalConfig::shortenSymbol(name));
3012 activateFunction = popup.addAction(menuStr);
3013 if (cycle && (cycle != f)) {
3014 name = GlobalConfig::shortenSymbol(cycle->prettyName());
3015 activateCycle = popup.addAction(tr("Go to '%1'").arg(name));
3016 }
3017 popup.addSeparator();
3018 }
3019
3020 // redirect from label / arrow to edge
3021 if (i->type() == CANVAS_EDGELABEL)
3022 i = ((CanvasEdgeLabel*)i)->canvasEdge();
3023 if (i->type() == CANVAS_EDGEARROW)
3024 i = ((CanvasEdgeArrow*)i)->canvasEdge();
3025
3026 if (i->type() == CANVAS_EDGE) {
3027 GraphEdge* e = ((CanvasEdge*)i)->edge();
3028 if (0)
3029 qDebug("CallGraphView: Menu on Edge '%s'",
3030 qPrintable(e->prettyName()));
3031
3032 c = e->call();
3033 if (c) {
3034 QString name = c->prettyName();
3035 QString menuStr = tr("Go to '%1'")
3036 .arg(GlobalConfig::shortenSymbol(name));
3037 activateCall = popup.addAction(menuStr);
3038
3039 popup.addSeparator();
3040 }
3041 }
3042 }
3043
3044 QAction* stopLayout = nullptr;
3045 if (_renderProcess) {
3046 stopLayout = popup.addAction(tr("Stop Layouting"));
3047 popup.addSeparator();
3048 }
3049
3050 addGoMenu(&popup);
3051 popup.addSeparator();
3052
3053 QMenu* epopup = popup.addMenu(tr("Export Graph"));
3054 QAction* exportAsDot = epopup->addAction(tr("As DOT file..."));
3055 QAction* exportAsImage = epopup->addAction(tr("As Image..."));
3056 popup.addSeparator();
3057
3058 QMenu* gpopup = popup.addMenu(tr("Graph"));
3059 addCallerDepthMenu(gpopup);
3060 addCalleeDepthMenu(gpopup);
3061 addNodeLimitMenu(gpopup);
3062 addCallLimitMenu(gpopup);
3063 gpopup->addSeparator();
3064
3065 QAction* toggleSkipped;
3066 toggleSkipped = gpopup->addAction(tr("Arrows for Skipped Calls"));
3067 toggleSkipped->setCheckable(true);
3068 toggleSkipped->setChecked(_showSkipped);
3069
3070 QAction* toggleExpand;
3071 toggleExpand = gpopup->addAction(tr("Inner-cycle Calls"));
3072 toggleExpand->setCheckable(true);
3073 toggleExpand->setChecked(_expandCycles);
3074
3075 QAction* toggleCluster;
3076 toggleCluster = gpopup->addAction(tr("Cluster Groups"));
3077 toggleCluster->setCheckable(true);
3078 toggleCluster->setChecked(_clusterGroups);
3079
3080 QMenu* vpopup = popup.addMenu(tr("Visualization"));
3081 QAction* layoutCompact = vpopup->addAction(tr("Compact"));
3082 layoutCompact->setCheckable(true);
3083 layoutCompact->setChecked(_detailLevel == 0);
3084 QAction* layoutNormal = vpopup->addAction(tr("Normal"));
3085 layoutNormal->setCheckable(true);
3086 layoutNormal->setChecked(_detailLevel == 1);
3087 QAction* layoutTall = vpopup->addAction(tr("Tall"));
3088 layoutTall->setCheckable(true);
3089 layoutTall->setChecked(_detailLevel == 2);
3090
3091 addLayoutMenu(&popup);
3092 addZoomPosMenu(&popup);
3093
3094 QAction* a = popup.exec(e->globalPos());
3095
3096 if (a == activateFunction)
3097 activated(f);
3098 else if (a == activateCycle)
3099 activated(cycle);
3100 else if (a == activateCall)
3101 activated(c);
3102
3103 else if (a == stopLayout)
3104 stopRendering();
3105
3106 else if (a == exportAsDot) {
3107 TraceFunction* f = activeFunction();
3108 if (!f) return;
3109
3110 GraphExporter::savePrompt(this, TraceItemView::data(), f, eventType(),
3111 groupType(), this);
3112 }
3113 else if (a == exportAsImage) {
3114 // write current content of canvas as image to file
3115 if (!_scene) return;
3116
3117 QString n;
3118 n = QFileDialog::getSaveFileName(this,
3119 tr("Export Graph As Image"),
3120 QString(),
3121 tr("Images (*.png *.jpg)"));
3122
3123 if (!n.isEmpty()) {
3124 QRect r = _scene->sceneRect().toRect();
3125 QPixmap pix(r.width(), r.height());
3126 QPainter p(&pix);
3127 _scene->render( &p );
3128 pix.save(n);
3129 }
3130 }
3131
3132 else if (a == toggleSkipped) {
3133 _showSkipped = !_showSkipped;
3134 refresh();
3135 }
3136 else if (a == toggleExpand) {
3137 _expandCycles = !_expandCycles;
3138 refresh();
3139 }
3140 else if (a == toggleCluster) {
3141 _clusterGroups = !_clusterGroups;
3142 refresh();
3143 }
3144
3145 else if (a == layoutCompact) {
3146 _detailLevel = 0;
3147 refresh();
3148 }
3149 else if (a == layoutNormal) {
3150 _detailLevel = 1;
3151 refresh();
3152 }
3153 else if (a == layoutTall) {
3154 _detailLevel = 2;
3155 refresh();
3156 }
3157 }
3158
3159
zoomPos(QString s)3160 CallGraphView::ZoomPosition CallGraphView::zoomPos(QString s)
3161 {
3162 if (s == QLatin1String("TopLeft"))
3163 return TopLeft;
3164 if (s == QLatin1String("TopRight"))
3165 return TopRight;
3166 if (s == QLatin1String("BottomLeft"))
3167 return BottomLeft;
3168 if (s == QLatin1String("BottomRight"))
3169 return BottomRight;
3170 if (s == QLatin1String("Automatic"))
3171 return Auto;
3172 if (s == QLatin1String("Hide"))
3173 return Hide;
3174
3175 return DEFAULT_ZOOMPOS;
3176 }
3177
zoomPosString(ZoomPosition p)3178 QString CallGraphView::zoomPosString(ZoomPosition p)
3179 {
3180 if (p == TopLeft)
3181 return QStringLiteral("TopLeft");
3182 if (p == TopRight)
3183 return QStringLiteral("TopRight");
3184 if (p == BottomLeft)
3185 return QStringLiteral("BottomLeft");
3186 if (p == BottomRight)
3187 return QStringLiteral("BottomRight");
3188 if (p == Auto)
3189 return QStringLiteral("Automatic");
3190 if (p == Hide)
3191 return QStringLiteral("Hide");
3192
3193 return QString();
3194 }
3195
restoreOptions(const QString & prefix,const QString & postfix)3196 void CallGraphView::restoreOptions(const QString& prefix, const QString& postfix)
3197 {
3198 ConfigGroup* g = ConfigStorage::group(prefix, postfix);
3199
3200 _maxCallerDepth = g->value(QStringLiteral("MaxCaller"), DEFAULT_MAXCALLER).toInt();
3201 _maxCalleeDepth = g->value(QStringLiteral("MaxCallee"), DEFAULT_MAXCALLEE).toInt();
3202 _funcLimit = g->value(QStringLiteral("FuncLimit"), DEFAULT_FUNCLIMIT).toDouble();
3203 _callLimit = g->value(QStringLiteral("CallLimit"), DEFAULT_CALLLIMIT).toDouble();
3204 _showSkipped = g->value(QStringLiteral("ShowSkipped"), DEFAULT_SHOWSKIPPED).toBool();
3205 _expandCycles = g->value(QStringLiteral("ExpandCycles"), DEFAULT_EXPANDCYCLES).toBool();
3206 _clusterGroups = g->value(QStringLiteral("ClusterGroups"), DEFAULT_CLUSTERGROUPS).toBool();
3207 _detailLevel = g->value(QStringLiteral("DetailLevel"), DEFAULT_DETAILLEVEL).toInt();
3208 _layout = GraphOptions::layout(g->value(QStringLiteral("Layout"),
3209 layoutString(DEFAULT_LAYOUT)).toString());
3210 _zoomPosition = zoomPos(g->value(QStringLiteral("ZoomPosition"),
3211 zoomPosString(DEFAULT_ZOOMPOS)).toString());
3212
3213 delete g;
3214 }
3215
saveOptions(const QString & prefix,const QString & postfix)3216 void CallGraphView::saveOptions(const QString& prefix, const QString& postfix)
3217 {
3218 ConfigGroup* g = ConfigStorage::group(prefix + postfix);
3219
3220 g->setValue(QStringLiteral("MaxCaller"), _maxCallerDepth, DEFAULT_MAXCALLER);
3221 g->setValue(QStringLiteral("MaxCallee"), _maxCalleeDepth, DEFAULT_MAXCALLEE);
3222 g->setValue(QStringLiteral("FuncLimit"), _funcLimit, DEFAULT_FUNCLIMIT);
3223 g->setValue(QStringLiteral("CallLimit"), _callLimit, DEFAULT_CALLLIMIT);
3224 g->setValue(QStringLiteral("ShowSkipped"), _showSkipped, DEFAULT_SHOWSKIPPED);
3225 g->setValue(QStringLiteral("ExpandCycles"), _expandCycles, DEFAULT_EXPANDCYCLES);
3226 g->setValue(QStringLiteral("ClusterGroups"), _clusterGroups, DEFAULT_CLUSTERGROUPS);
3227 g->setValue(QStringLiteral("DetailLevel"), _detailLevel, DEFAULT_DETAILLEVEL);
3228 g->setValue(QStringLiteral("Layout"), layoutString(_layout), layoutString(DEFAULT_LAYOUT));
3229 g->setValue(QStringLiteral("ZoomPosition"), zoomPosString(_zoomPosition),
3230 zoomPosString(DEFAULT_ZOOMPOS));
3231
3232 delete g;
3233 }
3234
3235
3236