diff options
Diffstat (limited to 'src/pmchart/groupcontrol.cpp')
-rw-r--r-- | src/pmchart/groupcontrol.cpp | 675 |
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; +} |