2 * Copyright (c) 2012, Red Hat.
3 * Copyright (c) 2012, Nathan Scott. All Rights Reserved.
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version.
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15 #include <qwt_plot_directpainter.h>
16 #include <qwt_picker_machine.h>
17 #include <qwt_plot_marker.h>
18 #include <qwt_symbol.h>
24 TracingItem::TracingItem(Chart
*chart
,
25 QmcMetric
*mp
, pmMetricSpec
*msp
, pmDesc
*dp
, const QString
&legend
)
26 : ChartItem(mp
, msp
, dp
, legend
)
32 limit
= mp
->context()->source().start();
33 my
.minSpanTime
= __pmtimevalToReal(&limit
);
34 if (mp
->context()->source().isArchive()) {
35 limit
= mp
->context()->source().end();
36 my
.maxSpanTime
= __pmtimevalToReal(&limit
);
39 my
.maxSpanTime
= my
.minSpanTime
* 1.1;
41 my
.spanSymbol
= new QwtIntervalSymbol(QwtIntervalSymbol::Box
);
42 my
.spanCurve
= new QwtPlotIntervalCurve(label());
43 my
.spanCurve
->setItemAttribute(QwtPlotItem::Legend
, false);
44 my
.spanCurve
->setStyle(QwtPlotIntervalCurve::NoCurve
);
45 my
.spanCurve
->setOrientation(Qt::Horizontal
);
46 my
.spanCurve
->setSymbol(my
.spanSymbol
);
47 my
.spanCurve
->setZ(1); // lowest/furthest
49 my
.dropSymbol
= new QwtIntervalSymbol(QwtIntervalSymbol::Box
);
50 my
.dropCurve
= new QwtPlotIntervalCurve(label());
51 my
.dropCurve
->setItemAttribute(QwtPlotItem::Legend
, false);
52 my
.dropCurve
->setStyle(QwtPlotIntervalCurve::NoCurve
);
53 my
.dropCurve
->setOrientation(Qt::Vertical
);
54 my
.dropCurve
->setSymbol(my
.dropSymbol
);
55 my
.dropCurve
->setZ(2); // middle/central
57 my
.pointSymbol
= new QwtSymbol(QwtSymbol::Ellipse
);
58 my
.pointCurve
= new ChartCurve(label());
59 my
.pointCurve
->setStyle(QwtPlotCurve::NoCurve
);
60 my
.pointCurve
->setSymbol(my
.pointSymbol
);
61 my
.pointCurve
->setZ(3); // higher/closer
63 my
.selectionSymbol
= new QwtSymbol(QwtSymbol::Ellipse
);
64 my
.selectionCurve
= new QwtPlotCurve(label());
65 my
.selectionCurve
->setItemAttribute(QwtPlotItem::Legend
, false);
66 my
.selectionCurve
->setStyle(QwtPlotCurve::NoCurve
);
67 my
.selectionCurve
->setSymbol(my
.selectionSymbol
);
68 my
.selectionCurve
->setZ(4); // highest/closest
70 my
.spanCurve
->attach(chart
);
71 my
.dropCurve
->attach(chart
);
72 my
.pointCurve
->attach(chart
);
73 my
.selectionCurve
->attach(chart
);
76 TracingItem::~TracingItem(void)
83 delete my
.pointSymbol
;
84 delete my
.selectionCurve
;
85 delete my
.selectionSymbol
;
89 TracingItem::item(void)
95 TracingItem::curve(void)
100 TracingEvent::TracingEvent(QmcEventRecord
const &record
, pmID pmid
, int inst
)
102 my
.timestamp
= __pmtimevalToReal(record
.timestamp());
103 my
.missed
= record
.missed();
104 my
.flags
= record
.flags();
107 my
.spanID
= record
.identifier();
108 my
.rootID
= record
.parent();
110 // details displayed about this record (on selection)
111 my
.description
.append(timeHiResString(my
.timestamp
));
112 my
.description
.append(": flags=");
113 my
.description
.append(pmEventFlagsStr(record
.flags()));
114 if (record
.missed()> 0) {
115 my
.description
.append(" (");
116 my
.description
.append(QString::number(record
.missed()));
117 my
.description
.append(" missed)");
119 my
.description
.append("\n");
120 record
.parameterSummary(my
.description
, inst
);
123 TracingEvent::~TracingEvent()
129 TracingItem::rescaleValues(double *minValue
, double *maxValue
)
131 if (my
.minSpanID
< *minValue
)
132 *minValue
= my
.minSpanID
;
133 if (my
.maxSpanID
> *maxValue
)
134 *maxValue
= my
.maxSpanID
;
138 // Walk the vectors/lists and drop no-longer-needed events.
139 // For points/events/drops (single time value):
140 // Events arrive time-ordered, so we can short-circuit these
141 // walks once we are within the time window. Two phases -
142 // walk once from the left, then a subsequent walk from the
143 // right (note: done *after* we modify the original vector)
144 // For horizonal spans (i.e. two time values):
145 // This is still true - events arrive in order - but we have
146 // to walk the entire list as these ranges can overlap. But
147 // thats OK - we expect far fewer spans than total events.
151 TracingItem::cullOutlyingSpans(double left
, double right
)
153 // Start from the end so that we can remove as we go
154 // without interfering with the index we are using.
155 for (int i
= my
.spans
.size() - 1; i
>= 0; i
--) {
156 const QwtIntervalSample
&span
= my
.spans
.at(i
);
157 if (span
.interval
.maxValue() >= left
||
158 span
.interval
.minValue() <= right
)
165 TracingItem::cullOutlyingDrops(double left
, double right
)
169 for (i
= cull
= 0; i
< my
.drops
.size(); i
++, cull
++)
170 if (my
.drops
.at(i
).value
>= left
)
173 my
.drops
.remove(0, cull
); // cull from the start (0-index)
174 for (i
= my
.drops
.size() - 1, cull
= 0; i
>= 0; i
--, cull
++)
175 if (my
.drops
.at(i
).value
<= right
)
178 my
.drops
.remove(my
.drops
.size() - cull
, cull
); // cull from end
182 TracingItem::cullOutlyingPoints(double left
, double right
)
186 for (i
= cull
= 0; i
< my
.points
.size(); i
++, cull
++)
187 if (my
.points
.at(i
).x() >= left
)
190 my
.points
.remove(0, cull
); // cull from the start (0-index)
191 for (i
= my
.points
.size() - 1, cull
= 0; i
>= 0; i
--, cull
++)
192 if (my
.points
.at(i
).x() <= right
)
195 my
.points
.remove(my
.points
.size() - cull
, cull
); // cull from end
199 TracingItem::cullOutlyingEvents(double left
, double right
)
203 for (i
= cull
= 0; i
< my
.events
.size(); i
++, cull
++)
204 if (my
.events
.at(i
).timestamp() >= left
)
207 my
.events
.remove(0, cull
); // cull from the start (0-index)
208 for (i
= my
.events
.size() - 1, cull
= 0; i
>= 0; i
--, cull
++)
209 if (my
.events
.at(i
).timestamp() <= right
)
212 my
.events
.remove(my
.events
.size() - cull
, cull
); // cull from end
216 TracingItem::resetValues(int, double left
, double right
)
218 cullOutlyingSpans(left
, right
);
219 cullOutlyingDrops(left
, right
);
220 cullOutlyingPoints(left
, right
);
221 cullOutlyingEvents(left
, right
);
223 // update the display
224 my
.dropCurve
->setSamples(my
.drops
);
225 my
.spanCurve
->setSamples(my
.spans
);
226 my
.pointCurve
->setSamples(my
.points
);
227 my
.selectionCurve
->setSamples(my
.selections
);
231 // Requirement here is to merge in any event records that have
232 // just arrived in the last timestep (from my.metric) with the
233 // set already being displayed.
234 // Additionally, we need to cull those that are no longer within
235 // the time window of interest.
238 TracingItem::updateValues(TracingEngine
*engine
, double left
, double right
)
240 QmcMetric
*metric
= ChartItem::my
.metric
;
243 console
->post(PmChart::DebugForce
, "TracingItem::updateValues: "
244 "%d total events, left=%.2f right=%.2f\n"
245 "Metadata counts: drops=%d spans=%d points=%d "
246 "Metric values: %d count\n",
247 my
.events
.size(), left
, right
,
248 my
.drops
.size(), my
.spans
.size(), my
.points
.size(), metric
->numValues());
251 cullOutlyingSpans(left
, right
);
252 cullOutlyingDrops(left
, right
);
253 cullOutlyingPoints(left
, right
);
254 cullOutlyingEvents(left
, right
);
256 // crack open newly arrived event records
257 if (metric
->numValues() > 0)
258 updateEvents(engine
, metric
);
260 // update the display
261 my
.dropCurve
->setSamples(my
.drops
);
262 my
.spanCurve
->setSamples(my
.spans
);
263 my
.pointCurve
->setSamples(my
.points
);
264 my
.selectionCurve
->setSamples(my
.selections
);
268 TracingItem::updateEvents(TracingEngine
*engine
, QmcMetric
*metric
)
270 if (metric
->hasIndom() && !metric
->explicitInsts()) {
271 for (int i
= 0; i
< metric
->numInst(); i
++)
272 updateEventRecords(engine
, metric
, i
);
274 updateEventRecords(engine
, metric
, 0);
279 // Fetch the new set of events, merging them into the existing sets
280 // - "Points" curve has an entry for *every* event to be displayed.
281 // - "Span" curve has an entry for all *begin/end* flagged events.
282 // These are initially unpaired, unless PMDA is playing games, and
283 // so usually min/max is used as a placeholder until corresponding
284 // begin/end event arrives.
285 // - "Drop" curve has an entry to match up events with the parents.
286 // The parent is the root "span" (terminology on loan from Dapper)
289 TracingItem::updateEventRecords(TracingEngine
*engine
, QmcMetric
*metric
, int index
)
291 if (metric
->error(index
) == 0) {
292 QVector
<QmcEventRecord
> const &records
= metric
->eventRecords(index
);
293 int slot
= 0, parentSlot
= -1;
296 // First lookup the event "slot" (aka "span" / y-axis-entry)
297 // Strategy is to use the ID from the event (if one exists),
298 // which must be mapped to a y-axis integer-based index. If
299 // no ID, we fall back to metric instance ID, else just zero.
301 if (metric
->hasInstances()) {
302 if (metric
->explicitInsts())
304 slot
= metric
->instIndex(index
);
305 name
= metric
->instName(index
);
307 name
= metric
->name();
309 addTraceSpan(engine
, name
, slot
);
311 for (int i
= 0; i
< records
.size(); i
++) {
312 QmcEventRecord
const &record
= records
.at(i
);
314 my
.events
.append(TracingEvent(record
, metric
->metricID(), index
));
315 TracingEvent
&event
= my
.events
.last();
317 if (event
.hasIdentifier() && name
== QString::null
) {
318 addTraceSpan(engine
, event
.spanID(), slot
);
321 // this adds the basic point (ellipse), all events get one
322 my
.points
.append(QPointF(event
.timestamp(), slot
));
325 if (event
.hasParent()) { // lookup parent in yMap
326 parentSlot
= engine
->getTraceSpan(event
.rootID(), parentSlot
);
327 if (parentSlot
== -1)
328 addTraceSpan(engine
, event
.rootID(), parentSlot
);
329 // do this on start/end only? (or if first?)
330 my
.drops
.append(QwtIntervalSample(event
.timestamp(),
331 QwtInterval(slot
, parentSlot
)));
335 QString timestamp
= QmcSource::timeStringBrief(record
.timestamp());
336 console
->post(PmChart::DebugForce
, "TracingItem::updateEventRecords: "
337 "[%s] span: %s (slot=%d) id=%s, root: %s (slot=%d,id=%s), start=%s end=%s",
338 (const char *)timestamp
.toLatin1(),
339 (const char *)event
.spanID().toLatin1(), slot
,
340 event
.hasIdentifier() ? "y" : "n",
341 (const char *)event
.rootID().toLatin1(), parentSlot
,
342 event
.hasParent() ? "y" : "n",
343 event
.hasStartFlag() ? "y" : "n", event
.hasEndFlag() ? "y" : "n");
346 if (event
.hasStartFlag()) {
347 if (!my
.spans
.isEmpty()) {
348 QwtIntervalSample
&active
= my
.spans
.last();
349 // did we get a start, then another start?
350 // (if so, just end the previous span now)
351 if (active
.interval
.maxValue() == my
.maxSpanTime
)
352 active
.interval
.setMaxValue(event
.timestamp());
354 // no matter what, we'll start a new span here
355 my
.spans
.append(QwtIntervalSample(slot
,
356 QwtInterval(event
.timestamp(), my
.maxSpanTime
)));
358 if (event
.hasEndFlag()) {
359 if (!my
.spans
.isEmpty()) {
360 QwtIntervalSample
&active
= my
.spans
.last();
361 // did we get an end, then another end?
362 // (if so, move previous span end to now)
363 if (active
.interval
.maxValue() == my
.maxSpanTime
)
364 active
.interval
.setMaxValue(event
.timestamp());
366 // got an end, but we haven't seen a start
367 my
.spans
.append(QwtIntervalSample(index
,
368 QwtInterval(my
.minSpanTime
, event
.timestamp())));
371 // Have not yet handled missed events (i.e. event.missed())
372 // Could have a separate list of events? (render differently?)
377 // TODO: need to track this failure point, and ensure that the
378 // begin/end spans do not cross this boundary.
380 console
->post(PmChart::DebugForce
,
381 "TracingItem::updateEventRecords: NYI error path: %d (%s)",
382 metric
->error(index
), pmErrStr(metric
->error(index
)));
388 TracingItem::addTraceSpan(TracingEngine
*engine
, const QString
&span
, int slot
)
390 double spanID
= (double)slot
;
392 my
.minSpanID
= qMin(my
.minSpanID
, spanID
);
393 my
.maxSpanID
= qMax(my
.maxSpanID
, spanID
);
394 engine
->addTraceSpan(span
, slot
);
398 TracingItem::setStroke(Chart::Style
, QColor color
, bool)
400 const QColor
black(Qt::black
);
401 const QPen
outline(black
);
402 QColor
darkColor(color
);
403 QColor
alphaColor(color
);
406 alphaColor
.setAlpha(196);
407 QBrush
alphaBrush(alphaColor
);
409 my
.pointCurve
->setLegendColor(color
);
411 my
.spanSymbol
->setWidth(6);
412 my
.spanSymbol
->setBrush(alphaBrush
);
413 my
.spanSymbol
->setPen(outline
);
415 my
.dropSymbol
->setWidth(1);
416 my
.dropSymbol
->setBrush(Qt::NoBrush
);
417 my
.dropSymbol
->setPen(outline
);
419 my
.pointSymbol
->setSize(8);
420 my
.pointSymbol
->setColor(color
);
421 my
.pointSymbol
->setPen(outline
);
423 my
.selectionSymbol
->setSize(8);
424 my
.selectionSymbol
->setColor(color
.dark(180));
425 my
.selectionSymbol
->setPen(outline
);
429 TracingItem::containsPoint(const QRectF
&rect
, int index
)
431 if (my
.points
.isEmpty())
433 return rect
.contains(my
.points
.at(index
));
437 TracingItem::updateCursor(const QPointF
&, int index
)
439 Q_ASSERT(index
<= my
.points
.size());
440 Q_ASSERT(index
<= (int)my
.pointCurve
->dataSize());
442 my
.selections
.append(my
.points
.at(index
));
443 my
.selectionInfo
.append(my
.events
.at(index
).description());
445 // required for immediate chart update after selection
446 QBrush pointBrush
= my
.pointSymbol
->brush();
447 my
.pointSymbol
->setBrush(my
.selectionSymbol
->brush());
448 QwtPlotDirectPainter directPainter
;
449 directPainter
.drawSeries(my
.pointCurve
, index
, index
);
450 my
.pointSymbol
->setBrush(pointBrush
);
454 TracingItem::clearCursor(void)
456 // immediately clear any current visible selections
457 for (int index
= 0; index
< my
.selections
.size(); index
++) {
458 QwtPlotDirectPainter directPainter
;
459 directPainter
.drawSeries(my
.pointCurve
, index
, index
);
461 my
.selections
.clear();
462 my
.selectionInfo
.clear();
466 // Display information text associated with selected events
469 TracingItem::cursorInfo(void)
471 if (my
.selections
.size() > 0) {
472 QString preamble
= metricName();
473 if (metricHasInstances())
474 preamble
.append("[").append(metricInstance()).append("]");
475 preamble
.append("\n");
476 my
.selectionInfo
.prepend(preamble
);
478 return my
.selectionInfo
;
482 TracingItem::revive(void)
486 my
.dropCurve
->attach(my
.chart
);
487 my
.spanCurve
->attach(my
.chart
);
488 my
.pointCurve
->attach(my
.chart
);
489 my
.selectionCurve
->attach(my
.chart
);
494 TracingItem::remove(void)
497 my
.dropCurve
->detach();
498 my
.spanCurve
->detach();
499 my
.pointCurve
->detach();
500 my
.selectionCurve
->detach();
504 TracingItem::redraw(void)
506 if (removed() == false) {
507 // point curve update by legend check, but not the rest:
508 my
.dropCurve
->setVisible(hidden() == false);
509 my
.spanCurve
->setVisible(hidden() == false);
510 my
.selectionCurve
->setVisible(hidden() == false);
515 TracingScaleEngine::TracingScaleEngine(TracingEngine
*engine
) : QwtLinearScaleEngine()
520 setMargins(0.5, 0.5);
524 TracingScaleEngine::getScale(double *minValue
, double *maxValue
)
526 *minValue
= my
.minSpanID
;
527 *maxValue
= my
.maxSpanID
;
531 TracingScaleEngine::setScale(double minValue
, double maxValue
)
533 my
.minSpanID
= minValue
;
534 my
.maxSpanID
= maxValue
;
538 TracingScaleEngine::updateScale(double minValue
, double maxValue
)
540 bool changed
= false;
542 if (minValue
< my
.minSpanID
) {
543 my
.minSpanID
= minValue
;
546 if (maxValue
> my
.maxSpanID
) {
547 my
.maxSpanID
= maxValue
;
554 TracingScaleEngine::autoScale(int maxSteps
, double &minValue
,
555 double &maxValue
, double &stepSize
) const
557 minValue
= my
.minSpanID
;
558 maxValue
= my
.maxSpanID
;
560 QwtLinearScaleEngine::autoScale(maxSteps
, minValue
, maxValue
, stepSize
);
564 TracingScaleEngine::divideScale(double x1
, double x2
, int numMajorSteps
,
565 int /*numMinorSteps*/, double /*stepSize*/) const
567 // discard minor steps - y-axis is displaying trace identifiers;
568 // sub-divisions of an identifier makes no sense
569 return QwtLinearScaleEngine::divideScale(x1
, x2
, numMajorSteps
, 0, 1.0);
574 // Use the hash map to provide event identifiers that map to given numeric IDs
575 // These values were mapped into the hash when we decoded the event records.
578 TracingScaleDraw::label(double value
) const
580 int slot
= (int)value
;
581 const int LABEL_CUTOFF
= 8; // maximum width for label (units: characters)
582 QString label
= my
.engine
->getSpanLabel(slot
);
585 console
->post(PmChart::DebugForce
,
586 "TracingScaleDraw::label: lookup ID %d (=>\"%s\")",
587 slot
, (const char *)label
.toLatin1());
590 // ensure label is not too long to fit
591 label
.truncate(LABEL_CUTOFF
);
592 // and only use up to the first space
593 if ((slot
= label
.indexOf(' ')) >= 0)
594 label
.truncate(slot
);
599 TracingScaleDraw::getBorderDistHint(const QFont
&f
, int &start
, int &end
) const
601 if (orientation() == Qt::Vertical
)
604 QwtScaleDraw::getBorderDistHint(f
, start
, end
);
609 // The (chart-level) implementation of tracing charts
611 TracingEngine::TracingEngine(Chart
*chart
)
613 QwtPlotPicker
*picker
= chart
->my
.picker
;
616 my
.chart
->my
.style
= Chart::EventStyle
;
618 my
.scaleDraw
= new TracingScaleDraw(this);
619 chart
->setAxisScaleDraw(QwtPlot::yLeft
, my
.scaleDraw
);
621 my
.scaleEngine
= new TracingScaleEngine(this);
622 chart
->setAxisScaleEngine(QwtPlot::yLeft
, my
.scaleEngine
);
624 // use a rectangular point picker for event tracing
625 picker
->setStateMachine(new QwtPickerDragRectMachine());
626 picker
->setRubberBand(QwtPicker::RectRubberBand
);
627 picker
->setRubberBandPen(QColor(Qt::green
));
631 TracingEngine::addItem(QmcMetric
*mp
, pmMetricSpec
*msp
, pmDesc
*desc
, const QString
&legend
)
633 return new TracingItem(my
.chart
, mp
, msp
, desc
, legend
);
637 TracingEngine::tracingItem(int index
)
639 return (TracingItem
*)my
.chart
->my
.items
[index
];
643 TracingEngine::selected(const QPolygon
&poly
)
645 my
.chart
->showPoints(poly
);
649 TracingEngine::replot(void)
651 for (int i
= 0; i
< my
.chart
->metricCount(); i
++)
652 tracingItem(i
)->redraw();
656 TracingEngine::updateValues(bool, int, int, double left
, double right
, double)
658 // Drive new values into each chart item
659 for (int i
= 0; i
< my
.chart
->metricCount(); i
++)
660 tracingItem(i
)->updateValues(this, left
, right
);
664 TracingEngine::getTraceSpan(const QString
&spanID
, int slot
) const
666 return my
.traceSpanMapping
.value(spanID
, slot
);
670 TracingEngine::addTraceSpan(const QString
&spanID
, int slot
)
672 Q_ASSERT(spanID
!= QString::null
&& spanID
!= "");
673 console
->post("TracingEngine::addTraceSpan: \"%s\" <=> slot %d (%d/%d span/label)",
674 (const char *)spanID
.toLatin1(), slot
,
675 my
.traceSpanMapping
.size(), my
.labelSpanMapping
.size());
676 my
.traceSpanMapping
.insert(spanID
, slot
);
677 my
.labelSpanMapping
.insert(slot
, spanID
);
681 TracingEngine::getSpanLabel(int slot
) const
683 return my
.labelSpanMapping
.value(slot
);
687 TracingEngine::redoScale(void)
689 double minValue
, maxValue
;
691 my
.scaleEngine
->getScale(&minValue
, &maxValue
);
693 for (int i
= 0; i
< my
.chart
->metricCount(); i
++)
694 tracingItem(i
)->rescaleValues(&minValue
, &maxValue
);
696 if (my
.scaleEngine
->updateScale(minValue
, maxValue
)) {
697 my
.scaleDraw
->invalidate();
703 TracingEngine::isCompatible(pmDesc
&desc
)
705 return (desc
.type
== PM_TYPE_EVENT
|| desc
.type
== PM_TYPE_HIGHRES_EVENT
);
709 TracingEngine::scale(bool *autoScale
, double *yMin
, double *yMax
)
712 my
.scaleEngine
->getScale(yMin
, yMax
);
716 TracingEngine::setScale(bool, double, double)
718 my
.chart
->setAxisAutoScale(QwtPlot::yLeft
);
722 TracingEngine::setStyle(Chart::Style
)
724 my
.chart
->setYAxisTitle("");