summaryrefslogtreecommitdiff
path: root/src/pmchart/timecontrol.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/pmchart/timecontrol.cpp')
-rw-r--r--src/pmchart/timecontrol.cpp447
1 files changed, 447 insertions, 0 deletions
diff --git a/src/pmchart/timecontrol.cpp b/src/pmchart/timecontrol.cpp
new file mode 100644
index 0000000..e72bcd0
--- /dev/null
+++ b/src/pmchart/timecontrol.cpp
@@ -0,0 +1,447 @@
+/*
+ * Copyright (c) 2014, Red Hat.
+ * Copyright (c) 2006-2007, Aconex. 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 <qmc_time.h>
+#include <QtGui/QMessageBox>
+#include <QtGui/QApplication>
+#include <QtNetwork/QHostAddress>
+
+TimeControl::TimeControl() : QProcess(NULL)
+{
+ my.tcpPort = -1;
+ my.tzLength = 0;
+ my.tzData = NULL;
+ my.bufferLength = sizeof(QmcTime::Packet);
+
+ my.buffer = (char *)malloc(my.bufferLength);
+ my.livePacket = (QmcTime::Packet *)malloc(sizeof(QmcTime::Packet));
+ my.archivePacket = (QmcTime::Packet *)malloc(sizeof(QmcTime::Packet));
+ if (!my.buffer || !my.livePacket || !my.archivePacket)
+ nomem();
+ my.livePacket->magic = QmcTime::Magic;
+ my.livePacket->source = QmcTime::HostSource;
+ my.liveState = TimeControl::Disconnected;
+ my.liveSocket = new QTcpSocket(this);
+ connect(my.liveSocket, SIGNAL(connected()),
+ SLOT(liveSocketConnected()));
+ connect(my.liveSocket, SIGNAL(disconnected()),
+ SLOT(liveCloseConnection()));
+ connect(my.liveSocket, SIGNAL(readyRead()),
+ SLOT(liveProtocolMessage()));
+
+ my.archivePacket->magic = QmcTime::Magic;
+ my.archivePacket->source = QmcTime::ArchiveSource;
+ my.archiveState = TimeControl::Disconnected;
+ my.archiveSocket = new QTcpSocket(this);
+ connect(my.archiveSocket, SIGNAL(connected()),
+ SLOT(archiveSocketConnected()));
+ connect(my.archiveSocket, SIGNAL(disconnected()),
+ SLOT(archiveCloseConnection()));
+ connect(my.archiveSocket, SIGNAL(readyRead()),
+ SLOT(archiveProtocolMessage()));
+}
+
+void TimeControl::quit()
+{
+ disconnect(this, SIGNAL(finished(int, QProcess::ExitStatus)), this,
+ SLOT(endTimeControl()));
+ if (my.liveSocket)
+ disconnect(my.liveSocket, SIGNAL(disconnected()), this,
+ SLOT(liveCloseConnection()));
+ if (my.archiveSocket)
+ disconnect(my.archiveSocket, SIGNAL(disconnected()), this,
+ SLOT(archiveCloseConnection()));
+ if (my.liveSocket) {
+ my.liveSocket->close();
+ my.liveSocket = NULL;
+ }
+ if (my.archiveSocket) {
+ my.archiveSocket->close();
+ my.archiveSocket = NULL;
+ }
+ terminate();
+}
+
+void TimeControl::init(int port, bool live,
+ struct timeval *interval, struct timeval *position,
+ struct timeval *starttime, struct timeval *endtime,
+ QString tzstring, QString tzlabel)
+{
+ struct timeval now;
+ int tzlen = tzstring.length(), lablen = tzlabel.length();
+
+ my.tzLength = tzlen+1 + lablen+1;
+ my.tzData = (char *)realloc(my.tzData, my.tzLength);
+ if (!my.tzData)
+ nomem();
+
+ my.livePacket->length = my.archivePacket->length =
+ sizeof(QmcTime::Packet) + my.tzLength;
+ my.livePacket->command = my.archivePacket->command = QmcTime::Set;
+ my.livePacket->delta = my.archivePacket->delta = *interval;
+ if (live) {
+ my.livePacket->position = *position;
+ my.livePacket->start = *starttime;
+ my.livePacket->end = *endtime;
+ memset(&my.archivePacket->position, 0, sizeof(struct timeval));
+ memset(&my.archivePacket->start, 0, sizeof(struct timeval));
+ memset(&my.archivePacket->end, 0, sizeof(struct timeval));
+ } else {
+ __pmtimevalNow(&now);
+ my.archivePacket->position = *position;
+ my.archivePacket->start = *starttime;
+ my.archivePacket->end = *endtime;
+ my.livePacket->position = now;
+ my.livePacket->start = now;
+ my.livePacket->end = now;
+ }
+ strncpy(my.tzData, (const char *)tzstring.toAscii(), tzlen+1);
+ strncpy(my.tzData + tzlen+1, (const char *)tzlabel.toAscii(), lablen+1);
+
+ if (port <= 0) {
+ startTimeServer();
+ } else {
+ my.tcpPort = port;
+ liveConnect();
+ archiveConnect();
+ }
+}
+
+void TimeControl::addArchive(
+ struct timeval starttime, struct timeval endtime,
+ QString tzstring, QString tzlabel, bool atEnd)
+{
+ QmcTime::Packet *message;
+ int tzlen = tzstring.length(), lablen = tzlabel.length();
+ int sz = sizeof(QmcTime::Packet) + tzlen + 1 + lablen + 1;
+
+ if (my.archivePacket->position.tv_sec == 0) { // first archive
+ my.archivePacket->position = atEnd ? endtime : starttime;
+ my.archivePacket->start = starttime;
+ my.archivePacket->end = endtime;
+ }
+
+ if ((message = (QmcTime::Packet *)malloc(sz)) == NULL)
+ nomem();
+ *message = *my.archivePacket;
+ message->command = QmcTime::Bounds;
+ message->length = sz;
+ message->start = starttime;
+ message->end = endtime;
+ strncpy((char *)message->data, (const char *)tzstring.toAscii(), tzlen+1);
+ strncpy((char *)message->data + tzlen+1,
+ (const char *)tzlabel.toAscii(), lablen+1);
+ if (my.archiveSocket->write((const char *)message, sz) < 0)
+ QMessageBox::warning(0,
+ QApplication::tr("Error"),
+ QApplication::tr("Cannot update pmtime boundaries."),
+ QApplication::tr("Quit") );
+ free(message);
+}
+
+void TimeControl::liveConnect()
+{
+ console->post("Connecting to pmtime, live source");
+ my.liveSocket->connectToHost(QHostAddress::LocalHost, my.tcpPort);
+}
+
+void TimeControl::archiveConnect()
+{
+ console->post("Connecting to pmtime, archive source");
+ my.archiveSocket->connectToHost(QHostAddress::LocalHost, my.tcpPort);
+}
+
+void TimeControl::showLiveTimeControl(void)
+{
+ my.livePacket->command = QmcTime::GUIShow;
+ my.livePacket->length = sizeof(QmcTime::Packet);
+ if (my.liveSocket->write((const char *)my.livePacket,
+ sizeof(QmcTime::Packet)) < 0)
+ QMessageBox::warning(0,
+ QApplication::tr("Error"),
+ QApplication::tr("Cannot get pmtime to show itself."),
+ QApplication::tr("Quit") );
+}
+
+void TimeControl::showArchiveTimeControl(void)
+{
+ my.archivePacket->command = QmcTime::GUIShow;
+ my.archivePacket->length = sizeof(QmcTime::Packet);
+ if (my.archiveSocket->write((const char *)my.archivePacket,
+ sizeof(QmcTime::Packet)) < 0)
+ QMessageBox::warning(0,
+ QApplication::tr("Error"),
+ QApplication::tr("Cannot get pmtime to show itself."),
+ QApplication::tr("Quit") );
+}
+
+void TimeControl::hideLiveTimeControl()
+{
+ my.livePacket->command = QmcTime::GUIHide;
+ my.livePacket->length = sizeof(QmcTime::Packet);
+ if (my.liveSocket->write((const char *)my.livePacket,
+ sizeof(QmcTime::Packet)) < 0)
+ QMessageBox::warning(0,
+ QApplication::tr("Error"),
+ QApplication::tr("Cannot get pmtime to hide itself."),
+ QApplication::tr("Quit") );
+}
+
+void TimeControl::hideArchiveTimeControl()
+{
+ my.archivePacket->command = QmcTime::GUIHide;
+ my.archivePacket->length = sizeof(QmcTime::Packet);
+ if (my.archiveSocket->write((const char *)my.archivePacket,
+ sizeof(QmcTime::Packet)) < 0)
+ QMessageBox::warning(0,
+ QApplication::tr("Error"),
+ QApplication::tr("Cannot get pmtime to hide itself."),
+ QApplication::tr("Quit") );
+}
+
+void TimeControl::endTimeControl(void)
+{
+ QMessageBox::warning(0,
+ QApplication::tr("Error"),
+ QApplication::tr("Time Control process pmtime has exited."),
+ QApplication::tr("Quit") );
+ exit(-1);
+}
+
+void TimeControl::liveCloseConnection()
+{
+ console->post("Disconnected from pmtime, live source");
+ QMessageBox::warning(0,
+ QApplication::tr("Error"),
+ QApplication::tr("Connection to pmtime lost (live mode)."),
+ QApplication::tr("Quit") );
+ quit();
+}
+
+void TimeControl::archiveCloseConnection()
+{
+ console->post("Disconnected from pmtime, archive source");
+ QMessageBox::warning(0,
+ QApplication::tr("Error"),
+ QApplication::tr("Connection to pmtime lost (archive mode)."),
+ QApplication::tr("Quit") );
+ quit();
+}
+
+void TimeControl::liveSocketConnected()
+{
+ if (my.liveSocket->write((const char *)my.livePacket,
+ sizeof(QmcTime::Packet)) < 0) {
+ QMessageBox::critical(0,
+ QApplication::tr("Fatal error"),
+ QApplication::tr(
+ "Failed socket write in live pmtime negotiation."),
+ QApplication::tr("Quit") );
+ exit(1);
+ }
+ if (my.liveSocket->write((const char *)my.tzData, my.tzLength) < 0) {
+ QMessageBox::critical(0,
+ QApplication::tr("Fatal error"),
+ QApplication::tr(
+ "Failed to send timezone in live pmtime negotiation."),
+ QApplication::tr("Quit"));
+ exit(1);
+ }
+ my.liveState = TimeControl::AwaitingACK;
+}
+
+void TimeControl::archiveSocketConnected()
+{
+ if (my.archiveSocket->write((const char *)my.archivePacket,
+ sizeof(QmcTime::Packet)) < 0) {
+ QMessageBox::critical(0,
+ QApplication::tr("Fatal error"),
+ QApplication::tr(
+ "Failed socket write in archive pmtime negotiation."),
+ QApplication::tr("Quit") );
+ exit(1);
+ }
+ if (my.archiveSocket->write((const char *)my.tzData, my.tzLength) < 0) {
+ QMessageBox::critical(0,
+ QApplication::tr("Fatal error"),
+ QApplication::tr(
+ "Failed timezone send in archive pmtime negotiation."),
+ QApplication::tr("Quit") );
+ exit(1);
+ }
+ my.archiveState = TimeControl::AwaitingACK;
+}
+
+//
+// Start a shiny new pmtime process.
+// The one process serves time for all (live and archive) tabs.
+// We do have to specify which form will be used first, however.
+//
+void TimeControl::startTimeServer()
+{
+ QStringList arguments;
+
+ if (pmDebug & DBG_TRACE_TIMECONTROL)
+ arguments << "-D" << "all";
+ connect(this, SIGNAL(finished(int, QProcess::ExitStatus)), this,
+ SLOT(endTimeControl()));
+ connect(this, SIGNAL(readyReadStandardOutput()), this,
+ SLOT(readPortFromStdout()));
+ start("pmtime", arguments);
+}
+
+//
+// When pmtime starts in "port probe" mode, port# is written to
+// stdout. We can only complete negotiation once we have that...
+//
+void TimeControl::readPortFromStdout(void)
+{
+ bool ok;
+ QString data = readAllStandardOutput();
+
+ my.tcpPort = data.remove("port=").toInt(&ok, 10);
+ if (!ok) {
+ QMessageBox::critical(0,
+ QApplication::tr("Fatal error"),
+ QApplication::tr("Bad port number from pmtime program."),
+ QApplication::tr("Quit") );
+ exit(1);
+ }
+
+ liveConnect();
+ archiveConnect();
+}
+
+void TimeControl::protocolMessage(bool live,
+ QmcTime::Packet *packet, QTcpSocket *socket, ProtocolState *state)
+{
+ int sts, need = sizeof(QmcTime::Packet), offset = 0;
+ QmcTime::Packet *msg;
+
+ // Read one pmtime packet, handling both small reads and large packets
+ for (;;) {
+ sts = socket->read(my.buffer + offset, need);
+ if (sts < 0) {
+ QMessageBox::critical(0,
+ QApplication::tr("Fatal error"),
+ QApplication::tr("Failed socket read in pmtime transfer."),
+ QApplication::tr("Quit") );
+ exit(1);
+ }
+ else if (sts != need) {
+ need -= sts;
+ offset += sts;
+ continue;
+ }
+
+ msg = (QmcTime::Packet *)my.buffer;
+ if (msg->magic != QmcTime::Magic) {
+ QMessageBox::critical(0,
+ QApplication::tr("Fatal error"),
+ QApplication::tr("Bad client message magic number."),
+ QApplication::tr("Quit") );
+ exit(1);
+ }
+ if (msg->length > my.bufferLength) {
+ my.bufferLength = msg->length;
+ my.buffer = (char *)realloc(my.buffer, my.bufferLength);
+ if (!my.buffer)
+ nomem();
+ msg = (QmcTime::Packet *)my.buffer;
+ }
+ if (msg->length > (uint)offset + sts) {
+ offset += sts;
+ need = msg->length - offset;
+ continue;
+ }
+ break;
+ }
+
+#if DESPERATE
+ console->post(PmChart::DebugProtocol,
+ "TimeControl::protocolMessage: recv pos=%s state=%d",
+ timeString(tosec(packet->position)), *state);
+#endif
+
+ switch (*state) {
+ case TimeControl::AwaitingACK:
+ if (msg->command != QmcTime::ACK) {
+ QMessageBox::critical(0,
+ QApplication::tr("Fatal error"),
+ QApplication::tr("Initial ACK not received from pmtime."),
+ QApplication::tr("Quit") );
+ exit(1);
+ }
+ if (msg->source != packet->source) {
+ QMessageBox::critical(0,
+ QApplication::tr("Fatal error"),
+ QApplication::tr("pmtime not serving same metric source."),
+ QApplication::tr("Quit") );
+ exit(1);
+ }
+ *state = TimeControl::ClientReady;
+ if (msg->length > packet->length) {
+ packet = (QmcTime::Packet *)realloc(packet, msg->length);
+ if (!packet)
+ nomem();
+ }
+ //
+ // Note: we drive the local state from the time control values,
+ // and _not_ from the values that we initially sent to it.
+ //
+ memcpy(packet, msg, msg->length);
+ pmchart->VCRMode(live, msg, true);
+ break;
+
+ case TimeControl::ClientReady:
+ if (msg->command == QmcTime::Step) {
+ pmchart->step(live, msg);
+ msg->command = QmcTime::ACK;
+ msg->length = sizeof(QmcTime::Packet);
+ sts = socket->write((const char *)msg, msg->length);
+ if (sts < 0 || sts != (int)msg->length) {
+ QMessageBox::critical(0,
+ QApplication::tr("Fatal error"),
+ QApplication::tr("Failed pmtime write for STEP ACK."),
+ QApplication::tr("Quit") );
+ exit(1);
+ }
+ } else if (msg->command == QmcTime::VCRMode ||
+ msg->command == QmcTime::VCRModeDrag ||
+ msg->command == QmcTime::Bounds) {
+ pmchart->VCRMode(live, msg, msg->command == QmcTime::VCRModeDrag);
+ } else if (msg->command == QmcTime::TZ) {
+ pmchart->timeZone(live, msg, (char *)msg->data);
+ }
+ break;
+
+ default:
+ QMessageBox::critical(0,
+ QApplication::tr("Fatal error"),
+ QApplication::tr("Protocol error with pmtime."),
+ QApplication::tr("Quit") );
+ // fall through
+ case TimeControl::Disconnected:
+ exit(1);
+ }
+}
+
+void TimeControl::protocolMessageLoop(bool live,
+ QmcTime::Packet *packet, QTcpSocket *socket, ProtocolState *state)
+{
+ do {
+ protocolMessage(live, packet, socket, state);
+ } while (socket->bytesAvailable() >= (int)sizeof(QmcTime::Packet));
+}