docs: describe the pmdaroot process interfaces
[pcp.git] / src / pmchart / tracing.cpp
blobc244b4110c8c2103824dfcc6d51d0b3fae037562
1 /*
2 * Copyright (c) 2012, Red Hat.
3 * Copyright (c) 2012, Nathan Scott. All Rights Reserved.
4 *
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
13 * for more details.
14 */
15 #include <qwt_plot_directpainter.h>
16 #include <qwt_picker_machine.h>
17 #include <qwt_plot_marker.h>
18 #include <qwt_symbol.h>
19 #include "tracing.h"
20 #include "main.h"
22 #define DESPERATE 0
24 TracingItem::TracingItem(Chart *chart,
25 QmcMetric *mp, pmMetricSpec *msp, pmDesc *dp, const QString &legend)
26 : ChartItem(mp, msp, dp, legend)
28 struct timeval limit;
29 my.chart = chart;
30 my.minSpanID = 0;
31 my.maxSpanID = 1;
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);
38 else
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)
78 delete my.spanCurve;
79 delete my.spanSymbol;
80 delete my.dropCurve;
81 delete my.dropSymbol;
82 delete my.pointCurve;
83 delete my.pointSymbol;
84 delete my.selectionCurve;
85 delete my.selectionSymbol;
88 QwtPlotItem *
89 TracingItem::item(void)
91 return my.pointCurve;
94 QwtPlotCurve *
95 TracingItem::curve(void)
97 return my.pointCurve;
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();
105 my.pmid = pmid;
106 my.inst = inst;
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()
125 my.timestamp = 0.0;
128 void
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.
150 void
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)
159 continue;
160 my.spans.remove(i);
164 void
165 TracingItem::cullOutlyingDrops(double left, double right)
167 int i, cull;
169 for (i = cull = 0; i < my.drops.size(); i++, cull++)
170 if (my.drops.at(i).value >= left)
171 break;
172 if (cull)
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)
176 break;
177 if (cull)
178 my.drops.remove(my.drops.size() - cull, cull); // cull from end
181 void
182 TracingItem::cullOutlyingPoints(double left, double right)
184 int i, cull;
186 for (i = cull = 0; i < my.points.size(); i++, cull++)
187 if (my.points.at(i).x() >= left)
188 break;
189 if (cull)
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)
193 break;
194 if (cull)
195 my.points.remove(my.points.size() - cull, cull); // cull from end
198 void
199 TracingItem::cullOutlyingEvents(double left, double right)
201 int i, cull;
203 for (i = cull = 0; i < my.events.size(); i++, cull++)
204 if (my.events.at(i).timestamp() >= left)
205 break;
206 if (cull)
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)
210 break;
211 if (cull)
212 my.events.remove(my.events.size() - cull, cull); // cull from end
215 void
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.
237 void
238 TracingItem::updateValues(TracingEngine *engine, double left, double right)
240 QmcMetric *metric = ChartItem::my.metric;
242 #if DESPERATE
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());
249 #endif
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);
267 void
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);
273 } else {
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)
288 void
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;
294 QString name;
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())
303 index = 0;
304 slot = metric->instIndex(index);
305 name = metric->instName(index);
306 } else {
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));
324 parentSlot = -1;
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)));
334 #if DESPERATE
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");
344 #endif
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());
365 } else {
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?)
374 } else {
375 #if DESPERATE
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)));
383 #endif
387 void
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);
397 void
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);
405 darkColor.dark(180);
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);
428 bool
429 TracingItem::containsPoint(const QRectF &rect, int index)
431 if (my.points.isEmpty())
432 return false;
433 return rect.contains(my.points.at(index));
436 void
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);
453 void
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
468 const QString &
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;
481 void
482 TracingItem::revive(void)
484 if (removed()) {
485 setRemoved(false);
486 my.dropCurve->attach(my.chart);
487 my.spanCurve->attach(my.chart);
488 my.pointCurve->attach(my.chart);
489 my.selectionCurve->attach(my.chart);
493 void
494 TracingItem::remove(void)
496 setRemoved(true);
497 my.dropCurve->detach();
498 my.spanCurve->detach();
499 my.pointCurve->detach();
500 my.selectionCurve->detach();
503 void
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()
517 my.engine = engine;
518 my.minSpanID = 0.0;
519 my.maxSpanID = 1.0;
520 setMargins(0.5, 0.5);
523 void
524 TracingScaleEngine::getScale(double *minValue, double *maxValue)
526 *minValue = my.minSpanID;
527 *maxValue = my.maxSpanID;
530 void
531 TracingScaleEngine::setScale(double minValue, double maxValue)
533 my.minSpanID = minValue;
534 my.maxSpanID = maxValue;
537 bool
538 TracingScaleEngine::updateScale(double minValue, double maxValue)
540 bool changed = false;
542 if (minValue < my.minSpanID) {
543 my.minSpanID = minValue;
544 changed = true;
546 if (maxValue > my.maxSpanID) {
547 my.maxSpanID = maxValue;
548 changed = true;
550 return changed;
553 void
554 TracingScaleEngine::autoScale(int maxSteps, double &minValue,
555 double &maxValue, double &stepSize) const
557 minValue = my.minSpanID;
558 maxValue = my.maxSpanID;
559 stepSize = 1.0;
560 QwtLinearScaleEngine::autoScale(maxSteps, minValue, maxValue, stepSize);
563 QwtScaleDiv
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.
577 QwtText
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);
584 #if DESPERATE
585 console->post(PmChart::DebugForce,
586 "TracingScaleDraw::label: lookup ID %d (=>\"%s\")",
587 slot, (const char *)label.toLatin1());
588 #endif
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);
595 return label;
598 void
599 TracingScaleDraw::getBorderDistHint(const QFont &f, int &start, int &end) const
601 if (orientation() == Qt::Vertical)
602 start = end = 0;
603 else
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;
615 my.chart = chart;
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));
630 ChartItem *
631 TracingEngine::addItem(QmcMetric *mp, pmMetricSpec *msp, pmDesc *desc, const QString &legend)
633 return new TracingItem(my.chart, mp, msp, desc, legend);
636 TracingItem *
637 TracingEngine::tracingItem(int index)
639 return (TracingItem *)my.chart->my.items[index];
642 void
643 TracingEngine::selected(const QPolygon &poly)
645 my.chart->showPoints(poly);
648 void
649 TracingEngine::replot(void)
651 for (int i = 0; i < my.chart->metricCount(); i++)
652 tracingItem(i)->redraw();
655 void
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);
669 void
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);
680 QString
681 TracingEngine::getSpanLabel(int slot) const
683 return my.labelSpanMapping.value(slot);
686 void
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();
698 replot();
702 bool
703 TracingEngine::isCompatible(pmDesc &desc)
705 return (desc.type == PM_TYPE_EVENT || desc.type == PM_TYPE_HIGHRES_EVENT);
708 void
709 TracingEngine::scale(bool *autoScale, double *yMin, double *yMax)
711 *autoScale = true;
712 my.scaleEngine->getScale(yMin, yMax);
715 void
716 TracingEngine::setScale(bool, double, double)
718 my.chart->setAxisAutoScale(QwtPlot::yLeft);
721 void
722 TracingEngine::setStyle(Chart::Style)
724 my.chart->setYAxisTitle("");