summaryrefslogtreecommitdiff
path: root/src/pmchart/groupcontrol.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/pmchart/groupcontrol.cpp')
-rw-r--r--src/pmchart/groupcontrol.cpp675
1 files changed, 675 insertions, 0 deletions
diff --git a/src/pmchart/groupcontrol.cpp b/src/pmchart/groupcontrol.cpp
new file mode 100644
index 0000000..5bac8d3
--- /dev/null
+++ b/src/pmchart/groupcontrol.cpp
@@ -0,0 +1,675 @@
+/*
+ * Copyright (c) 2012, Red Hat.
+ * Copyright (c) 2007-2008, Aconex. All Rights Reserved.
+ * Copyright (c) 2006, Ken McDonell. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include "main.h"
+#include "groupcontrol.h"
+
+#define DESPERATE 0
+
+GroupControl::GroupControl()
+{
+ my.samples = 0;
+ my.visible = 0;
+ my.realDelta = 0;
+ my.realPosition = 0;
+ my.timeData = NULL;
+ my.timeState = StartState;
+ my.buttonState = QedTimeButton::Timeless;
+ my.pmtimeState = QmcTime::StoppedState;
+ memset(&my.delta, 0, sizeof(struct timeval));
+ memset(&my.position, 0, sizeof(struct timeval));
+}
+
+void
+GroupControl::init(int samples, int visible,
+ struct timeval *interval, struct timeval *position)
+{
+ my.samples = samples;
+ my.visible = visible;
+
+ if (isArchiveSource()) {
+ my.pmtimeState = QmcTime::StoppedState;
+ my.buttonState = QedTimeButton::StoppedArchive;
+ }
+ else {
+ my.pmtimeState = QmcTime::ForwardState;
+ my.buttonState = QedTimeButton::ForwardLive;
+ }
+ my.delta = *interval;
+ my.position = *position;
+ my.realDelta = tosec(*interval);
+ my.realPosition = tosec(*position);
+
+ my.timeData = (double *)malloc(samples * sizeof(double));
+ for (int i = 0; i < samples; i++)
+ my.timeData[i] = my.realPosition - (i * my.realDelta);
+}
+
+bool
+GroupControl::isArchiveSource(void)
+{
+ // Note: We purposefully are not using QmcGroup::mode() here, as we
+ // may not have initialised any contexts yet. In such a case, live
+ // mode is always returned (default, from the QmcGroup constructor).
+
+ return this == archiveGroup;
+}
+
+bool
+GroupControl::isActive(QmcTime::Packet *packet)
+{
+ return (((activeGroup == archiveGroup) &&
+ (packet->source == QmcTime::ArchiveSource)) ||
+ ((activeGroup == liveGroup) &&
+ (packet->source == QmcTime::HostSource)));
+}
+
+void
+GroupControl::addGadget(Gadget *gadget)
+{
+ my.gadgetsList.append(gadget);
+}
+
+void
+GroupControl::deleteGadget(Gadget *gadget)
+{
+ for (int i = 0; i < gadgetCount(); i++)
+ if (my.gadgetsList.at(i) == gadget)
+ my.gadgetsList.removeAt(i);
+}
+
+int
+GroupControl::gadgetCount() const
+{
+ return my.gadgetsList.size();
+}
+
+void
+GroupControl::updateBackground(void)
+{
+ for (int i = 0; i < gadgetCount(); i++)
+ my.gadgetsList.at(i)->updateBackground(globalSettings.chartBackground);
+}
+
+void
+GroupControl::updateTimeAxis(void)
+{
+ QString tz, otz, unused;
+
+ if (numContexts() > 0 || isArchiveSource() == false) {
+ if (numContexts() > 0)
+ defaultTZ(unused, otz);
+ else
+ otz = QmcSource::localHost;
+ tz = otz;
+ pmchart->timeAxis()->setAxisScale(QwtPlot::xBottom,
+ my.timeData[my.visible - 1], my.timeData[0],
+ pmchart->timeAxis()->scaleValue(my.realDelta, my.visible));
+ pmchart->setDateLabel(my.position.tv_sec, tz);
+ pmchart->timeAxis()->replot();
+ } else {
+ pmchart->timeAxis()->noArchiveSources();
+ pmchart->setDateLabel(tr("[No open archives]"));
+ }
+
+ if (console->logLevel(PmChart::DebugProtocol)) {
+ int i = my.visible - 1;
+ console->post(PmChart::DebugProtocol,
+ "GroupControl::updateTimeAxis: tz=%s; visible points=%d",
+ (const char *)tz.toAscii(), i);
+ console->post(PmChart::DebugProtocol,
+ "GroupControl::updateTimeAxis: first time is %.3f (%s)",
+ my.timeData[i], timeString(my.timeData[i]));
+ console->post(PmChart::DebugProtocol,
+ "GroupControl::updateTimeAxis: final time is %.3f (%s)",
+ my.timeData[0], timeString(my.timeData[0]));
+ }
+}
+
+void
+GroupControl::updateTimeButton(void)
+{
+ pmchart->setButtonState(my.buttonState);
+}
+
+QmcTime::State
+GroupControl::pmtimeState(void)
+{
+ return my.pmtimeState;
+}
+
+char *
+GroupControl::timeState()
+{
+ static char buf[16];
+
+ switch (my.timeState) {
+ case StartState: strcpy(buf, "Start"); break;
+ case ForwardState: strcpy(buf, "Forward"); break;
+ case BackwardState: strcpy(buf, "Backward"); break;
+ case EndLogState: strcpy(buf, "EndLog"); break;
+ case StandbyState: strcpy(buf, "Standby"); break;
+ default: strcpy(buf, "Dodgey"); break;
+ }
+ return buf;
+}
+
+void
+GroupControl::timeSelectionActive(Gadget *source, int timestamp)
+{
+ QPoint point(timestamp, -1);
+ QMouseEvent pressed(QEvent::MouseButtonPress, point,
+ Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
+ for (int i = 0; i < gadgetCount(); i++) {
+ Gadget *gadget = my.gadgetsList.at(i);
+ if (source != gadget)
+ gadget->activateTime(&pressed);
+ }
+}
+
+void
+GroupControl::timeSelectionReactive(Gadget *source, int timestamp)
+{
+ QPoint point(timestamp, -1);
+ QMouseEvent moved(QEvent::MouseMove, point,
+ Qt::NoButton, Qt::LeftButton, Qt::NoModifier);
+ for (int i = 0; i < gadgetCount(); i++) {
+ Gadget *gadget = my.gadgetsList.at(i);
+ if (source != gadget)
+ gadget->reactivateTime(&moved);
+ }
+}
+
+void
+GroupControl::timeSelectionInactive(Gadget *source)
+{
+ QPoint point(-1, -1);
+ QMouseEvent release(QEvent::MouseButtonRelease, point,
+ Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
+ for (int i = 0; i < gadgetCount(); i++) {
+ Gadget *gadget = my.gadgetsList.at(i);
+ if (source != gadget)
+ gadget->deactivateTime(&release);
+ }
+}
+
+//
+// Drive all updates into each gadget (refresh the display)
+//
+void
+GroupControl::refreshGadgets(bool active)
+{
+#if DESPERATE
+ for (int s = 0; s < my.samples; s++)
+ console->post(PmChart::DebugProtocol,
+ "GroupControl::refreshGadgets: timeData[%2d] is %.2f (%s)",
+ s, my.timeData[s], timeString(my.timeData[s]));
+ console->post(PmChart::DebugProtocol,
+ "GroupControl::refreshGadgets: state=%s", timeState());
+#endif
+
+ for (int i = 0; i < gadgetCount(); i++) {
+ my.gadgetsList.at(i)->updateValues(my.timeState != BackwardState,
+ active, my.samples, my.visible,
+ my.timeData[my.visible - 1],
+ my.timeData[0],
+ my.realDelta);
+ }
+ if (active) {
+ updateTimeButton();
+ updateTimeAxis();
+ }
+}
+
+//
+// Setup the initial data needed after opening a view.
+// All of the work is in archive mode; in live mode we have
+// not yet got any historical data that we can display...
+//
+void
+GroupControl::setupWorldView(void)
+{
+ if (isArchiveSource() == false)
+ return;
+
+ QmcTime::Packet packet;
+ packet.source = QmcTime::ArchiveSource;
+ packet.state = QmcTime::ForwardState;
+ packet.mode = QmcTime::NormalMode;
+ memcpy(&packet.delta, pmtime->archiveInterval(), sizeof(packet.delta));
+ memcpy(&packet.position, pmtime->archivePosition(), sizeof(packet.position));
+ memcpy(&packet.start, pmtime->archiveStart(), sizeof(packet.start));
+ memcpy(&packet.end, pmtime->archiveEnd(), sizeof(packet.end));
+ adjustWorldView(&packet, true);
+}
+
+//
+// Received a Set or a VCRMode requiring us to adjust our state
+// and possibly rethink everything. This can result from a time
+// control position change, delta change, direction change, etc.
+//
+void
+GroupControl::adjustWorldView(QmcTime::Packet *packet, bool vcrMode)
+{
+ my.delta = packet->delta;
+ my.position = packet->position;
+ my.realDelta = tosec(packet->delta);
+ my.realPosition = tosec(packet->position);
+
+ console->post("GroupControl::adjustWorldView: "
+ "sh=%d vh=%d delta=%.2f position=%.2f (%s) state=%s",
+ my.samples, my.visible, my.realDelta, my.realPosition,
+ timeString(my.realPosition), timeState());
+
+ QmcTime::State state = packet->state;
+ if (isArchiveSource()) {
+ if (packet->state == QmcTime::ForwardState)
+ adjustArchiveWorldViewForward(packet, vcrMode);
+ else if (packet->state == QmcTime::BackwardState)
+ adjustArchiveWorldViewBackward(packet, vcrMode);
+ else
+ adjustArchiveWorldViewStopped(packet, vcrMode);
+ }
+ else if (state != QmcTime::StoppedState)
+ adjustLiveWorldViewForward(packet);
+ else
+ adjustLiveWorldViewStopped(packet);
+}
+
+void
+GroupControl::adjustLiveWorldViewStopped(QmcTime::Packet *packet)
+{
+ if (isActive(packet)) {
+ newButtonState(packet->state, packet->mode, pmchart->isTabRecording());
+ updateTimeButton();
+ }
+}
+
+static bool
+fuzzyTimeMatch(double a, double b, double tolerance)
+{
+ // a matches b if the difference is within 1% of the delta (tolerance)
+ return (a == b ||
+ (b > a && a + tolerance > b) ||
+ (b < a && a - tolerance < b));
+}
+
+void
+GroupControl::adjustLiveWorldViewForward(QmcTime::Packet *packet)
+{
+ //
+ // X-Axis _max_ becomes packet->position.
+ // Rest of (preceeding) time window filled in using packet->delta.
+ // In live mode, we can only fetch current data. However, we make
+ // an effort to keep old data that happens to align with the delta
+ // time points (or near enough) that we are now interested in. So,
+ // "oi" is the old index, whereas "i" is the new timeData[] index.
+ // First we try to find a fuzzy match on current old index, if it
+ // doesn't exit, we continue moving "oi" until it points at a time
+ // larger than the one we're after, and then see how that fares on
+ // the next iteration.
+ //
+ int i, oi, last = my.samples - 1;
+ bool preserve = false;
+ double tolerance = my.realDelta;
+ double position = my.realPosition - (my.realDelta * my.samples);
+
+ for (i = oi = last; i >= 0; i--, position += my.realDelta) {
+ while (i > 0 && my.timeData[oi] < position + my.realDelta && oi > 0) {
+ if (fuzzyTimeMatch(my.timeData[oi], position, tolerance) == false) {
+#if DESPERATE
+ console->post("NO fuzzyTimeMatch %.3f to %.3f (%s)",
+ my.timeData[oi], position, timeString(position));
+#endif
+ if (my.timeData[oi] > position)
+ break;
+ oi--;
+ continue;
+ }
+ console->post("Saved live data (oi=%d/i=%d) for %s", oi, i,
+ timeString(position));
+ for (int j = 0; j < gadgetCount(); j++)
+ my.gadgetsList.at(j)->preserveSample(i, oi);
+ my.timeData[i] = my.timeData[oi];
+ preserve = true;
+ oi--;
+ break;
+ }
+
+ if (i == 0) { // refreshGadgets() finishes up last one
+ console->post("Fetching data[%d] at %s", i, timeString(position));
+ my.timeData[i] = position;
+ fetch();
+ }
+ else if (preserve == false) {
+#if DESPERATE
+ console->post("No live data for %s", timeString(position));
+#endif
+ my.timeData[i] = position;
+ for (int j = 0; j < gadgetCount(); j++)
+ my.gadgetsList.at(j)->punchoutSample(i);
+ }
+ else
+ preserve = false;
+ }
+ // One (per-gadget) recalculation & refresh at the end, after all data moved
+ for (int j = 0; j < gadgetCount(); j++)
+ my.gadgetsList.at(j)->adjustValues();
+ my.timeState = (packet->state == QmcTime::StoppedState) ?
+ StandbyState : ForwardState;
+
+ bool active = isActive(packet);
+ if (active)
+ newButtonState(packet->state, packet->mode, pmchart->isTabRecording());
+ refreshGadgets(active);
+}
+
+void
+GroupControl::adjustArchiveWorldViewForward(QmcTime::Packet *packet, bool setup)
+{
+ console->post("GroupControl::adjustArchiveWorldViewForward");
+ my.timeState = ForwardState;
+
+ int setmode = PM_MODE_INTERP;
+ int delta = packet->delta.tv_sec;
+ if (packet->delta.tv_usec == 0) {
+ setmode |= PM_XTB_SET(PM_TIME_SEC);
+ } else {
+ delta = delta * 1000 + packet->delta.tv_usec / 1000;
+ setmode |= PM_XTB_SET(PM_TIME_MSEC);
+ }
+
+ //
+ // X-Axis _max_ becomes packet->position.
+ // Rest of (preceeding) time window filled in using packet->delta.
+ //
+ int last = my.samples - 1;
+ double tolerance = my.realDelta / 20.0; // 5% of the sample interval
+ double position = my.realPosition - (my.realDelta * last);
+
+ double left = position;
+ double right = my.realPosition;
+ double interval = pmchart->timeAxis()->scaleValue((double)delta, my.visible);
+
+ for (int i = last; i >= 0; i--, position += my.realDelta) {
+ if (setup == false &&
+ fuzzyTimeMatch(my.timeData[i], position, tolerance) == true) {
+ continue;
+ }
+
+ my.timeData[i] = position;
+
+ struct timeval timeval;
+ fromsec(position, &timeval);
+ setArchiveMode(setmode, &timeval, delta);
+ console->post("Fetching data[%d] at %s", i, timeString(position));
+ fetch();
+ if (i == 0) // refreshGadgets() finishes up last one
+ break;
+ console->post("GroupControl::adjustArchiveWorldViewForward: "
+ "setting time position[%d]=%.2f[%s] state=%s count=%d",
+ i, position, timeString(position),
+ timeState(), gadgetCount());
+ for (int j = 0; j < gadgetCount(); j++)
+ my.gadgetsList.at(j)->updateValues(true, false, my.samples, my.visible,
+ left, right, interval);
+ }
+
+ bool active = isActive(packet);
+ if (setup)
+ packet->state = QmcTime::StoppedState;
+ if (active)
+ newButtonState(packet->state, packet->mode, pmchart->isTabRecording());
+ pmtime->setArchivePosition(&packet->position);
+ pmtime->setArchiveInterval(&packet->delta);
+ refreshGadgets(active);
+}
+
+void
+GroupControl::adjustArchiveWorldViewBackward(QmcTime::Packet *packet, bool setup)
+{
+ console->post("GroupControl::adjustArchiveWorldViewBackward");
+ my.timeState = BackwardState;
+
+ int setmode = PM_MODE_INTERP;
+ int delta = packet->delta.tv_sec;
+ if (packet->delta.tv_usec == 0) {
+ setmode |= PM_XTB_SET(PM_TIME_SEC);
+ } else {
+ delta = delta * 1000 + packet->delta.tv_usec / 1000;
+ setmode |= PM_XTB_SET(PM_TIME_MSEC);
+ }
+
+ //
+ // X-Axis _min_ becomes packet->position.
+ // Rest of (following) time window filled in using packet->delta.
+ //
+ int last = my.samples - 1;
+ double tolerance = my.realDelta / 20.0; // 5% of the sample interval
+ double position = my.realPosition;
+
+ double left = position - (my.realDelta * last);
+ double right = position;
+ double interval = pmchart->timeAxis()->scaleValue((double)delta, my.visible);
+
+ for (int i = 0; i <= last; i++, position -= my.realDelta) {
+ if (setup == false &&
+ fuzzyTimeMatch(my.timeData[i], position, tolerance) == true) {
+ continue;
+ }
+
+ my.timeData[i] = position;
+
+ struct timeval timeval;
+ fromsec(position, &timeval);
+ setArchiveMode(setmode, &timeval, -delta);
+ console->post("Fetching data[%d] at %s", i, timeString(position));
+ fetch();
+ if (i == last) // refreshGadgets() finishes up last one
+ break;
+ console->post("GroupControl::adjustArchiveWorldViewBackward: "
+ "setting time position[%d]=%.2f[%s] state=%s count=%d",
+ i, position, timeString(position),
+ timeState(), gadgetCount());
+ for (int j = 0; j < gadgetCount(); j++)
+ my.gadgetsList.at(j)->updateValues(false, false, my.samples, my.visible,
+ left, right, interval);
+ }
+
+ bool active = isActive(packet);
+ if (setup)
+ packet->state = QmcTime::StoppedState;
+ if (active)
+ newButtonState(packet->state, packet->mode, pmchart->isTabRecording());
+ pmtime->setArchivePosition(&packet->position);
+ pmtime->setArchiveInterval(&packet->delta);
+ refreshGadgets(active);
+}
+
+void
+GroupControl::adjustArchiveWorldViewStopped(QmcTime::Packet *packet, bool needFetch)
+{
+ if (needFetch) { // stopped, but VCR reposition event occurred
+ adjustArchiveWorldViewForward(packet, needFetch);
+ } else {
+ my.timeState = StandbyState;
+ packet->state = QmcTime::StoppedState;
+ newButtonState(packet->state, packet->mode, pmchart->isTabRecording());
+ updateTimeButton();
+ }
+}
+
+//
+// Catch the situation where we get a larger than expected increase
+// in position. This happens when we restart after a stop in live
+// mode (both with and without a change in the delta).
+//
+static bool
+sideStep(double n, double o, double interval)
+{
+ // tolerance set to 5% of the sample interval:
+ return fuzzyTimeMatch(o + interval, n, interval/20.0) == false;
+}
+
+//
+// Fetch all metric values across all gadgets, and also update the
+// unified time axis.
+//
+void
+GroupControl::step(QmcTime::Packet *packet)
+{
+ double stepPosition = tosec(packet->position);
+
+ console->post(PmChart::DebugProtocol,
+ "GroupControl::step: stepping to time %.2f, delta=%.2f, state=%s",
+ stepPosition, my.realDelta, timeState());
+
+ if ((packet->source == QmcTime::ArchiveSource &&
+ ((packet->state == QmcTime::ForwardState &&
+ my.timeState != ForwardState) ||
+ (packet->state == QmcTime::BackwardState &&
+ my.timeState != BackwardState))) ||
+ sideStep(stepPosition, my.realPosition, my.realDelta))
+ return adjustWorldView(packet, false);
+
+ my.pmtimeState = packet->state;
+ my.position = packet->position;
+ my.realPosition = stepPosition;
+
+ int last = my.samples - 1;
+ if (packet->state == QmcTime::ForwardState) { // left-to-right (all but 1st)
+ if (my.samples > 1)
+ memmove(&my.timeData[1], &my.timeData[0], sizeof(double) * last);
+ my.timeData[0] = my.realPosition;
+ }
+ else if (packet->state == QmcTime::BackwardState) { // right-to-left
+ if (my.samples > 1)
+ memmove(&my.timeData[0], &my.timeData[1], sizeof(double) * last);
+ my.timeData[last] = my.realPosition - torange(my.delta, last);
+ }
+
+ fetch();
+
+ bool active = isActive(packet);
+ if (isActive(packet))
+ newButtonState(packet->state, packet->mode, pmchart->isTabRecording());
+ refreshGadgets(active);
+}
+
+void
+GroupControl::VCRMode(QmcTime::Packet *packet, bool dragMode)
+{
+ if (!dragMode)
+ adjustWorldView(packet, true);
+}
+
+void
+GroupControl::setTimezone(QmcTime::Packet *packet, char *tz)
+{
+ console->post(PmChart::DebugProtocol, "GroupControl::setTimezone %s", tz);
+
+ useTZ(QString(tz));
+
+ if (isActive(packet))
+ updateTimeAxis();
+}
+
+void
+GroupControl::setSampleHistory(int v)
+{
+ console->post("GroupControl::setSampleHistory (%d -> %d)", my.samples, v);
+ if (my.samples != v) {
+ my.samples = v;
+
+ my.timeData = (double *)malloc(my.samples * sizeof(my.timeData[0]));
+ if (my.timeData == NULL)
+ nomem();
+
+ double right = my.realPosition;
+ for (v = 0; v < my.samples; v++)
+ my.timeData[v] = my.realPosition - (v * my.realDelta);
+ double left = my.timeData[v-1];
+ for (v = 0; v < gadgetCount(); v++)
+ my.gadgetsList.at(v)->resetValues(my.samples, left, right);
+ }
+}
+
+int
+GroupControl::sampleHistory(void)
+{
+ return my.samples;
+}
+
+void
+GroupControl::setVisibleHistory(int v)
+{
+ console->post("GroupControl::setVisibleHistory (%d -> %d)", my.visible, v);
+
+ if (my.visible != v)
+ my.visible = v;
+}
+
+int
+GroupControl::visibleHistory(void)
+{
+ return my.visible;
+}
+
+double *
+GroupControl::timeAxisData(void)
+{
+ return my.timeData;
+}
+
+QedTimeButton::State
+GroupControl::buttonState(void)
+{
+ return my.buttonState;
+}
+
+void
+GroupControl::newButtonState(QmcTime::State s, QmcTime::Mode m, bool record)
+{
+ if (isArchiveSource() == false) {
+ if (s == QmcTime::StoppedState)
+ my.buttonState = record ?
+ QedTimeButton::StoppedRecord : QedTimeButton::StoppedLive;
+ else
+ my.buttonState = record ?
+ QedTimeButton::ForwardRecord : QedTimeButton::ForwardLive;
+ }
+ else if (m == QmcTime::StepMode) {
+ if (s == QmcTime::ForwardState)
+ my.buttonState = QedTimeButton::StepForwardArchive;
+ else if (s == QmcTime::BackwardState)
+ my.buttonState = QedTimeButton::StepBackwardArchive;
+ else
+ my.buttonState = QedTimeButton::StoppedArchive;
+ }
+ else if (m == QmcTime::FastMode) {
+ if (s == QmcTime::ForwardState)
+ my.buttonState = QedTimeButton::FastForwardArchive;
+ else if (s == QmcTime::BackwardState)
+ my.buttonState = QedTimeButton::FastBackwardArchive;
+ else
+ my.buttonState = QedTimeButton::StoppedArchive;
+ }
+ else if (s == QmcTime::ForwardState)
+ my.buttonState = QedTimeButton::ForwardArchive;
+ else if (s == QmcTime::BackwardState)
+ my.buttonState = QedTimeButton::BackwardArchive;
+ else
+ my.buttonState = QedTimeButton::StoppedArchive;
+}