diff options
Diffstat (limited to 'src/libpcp_qmc')
-rw-r--r-- | src/libpcp_qmc/GNUmakefile | 27 | ||||
-rw-r--r-- | src/libpcp_qmc/src/GNUmakefile | 24 | ||||
-rw-r--r-- | src/libpcp_qmc/src/libpcp_qmc.pro | 13 | ||||
-rw-r--r-- | src/libpcp_qmc/src/qmc.h | 26 | ||||
-rw-r--r-- | src/libpcp_qmc/src/qmc_context.cpp | 402 | ||||
-rw-r--r-- | src/libpcp_qmc/src/qmc_context.h | 115 | ||||
-rw-r--r-- | src/libpcp_qmc/src/qmc_desc.cpp | 203 | ||||
-rw-r--r-- | src/libpcp_qmc/src/qmc_desc.h | 49 | ||||
-rw-r--r-- | src/libpcp_qmc/src/qmc_group.cpp | 521 | ||||
-rw-r--r-- | src/libpcp_qmc/src/qmc_group.h | 103 | ||||
-rw-r--r-- | src/libpcp_qmc/src/qmc_indom.cpp | 481 | ||||
-rw-r--r-- | src/libpcp_qmc/src/qmc_indom.h | 137 | ||||
-rw-r--r-- | src/libpcp_qmc/src/qmc_metric.cpp | 1248 | ||||
-rw-r--r-- | src/libpcp_qmc/src/qmc_metric.h | 336 | ||||
-rw-r--r-- | src/libpcp_qmc/src/qmc_source.cpp | 425 | ||||
-rw-r--r-- | src/libpcp_qmc/src/qmc_source.h | 108 | ||||
-rw-r--r-- | src/libpcp_qmc/src/qmc_time.cpp | 174 | ||||
-rw-r--r-- | src/libpcp_qmc/src/qmc_time.h | 121 |
18 files changed, 4513 insertions, 0 deletions
diff --git a/src/libpcp_qmc/GNUmakefile b/src/libpcp_qmc/GNUmakefile new file mode 100644 index 0000000..552118e --- /dev/null +++ b/src/libpcp_qmc/GNUmakefile @@ -0,0 +1,27 @@ +# +# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved. +# +# This library is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. +# +# This library 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 Lesser General Public +# License for more details. +# + +TOPDIR = ../.. +include $(TOPDIR)/src/include/builddefs + +SUBDIRS = src + +default install : $(SUBDIRS) + $(SUBDIRS_MAKERULE) + +include $(BUILDRULES) + +default_pcp : default + +install_pcp : install diff --git a/src/libpcp_qmc/src/GNUmakefile b/src/libpcp_qmc/src/GNUmakefile new file mode 100644 index 0000000..3c8d8f8 --- /dev/null +++ b/src/libpcp_qmc/src/GNUmakefile @@ -0,0 +1,24 @@ +TOPDIR = ../../.. +LIBRARY = libpcp_qmc +PROJECT = $(LIBRARY).pro +include $(TOPDIR)/src/include/builddefs + +HEADERS = $(shell echo *.h) +SOURCES = $(shell echo *.cpp) + +default: build-me + +include $(BUILDRULES) + +ifeq "$(ENABLE_QT)" "true" +build-me: $(PROJECT) + $(QTMAKE) +else +build-me: +endif + +install: default + +default_pcp: default + +install_pcp: install diff --git a/src/libpcp_qmc/src/libpcp_qmc.pro b/src/libpcp_qmc/src/libpcp_qmc.pro new file mode 100644 index 0000000..2be408f --- /dev/null +++ b/src/libpcp_qmc/src/libpcp_qmc.pro @@ -0,0 +1,13 @@ +TARGET = pcp_qmc +TEMPLATE = lib +VERSION = 1.0.0 +CONFIG += qt staticlib warn_on +INCLUDEPATH += ../../include + +HEADERS = qmc_context.h qmc_desc.h qmc_group.h \ + qmc_indom.h qmc_metric.h qmc_source.h \ + qmc_time.h + +SOURCES = qmc_context.cpp qmc_desc.cpp qmc_group.cpp \ + qmc_indom.cpp qmc_metric.cpp qmc_source.cpp \ + qmc_time.cpp diff --git a/src/libpcp_qmc/src/qmc.h b/src/libpcp_qmc/src/qmc.h new file mode 100644 index 0000000..cdd0f99 --- /dev/null +++ b/src/libpcp_qmc/src/qmc.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 1998-2005 Silicon Graphics, Inc. All Rights Reserved. + * Copyright (c) 2007 Aconex. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + */ +#ifndef QMC_H +#define QMC_H + +#include <pcp/pmapi.h> +#include <pcp/impl.h> + +// +// Classes +// +class QmcContext; +class QmcDesc; +class QmcGroup; +class QmcIndom; +class QmcMetric; +class QmcSource; + +#endif // QMC_H diff --git a/src/libpcp_qmc/src/qmc_context.cpp b/src/libpcp_qmc/src/qmc_context.cpp new file mode 100644 index 0000000..0a45a61 --- /dev/null +++ b/src/libpcp_qmc/src/qmc_context.cpp @@ -0,0 +1,402 @@ +/* + * Copyright (c) 2012 Red Hat. + * Copyright (c) 2007-2008 Aconex. All Rights Reserved. + * Copyright (c) 1997,2005 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + */ + +#include "qmc_context.h" +#include "qmc_metric.h" +#include <limits.h> +#include <QVector> +#include <QStringList> +#include <QHashIterator> + +QStringList *QmcContext::theStringList; + +QmcContext::QmcContext(QmcSource* source) +{ + my.delta = 0.0; + my.context = -1; + my.source = source; + my.needReconnect = false; + + if (my.source->status() >= 0) + my.context = my.source->dupContext(); + else + my.context = my.source->status(); +} + +QmcContext::~QmcContext() +{ + while (my.metrics.isEmpty() == false) { + delete my.metrics.takeFirst(); + } + QHashIterator<pmID, QmcDesc*> descs(my.descCache); + while (descs.hasNext()) { + descs.next(); + delete descs.value(); + } + while (my.indoms.isEmpty() == false) { + delete my.indoms.takeFirst(); + } + if (my.context >= 0) + my.source->delContext(my.context); +} + +int +QmcContext::lookupName(pmID pmid, QString **name) +{ + char *value; + int sts = 0; + + if ((sts = pmUseContext(my.context)) < 0) + return sts; + + if (my.pmidCache.contains(pmid) == false) { + if ((sts = pmNameID(pmid, &value)) >= 0) { + *name = new QString(value); + my.pmidCache.insert(pmid, *name); + free(value); + } + } else { + QString *np = my.pmidCache.value(pmid); + if (pmDebug & DBG_TRACE_PMC) { + QTextStream cerr(stderr); + cerr << "QmcContext::lookupName: Matched id " + << pmIDStr(pmid) << " to \"" << *np << "\"" << endl; + } + *name = np; + } + return sts; +} + +int +QmcContext::lookupPMID(const char *name, pmID& id) +{ + QString key = name; + int sts; + + if ((sts = pmUseContext(my.context)) < 0) + return sts; + + if (my.nameCache.contains(key) == false) { + if ((sts = pmLookupName(1, (char **)(&name), &id)) >= 0) + my.nameCache.insert(key, id); + } else { + id = my.nameCache.value(key); + if (pmDebug & DBG_TRACE_PMC) { + QTextStream cerr(stderr); + cerr << "QmcContext::lookupPMID: Matched \"" << name + << "\" to id " << pmIDStr(id) << endl; + } + sts = 1; + } + return sts; +} + +int +QmcContext::lookupInDom(const char *name, uint_t& indom) +{ + pmID pmid; + int sts = lookupPMID(name, pmid); + if (sts < 0) + return sts; + return lookupInDom(pmid, indom); +} + +int +QmcContext::lookupDesc(pmID pmid, QmcDesc **descriptor) +{ + int sts; + QmcDesc *descPtr; + + if ((sts = pmUseContext(my.context)) < 0) + return sts; + + if (my.descCache.contains(pmid) == false) { + descPtr = new QmcDesc(pmid); + if (descPtr->status() < 0) { + sts = descPtr->status(); + delete descPtr; + return sts; + } + my.descCache.insert(pmid, descPtr); + if (pmDebug & DBG_TRACE_PMC) { + QTextStream cerr(stderr); + cerr << "QmcContext::lookupDesc: Add descriptor for " + << pmIDStr(descPtr->id()) << endl; + } + } + else { + descPtr = my.descCache.value(pmid); + if (pmDebug & DBG_TRACE_PMC) { + QTextStream cerr(stderr); + cerr << "QmcContext::lookupDesc: Reusing descriptor " + << pmIDStr(descPtr->id()) << endl; + } + } + *descriptor = descPtr; + return 0; +} + +int +QmcContext::lookup(pmID pmid, QString **namePtr, QmcDesc **descPtr, QmcIndom **indomPtr) +{ + uint_t indom; + int sts; + + if ((sts = lookupName(pmid, namePtr)) < 0) + return sts; + if ((sts = lookupDesc(pmid, descPtr)) < 0) + return sts; + if ((sts = lookupInDom(pmid, indom)) < 0) + return sts; + *indomPtr = (indom == UINT_MAX) ? NULL : my.indoms[indom]; + return 0; +} + +int +QmcContext::lookupInDom(pmID pmid, uint_t& indom) +{ + QmcDesc *descPtr; + int sts; + + if ((sts = lookupDesc(pmid, &descPtr)) < 0) + return sts; + return lookupInDom(descPtr, indom); +} + +int +QmcContext::lookupInDom(QmcDesc *descPtr, uint_t& indom) +{ + int i, sts; + QmcIndom *indomPtr; + + if ((sts = pmUseContext(my.context)) < 0) + return sts; + + indom = UINT_MAX; + if (descPtr->desc().indom != PM_INDOM_NULL) { + for (i = 0; i < my.indoms.size(); i++) + if (my.indoms[i]->id() == (int)descPtr->desc().indom) + break; + if (i == my.indoms.size()) { + indomPtr = new QmcIndom(my.source->type(), *descPtr); + if (indomPtr->status() < 0) { + sts = indomPtr->status(); + delete indomPtr; + return sts; + } + my.indoms.append(indomPtr); + indom = my.indoms.size() - 1; + if (pmDebug & DBG_TRACE_PMC) { + QTextStream cerr(stderr); + cerr << "QmcContext::lookupInDom: Add indom for " + << pmInDomStr(indomPtr->id()) << endl; + } + } + else { + indomPtr = my.indoms[i]; + indom = i; + if (pmDebug & DBG_TRACE_PMC) { + QTextStream cerr(stderr); + cerr << "QmcContext::lookupInDom: Reusing indom " + << pmInDomStr(indomPtr->id()) << endl; + } + } + } + return 0; +} + +int +QmcContext::useTZ() +{ + if (my.source->tzHandle() >= 0) + return pmUseZone(my.source->tzHandle()); + return 0; +} + +QTextStream& +operator<<(QTextStream &stream, const QmcContext &context) +{ + stream << context.source().desc() << " has " + << context.numMetrics() << " metrics"; + return stream; +} + +void +QmcContext::dump(QTextStream &stream) +{ + stream << "Context " << my.context << " has " << my.nameCache.size() + << " metric names for source:" << endl; + my.source->dump(stream); +} + +void +QmcContext::dumpMetrics(QTextStream &stream) +{ + for (int i = 0; i < my.metrics.size(); i++) + stream << " [" << i << "] " + << my.metrics[i]->spec(false, true) << endl; +} + +void +QmcContext::addMetric(QmcMetric *metric) +{ + pmID pmid; + int i; + + my.metrics.append(metric); + if (metric->status() >= 0) { + pmid = metric->desc().desc().pmid; + for (i = 0; i < my.pmids.size(); i++) + if (my.pmids[i] == pmid) + break; + if (i == my.pmids.size()) + my.pmids.append(pmid); + metric->setIdIndex(i); + } +} + +int +QmcContext::fetch(bool update) +{ + int i, sts; + pmResult *result; + + for (i = 0; i < my.metrics.size(); i++) { + QmcMetric *metric = my.metrics[i]; + if (metric->status() < 0) + continue; + metric->shiftValues(); + } + + // Inform each indom that we are about to do a new fetch so any + // indom changes are now irrelevant + for (i = 0; i < my.indoms.size(); i++) + my.indoms[i]->newFetch(); + + sts = pmUseContext(my.context); + if (sts >= 0) { + for (i = 0; i < my.indoms.size(); i++) { + if (my.indoms[i]->diffProfile()) + sts = my.indoms[i]->genProfile(); + } + } + else if (pmDebug & DBG_TRACE_OPTFETCH) { + QTextStream cerr(stderr); + cerr << "QmcContext::fetch: Unable to switch to this context: " + << pmErrStr(sts) << endl; + } + + if (sts >= 0 && my.needReconnect) { + sts = pmReconnectContext(my.context); + if (sts >= 0) { + my.needReconnect = false; + if (pmDebug & DBG_TRACE_PMC) { + QTextStream cerr(stderr); + cerr << "QmcContext::fetch: Reconnected context \"" + << *my.source << endl; + } + } + else if (pmDebug & DBG_TRACE_PMC) { + QTextStream cerr(stderr); + cerr << "QmcContext::fetch: Reconnect failed: " + << pmErrStr(sts) << endl; + } + } + + if (sts >= 0 && my.pmids.size()) { + if (pmDebug & DBG_TRACE_OPTFETCH) { + QTextStream cerr(stderr); + cerr << "QmcContext::fetch: fetching context " << *this << endl; + } + + sts = pmFetch(my.pmids.size(), + (pmID *)(my.pmids.toVector().data()), &result); + if (sts >= 0) { + my.previousTime = my.currentTime; + my.currentTime = result->timestamp; + my.delta = __pmtimevalSub(&my.currentTime, &my.previousTime); + for (i = 0; i < my.metrics.size(); i++) { + QmcMetric *metric = my.metrics[i]; + if (metric->status() < 0) + continue; + Q_ASSERT((int)metric->idIndex() < result->numpmid); + metric->extractValues(result->vset[metric->idIndex()]); + } + pmFreeResult(result); + } + else { + if (pmDebug & DBG_TRACE_OPTFETCH) { + QTextStream cerr(stderr); + cerr << "QmcContext::fetch: pmFetch: " << pmErrStr(sts) << endl; + } + for (i = 0; i < my.metrics.size(); i++) { + QmcMetric *metric = my.metrics[i]; + if (metric->status() < 0) + continue; + metric->setError(sts); + } + if (sts == PM_ERR_IPC || sts == PM_ERR_TIMEOUT) + my.needReconnect = true; + } + + if (update) { + if (pmDebug & DBG_TRACE_OPTFETCH) { + QTextStream cerr(stderr); + cerr << "QmcContext::fetch: Updating metrics" << endl; + } + for (i = 0; i < my.metrics.size(); i++) { + QmcMetric *metric = my.metrics[i]; + if (metric->status() < 0) + continue; + metric->update(); + } + } + } + else if (pmDebug & DBG_TRACE_OPTFETCH) { + QTextStream cerr(stderr); + cerr << "QmcContext::fetch: nothing to fetch" << endl; + } + + return sts; +} + +void +QmcContext::dometric(const char *name) +{ + theStringList->append(name); +} + +int +QmcContext::traverse(const char *name, QStringList &list) +{ + int sts; + + theStringList = &list; + theStringList->clear(); + + if ((sts = pmUseContext(my.context)) < 0) + return sts; + + sts = pmTraversePMNS(name, QmcContext::dometric); + + if (pmDebug & DBG_TRACE_PMC) { + QTextStream cerr(stderr); + if (sts >= 0) { + cerr << "QmcContext::traverse: Found " << list.size() + << " names from " << name << endl; + } + else + cerr << "QmcContext::traverse: Failed: " << pmErrStr(sts) + << endl; + } + + return sts; +} diff --git a/src/libpcp_qmc/src/qmc_context.h b/src/libpcp_qmc/src/qmc_context.h new file mode 100644 index 0000000..8751597 --- /dev/null +++ b/src/libpcp_qmc/src/qmc_context.h @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2012 Red Hat. + * Copyright (c) 2007 Aconex. All Rights Reserved. + * Copyright (c) 1998-2005 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + */ +#ifndef QMC_CONTEXT_H +#define QMC_CONTEXT_H + +#include "qmc.h" +#include "qmc_desc.h" +#include "qmc_indom.h" +#include "qmc_source.h" + +#include <qhash.h> +#include <qlist.h> +#include <qstring.h> +#include <qtextstream.h> + +class QmcContext +{ +public: + QmcContext(QmcSource *source); + ~QmcContext(); + + int status() const // Is this a valid context? + { return (my.context < 0) ? my.context : 0; } + + int handle() const // The PMAPI context handle + { return my.context; } + + QmcSource const& source() const // Source description + { return *my.source; } + + unsigned int numIDs() const // Number of unique pmIDs in use + { return my.pmids.size(); } + + pmID id(unsigned int index) const // Access to each unique pmID + { return my.pmids[index]; } + + QmcDesc const& desc(pmID pmid) const + { return *(my.descCache.value(pmid)); } + QmcDesc& desc(pmID pmid) // Access to each descriptor + { return *(my.descCache.value(pmid)); } + + unsigned int numIndoms() const // Number of indom descriptors + { return my.indoms.size(); } // requested from this context + + QmcIndom const& indom(unsigned int index) const + { return *(my.indoms[index]); } + QmcIndom& indom(unsigned int index) // Access to each indom + { return *(my.indoms[index]); } + + // Lookup the pmid or indom (implies descriptor) for metric <name>|<id> + int lookupPMID(const char *name, pmID& id); + int lookupInDom(const char *name, unsigned int& indom); + int lookupInDom(QmcDesc *desc, uint_t& indom); + + // Lookup various structures using the pmid + int lookupInDom(pmID pmid, unsigned int& indom); + int lookupName(pmID pmid, QString **name); + int lookupDesc(pmID pmid, QmcDesc **desc); + int lookup(pmID pmid, QString **name, QmcDesc **desc, QmcIndom **indom); + + int useTZ(); // Use this timezone + + unsigned int numMetrics() const // Number of metrics using this context + { return my.metrics.size(); } + + QmcMetric const& metric(unsigned int index) const + { return *(my.metrics[index]); } + QmcMetric& metric(unsigned int index) // Get a handle to a metric + { return *(my.metrics[index]); } + + void addMetric(QmcMetric* metric); // Add a metric using this context + + int fetch(bool update); // Fetch metrics using this context + + struct timeval const& timeStamp() const + { return my.currentTime; } + + double timeDelta() const // Time since previous fetch + { return my.delta; } + + int traverse(const char *name, QStringList &list); // Walk the namespace + + friend QTextStream &operator<<(QTextStream &stream, const QmcContext &rhs); + void dump(QTextStream &stream); // Dump debugging information + void dumpMetrics(QTextStream &stream); // Dump list of metrics + +private: + struct { + int context; // PMAPI Context handle + bool needReconnect; // Need to reconnect the context + QmcSource *source; // Handle to the source description + QHash<QString, pmID> nameCache; // Reverse map from names to PMIDs + QHash<pmID, QString*> pmidCache;// Mapping between PMIDs and names + QHash<pmID, QmcDesc*> descCache;// Mapping between PMIDs and descs + QList<pmID> pmids; // List of valid PMIDs to be fetched + QList<QmcIndom*> indoms; // List of requested indoms + QList<QmcMetric*> metrics; // List of metrics using this context + struct timeval currentTime; // Time of current fetch + struct timeval previousTime; // Time of previous fetch + double delta; // Time between fetches + } my; + + static QStringList *theStringList; // List of metric names in traversal + static void dometric(const char *); +}; + +#endif // QMC_CONTEXT_H diff --git a/src/libpcp_qmc/src/qmc_desc.cpp b/src/libpcp_qmc/src/qmc_desc.cpp new file mode 100644 index 0000000..af496d8 --- /dev/null +++ b/src/libpcp_qmc/src/qmc_desc.cpp @@ -0,0 +1,203 @@ +/* + * Copyright (c) 1998,2005 Silicon Graphics, Inc. All Rights Reserved. + * Copyright (c) 2007 Aconex. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + */ +#include "qmc_desc.h" +#include <QTextStream> + +QmcDesc::QmcDesc(pmID pmid) +{ + my.pmid = pmid; + my.scaleFlag = false; + my.status = pmLookupDesc(my.pmid, &my.desc); + if (my.status >= 0) { + my.scaleUnits = my.desc.units; + setUnitStrings(); + } + else if (pmDebug & DBG_TRACE_PMC) { + QTextStream cerr(stderr); + cerr << "QmcDesc::QmcDesc: unable to lookup " + << pmIDStr(my.pmid) << ": " << pmErrStr(my.status) << endl; + } +} + +void +QmcDesc::setUnitStrings() +{ + const char *units = pmUnitsStr(&my.scaleUnits); + const char *shortUnits = shortUnitsString(&my.scaleUnits); + + if (my.desc.sem == PM_SEM_COUNTER) { + if (my.scaleFlag && + my.scaleUnits.dimTime == 1 && + my.scaleUnits.dimSpace == 0 && + my.scaleUnits.dimCount == 0) { + my.units = "Time Utilization"; + my.shortUnits = "util"; + } + else { + my.units = units; + my.units.append(" / second"); + my.shortUnits = shortUnits; + my.shortUnits.append("/s"); + } + } + else { + if (units[0] == '\0') + my.units = "none"; + else + my.units = units; + if (shortUnits[0] == '\0') + my.shortUnits = "none"; + else + my.shortUnits = shortUnits; + } +} + +void +QmcDesc::setScaleUnits(const pmUnits &units) +{ + my.scaleUnits = units; + my.scaleFlag = true; + setUnitStrings(); +} + +const char * +QmcDesc::shortUnitsString(pmUnits *pu) +{ + const char *spaceString, *timeString, *countString; + char sbuf[20], tbuf[20], cbuf[20]; + static char buf[60]; + char *p; + + spaceString = timeString = countString = NULL; + buf[0] = '\0'; + + if (pu->dimSpace) { + switch (pu->scaleSpace) { + case PM_SPACE_BYTE: + spaceString = "b"; + break; + case PM_SPACE_KBYTE: + spaceString = "Kb"; + break; + case PM_SPACE_MBYTE: + spaceString = "Mb"; + break; + case PM_SPACE_GBYTE: + spaceString = "Gb"; + break; + case PM_SPACE_TBYTE: + spaceString = "Tb"; + break; + default: + sprintf(sbuf, "space-%d", pu->scaleSpace); + spaceString = sbuf; + break; + } + } + if (pu->dimTime) { + switch (pu->scaleTime) { + case PM_TIME_NSEC: + timeString = "ns"; + break; + case PM_TIME_USEC: + timeString = "us"; + break; + case PM_TIME_MSEC: + timeString = "msec"; + break; + case PM_TIME_SEC: + timeString = "s"; + break; + case PM_TIME_MIN: + timeString = "m"; + break; + case PM_TIME_HOUR: + timeString = "h"; + break; + default: + sprintf(tbuf, "time-%d", pu->scaleTime); + timeString = tbuf; + break; + } + } + if (pu->dimCount) { + switch (pu->scaleCount) { + case 0: + countString = "c"; + break; + case 1: + countString = "cx10"; + break; + default: + sprintf(cbuf, "cx10^%d", pu->scaleCount); + countString = cbuf; + break; + } + } + + p = buf; + + if (pu->dimSpace > 0) { + if (pu->dimSpace == 1) + sprintf(p, "%s", spaceString); + else + sprintf(p, "%s^%d", spaceString, pu->dimSpace); + while (*p) p++; + } + if (pu->dimTime > 0) { + if (pu->dimTime == 1) + sprintf(p, "%s", timeString); + else + sprintf(p, "%s^%d", timeString, pu->dimTime); + while (*p) p++; + } + if (pu->dimCount > 0) { + if (pu->dimCount == 1) + sprintf(p, "%s", countString); + else + sprintf(p, "%s^%d", countString, pu->dimCount); + while (*p) p++; + } + if (pu->dimSpace < 0 || pu->dimTime < 0 || pu->dimCount < 0) { + *p++ = '/'; + if (pu->dimSpace < 0) { + if (pu->dimSpace == -1) + sprintf(p, "%s", spaceString); + else + sprintf(p, "%s^%d", spaceString, -pu->dimSpace); + while (*p) p++; + } + if (pu->dimTime < 0) { + if (pu->dimTime == -1) + sprintf(p, "%s", timeString); + else + sprintf(p, "%s^%d", timeString, -pu->dimTime); + while (*p) p++; + } + if (pu->dimCount < 0) { + if (pu->dimCount == -1) + sprintf(p, "%s", countString); + else + sprintf(p, "%s^%d", countString, -pu->dimCount); + while (*p) p++; + } + } + + if (buf[0] == '\0') { + if (pu->scaleCount == 1) + sprintf(buf, "x10"); + else if (pu->scaleCount != 0) + sprintf(buf, "x10^%d", pu->scaleCount); + } + else + *p = '\0'; + + return buf; +} diff --git a/src/libpcp_qmc/src/qmc_desc.h b/src/libpcp_qmc/src/qmc_desc.h new file mode 100644 index 0000000..1f269ff --- /dev/null +++ b/src/libpcp_qmc/src/qmc_desc.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 1998-2005 Silicon Graphics, Inc. All Rights Reserved. + * Copyright (c) 2007 Aconex. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + */ +#ifndef QMC_DESC_H +#define QMC_DESC_H + +#include "qmc.h" +#include <qstring.h> + +class QmcDesc +{ +public: + QmcDesc(pmID pmid); + + int status() const { return my.status; } + pmID id() const { return my.pmid; } + pmDesc desc() const { return my.desc; } + const pmDesc *descPtr() const { return &my.desc; } + const QString units() const { return my.units; } + const QString shortUnits() const { return my.shortUnits; } + const pmUnits &scaleUnits() const { return my.scaleUnits; } + + void setScaleUnits(const pmUnits &units); + + // Are we using scaled units provided by a call to setScaleUnits? + bool useScaleUnits() const { return my.scaleFlag; } + +private: + struct { + int status; + pmID pmid; + pmDesc desc; + QString units; + QString shortUnits; + pmUnits scaleUnits; + bool scaleFlag; + } my; + + void setUnitStrings(); + static const char *shortUnitsString(pmUnits *pu); +}; + +#endif // QMC_DESC_H diff --git a/src/libpcp_qmc/src/qmc_group.cpp b/src/libpcp_qmc/src/qmc_group.cpp new file mode 100644 index 0000000..d2ccb72 --- /dev/null +++ b/src/libpcp_qmc/src/qmc_group.cpp @@ -0,0 +1,521 @@ +/* + * Copyright (c) 2013, Red Hat. + * Copyright (c) 2007 Aconex. All Rights Reserved. + * Copyright (c) 1997-2005 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + */ + +#include <limits.h> +#include <float.h> +#include <math.h> + +#include <pcp/pmapi.h> +#include <pcp/impl.h> +#include "qmc_group.h" +#include "qmc_source.h" +#include "qmc_context.h" +#include "qmc_metric.h" + +int QmcGroup::tzLocal = -1; +bool QmcGroup::tzLocalInit = false; +QString QmcGroup::tzLocalString; +QString QmcGroup::localHost; + +QmcGroup::QmcGroup(bool restrictArchives) +{ + my.restrictArchives = restrictArchives; + my.mode = PM_CONTEXT_HOST; + my.use = -1; + my.localSource = 0; + my.tzFlag = unknownTZ; + my.tzDefault = -1; + my.tzUser = -1; + my.tzGroupIndex = 0; + my.timeEndReal = 0.0; + + // Get timezone from environment + if (tzLocalInit == false) { + char buf[MAXHOSTNAMELEN]; + gethostname(buf, MAXHOSTNAMELEN); + buf[MAXHOSTNAMELEN-1] = '\0'; + localHost = buf; + + char *tz = __pmTimezone(); + if (tz == NULL) + pmprintf("%s: Warning: Unable to get timezone from environment\n", + pmProgname); + else { + tzLocal = pmNewZone(tz); + if (tzLocal < 0) + pmprintf("%s: Warning: Timezone for localhost: %s\n", + pmProgname, pmErrStr(tzLocal)); + else { + tzLocalString = tz; + my.tzDefault = tzLocal; + my.tzFlag = localTZ; + } + } + tzLocalInit = true; + } +} + +QmcGroup::~QmcGroup() +{ + for (int i = 0; i < my.contexts.size(); i++) + if (my.contexts[i]) + delete my.contexts[i]; +} + +int +QmcGroup::use(int type, const QString &theSource, int flags) +{ + int sts = 0; + unsigned int i; + QString source(theSource); + + if (type == PM_CONTEXT_LOCAL) { + for (i = 0; i < numContexts(); i++) + if (my.contexts[i]->source().type() == type) + break; + } + else if (type == PM_CONTEXT_ARCHIVE) { + if (source == QString::null) { + pmprintf("%s: Error: Archive context requires archive path\n", + pmProgname); + return PM_ERR_NOCONTEXT; + } + // This doesn't take into account {.N,.meta,.index,} ... but + // then again, nor does pmNewContext. More work ... useful? + for (i = 0; i < numContexts(); i++) { + if (source == my.contexts[i]->source().source()) + break; + } + } + else { + if (source == QString::null) { + if (!defaultDefined()) { + createLocalContext(); + if (!defaultDefined()) { + pmprintf("%s: Error: " + "Cannot connect to PMCD on localhost: %s\n", + pmProgname, + pmErrStr(my.localSource->status())); + return my.localSource->status(); + } + } + source = my.contexts[0]->source().source(); + } + + for (i = 0; i < numContexts(); i++) { + if (source == my.contexts[i]->source().source()) + break; + } + } + + if (i == numContexts()) { + if (pmDebug & DBG_TRACE_PMC) { + QTextStream cerr(stderr); + cerr << "QmcGroup::use: No direct match for context \"" << source + << "\" (type " << type << ")." << endl; + } + + // Determine live or archive mode by the first source + if (i == 0) + my.mode = type; + + // If the assumed mode differs from the requested context type + // we may need to map the host to an archive + if (my.mode != type) { + + if (my.mode == PM_CONTEXT_HOST && type == PM_CONTEXT_ARCHIVE) { + pmprintf("%s: Error: Archive \"%s\" requested " + "after live mode was assumed.\n", pmProgname, + (const char *)source.toAscii()); + return PM_ERR_NOCONTEXT; + } + + // If we are in archive mode, map hosts to archives of same host + if (my.mode == PM_CONTEXT_ARCHIVE && type == PM_CONTEXT_HOST) { + QString chop1 = source.remove(PM_LOG_MAXHOSTLEN-1, INT_MAX); + for (i = 0; i < numContexts(); i++) { + QString chop2 = my.contexts[i]->source().host(); + chop2.remove(PM_LOG_MAXHOSTLEN-1, INT_MAX); + if (chop1 == chop2) + break; + } + + if (i == numContexts()) { + pmprintf("%s: Error: No archives were specified " + "for host \"%s\"\n", pmProgname, + (const char *)source.toAscii()); + return PM_ERR_NOTARCHIVE; + } + } + } + } + + if (i == numContexts()) { + if (pmDebug & DBG_TRACE_PMC) { + QTextStream cerr(stderr); + cerr << "QmcGroup::use: Creating new context for \"" << source + << '\"' << endl; + } + + QmcSource *src = QmcSource::getSource(type, source, flags, false); + if (src == NULL) { + pmprintf("%s: Error: No archives were specified for host \"%s\"\n", + pmProgname, (const char *)source.toAscii()); + return PM_ERR_NOTARCHIVE; + } + + QmcContext *newContext = new QmcContext(src); + if (newContext->handle() < 0) { + sts = newContext->handle(); + pmprintf("%s: Error: %s: %s\n", pmProgname, + (const char *)source.toAscii(), pmErrStr(sts)); + delete newContext; + return sts; + } + + // If we are in archive mode and are adding an archive, + // make sure another archive for the same host does not exist + if (my.restrictArchives && type == PM_CONTEXT_ARCHIVE) { + for (i = 0; i < numContexts(); i++) + // No need to restrict comparison here, both are from + // log labels. + if (my.contexts[i]->source().host() == + newContext->source().host()) { + pmprintf("%s: Error: Archives \"%s\" and \"%s\" are from " + "the same host \"%s\"\n", pmProgname, + my.contexts[i]->source().sourceAscii(), + newContext->source().sourceAscii(), + my.contexts[i]->source().hostAscii()); + delete newContext; + return PM_ERR_NOCONTEXT; + } + } + + my.contexts.append(newContext); + my.use = my.contexts.size() - 1; + + if (pmDebug & DBG_TRACE_PMC) { + QTextStream cerr(stderr); + cerr << "QmcGroup::use: Added context " << my.use << ": " + << *newContext << endl; + } + } + + // We found a match, do we need to use a different context? + else if (i != (unsigned int)my.use) { + my.use = i; + sts = useContext(); + if (sts < 0) { + pmprintf("%s: Error: Unable to use context to %s: %s\n", pmProgname, + context()->source().sourceAscii(), pmErrStr(sts)); + return sts; + } + + if (pmDebug & DBG_TRACE_PMC) { + QTextStream cerr(stderr); + cerr << "QmcGroup::use: Using existing context " << my.use + << " for " << context()->source().desc() << endl; + } + } + else if (pmDebug & DBG_TRACE_PMC) { + QTextStream cerr(stderr); + cerr << "QmcGroup::use: Using current context " << my.use + << " (handle = " << context()->handle() << ") for " + << context()->source().desc() << endl; + } + + return context()->handle(); +} + +int +QmcGroup::useTZ() +{ + int sts = context()->useTZ(); + + if (sts >= 0) { + my.tzDefault = context()->source().tzHandle(); + my.tzFlag = groupTZ; + my.tzGroupIndex = my.use; + if (pmDebug & DBG_TRACE_PMC) { + QTextStream cerr(stderr); + cerr << "QmcGroup::useTZ: Using timezone of " + << context()->source().desc() + << " (" << my.tzGroupIndex << ')' << endl; + } + } + return sts; +} + +int +QmcGroup::useTZ(const QString &tz) +{ + int sts = pmNewZone(tz.toAscii()); + + if (sts >= 0) { + my.tzUser = sts; + my.tzUserString = tz; + my.tzFlag = userTZ; + my.tzDefault = sts; + + if (pmDebug & DBG_TRACE_PMC) { + QTextStream cerr(stderr); + cerr << "QmcGroup::useTZ: Switching timezones to \"" << tz + << "\" (" << my.tzUserString << ')' << endl; + } + } + return sts; +} + +int +QmcGroup::useLocalTZ() +{ + if (tzLocal >= 0) { + int sts = pmUseZone(tzLocal); + if (sts > 0) { + my.tzFlag = localTZ; + my.tzDefault = tzLocal; + if (pmDebug & DBG_TRACE_PMC) { + QTextStream cerr(stderr); + cerr << "QmcGroup::useTZ: Using timezone of host \"localhost\"" + << endl; + } + } + return sts; + } + return tzLocal; +} + +void +QmcGroup::defaultTZ(QString &label, QString &tz) +{ + if (my.tzFlag == userTZ) { + label = my.tzUserString; + tz = my.tzUserString; + } + else if (my.tzFlag == localTZ) { + label = localHost; + tz = tzLocalString; + } + else { + label = my.contexts[my.tzGroupIndex]->source().host(); + tz = my.contexts[my.tzGroupIndex]->source().timezone(); + } +} + +int +QmcGroup::useDefaultTZ() +{ + if (my.tzFlag == unknownTZ) + return -1; + return pmUseZone(my.tzDefault); +} + +int +QmcGroup::useDefault() +{ + if (numContexts() == 0) + createLocalContext(); + if (numContexts() == 0) + return my.localSource->status(); + my.use = 0; + return pmUseContext(context()->handle()); +} + +void +QmcGroup::createLocalContext() +{ + if (numContexts() == 0) { + QTextStream cerr(stderr); + QmcSource *localSource = QmcSource::getSource(PM_CONTEXT_HOST, + localHost, 0, false); + if (localSource->status() < 0 && pmDebug & DBG_TRACE_PMC) + cerr << "QmcGroup::createLocalContext: Default context to " + << localSource->desc() << " failed: " + << pmErrStr(localSource->status()) << endl; + else if (pmDebug & DBG_TRACE_PMC) + cerr << "QmcGroup::createLocalContext: Default context to " + << localSource->desc() << endl; + + QmcContext *newContext = new QmcContext(localSource); + if (newContext->handle() < 0) { + pmprintf("%s: Error: %s: %s\n", pmProgname, + (const char *)localHost.toAscii(), pmErrStr(newContext->handle())); + } + my.contexts.append(newContext); + my.use = my.contexts.size() - 1; + } +} + +void +QmcGroup::updateBounds() +{ + double newStart = DBL_MAX; + double newEnd = 0.0; + double startReal; + double endReal; + struct timeval startTv; + struct timeval endTv; + + my.timeStart.tv_sec = 0; + my.timeStart.tv_usec = 0; + my.timeEnd = my.timeStart; + + for (unsigned int i = 0; i < numContexts(); i++) { + if (my.contexts[i]->handle() >= 0 && + my.contexts[i]->source().type() == PM_CONTEXT_ARCHIVE) { + startTv = my.contexts[i]->source().start(); + endTv = my.contexts[i]->source().end(); + startReal = __pmtimevalToReal(&startTv); + endReal = __pmtimevalToReal(&endTv); + if (startReal < newStart) + newStart = startReal; + if (endReal > newEnd) + newEnd = endReal; + } + } + + __pmtimevalFromReal(newStart, &my.timeStart); + __pmtimevalFromReal(newEnd, &my.timeEnd); + my.timeEndReal = newEnd; + + if (pmDebug & DBG_TRACE_PMC) { + QTextStream cerr(stderr); + cerr << "QmcGroup::updateBounds: start = " << my.timeStart.tv_sec + << '.' << my.timeStart.tv_usec << ", end = " + << my.timeEnd.tv_sec << '.' << my.timeEnd.tv_usec << endl; + } +} + +void +QmcGroup::dump(QTextStream &stream) +{ + stream << "mode: "; + switch(my.mode) { + case PM_CONTEXT_LOCAL: + stream << "local"; + break; + case PM_CONTEXT_HOST: + stream << "live host"; + break; + case PM_CONTEXT_ARCHIVE: + stream << "archive"; + break; + } + + stream << ", timezone: "; + switch(my.tzFlag) { + case QmcGroup::localTZ: + stream << "local = \"" << tzLocalString; + break; + case QmcGroup::userTZ: + stream << "user = \"" << my.tzUserString; + break; + case QmcGroup::groupTZ: + stream << "group = \"" + << my.contexts[my.tzGroupIndex]->source().timezone(); + break; + case QmcGroup::unknownTZ: + stream << "unknown = \"???"; + break; + } + stream << "\": " << endl; + + stream << " " << numContexts() << " contexts:" << endl; + for (unsigned int i = 0; i < numContexts(); i++) { + stream << " [" << i << "] " << *(my.contexts[i]) << endl; + my.contexts[i]->dumpMetrics(stream); + } +} + +int +QmcGroup::useContext() +{ + int sts = 0; + + if ((context()->status() == 0) && + (sts = pmUseContext(context()->handle())) < 0) + pmprintf("%s: Error: Unable to reuse context to %s: %s\n", + pmProgname, context()->source().sourceAscii(), pmErrStr(sts)); + return sts; +} + +QmcMetric * +QmcGroup::addMetric(char const *string, double theScale, bool active) +{ + QmcMetric *metric = new QmcMetric(this, string, theScale, active); + if (metric->status() >= 0) + metric->context()->addMetric(metric); + return metric; +} + +QmcMetric * +QmcGroup::addMetric(pmMetricSpec *theMetric, double theScale, bool active) +{ + QmcMetric *metric = new QmcMetric(this, theMetric, theScale, active); + if (metric->status() >= 0) + metric->context()->addMetric(metric); + return metric; +} + +int +QmcGroup::fetch(bool update) +{ + int sts = 0; + + if (pmDebug & DBG_TRACE_PMC) { + QTextStream cerr(stderr); + cerr << "QmcGroup::fetch: " << numContexts() << " contexts" << endl; + } + + for (unsigned int i = 0; i < numContexts(); i++) + my.contexts[i]->fetch(update); + + if (numContexts()) + sts = useContext(); + + if (pmDebug & DBG_TRACE_PMC) { + QTextStream cerr(stderr); + cerr << "QmcGroup::fetch: Done" << endl; + } + + return sts; +} + +int +QmcGroup::setArchiveMode(int mode, const struct timeval *when, int interval) +{ + int sts, result = 0; + + for (unsigned int i = 0; i < numContexts(); i++) { + if (my.contexts[i]->source().type() != PM_CONTEXT_ARCHIVE) + continue; + + sts = pmUseContext(my.contexts[i]->handle()); + if (sts < 0) { + pmprintf("%s: Error: Unable to switch to context for %s: %s\n", + pmProgname, my.contexts[i]->source().sourceAscii(), + pmErrStr(sts)); + result = sts; + continue; + } + sts = pmSetMode(mode, when, interval); + if (sts < 0) { + pmprintf("%s: Error: Unable to set context mode for %s: %s\n", + pmProgname, my.contexts[i]->source().sourceAscii(), + pmErrStr(sts)); + result = sts; + } + } + sts = useContext(); + if (sts < 0) + result = sts; + return result; +} diff --git a/src/libpcp_qmc/src/qmc_group.h b/src/libpcp_qmc/src/qmc_group.h new file mode 100644 index 0000000..05edadf --- /dev/null +++ b/src/libpcp_qmc/src/qmc_group.h @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2013, Red Hat. + * Copyright (c) 2007 Aconex. All Rights Reserved. + * Copyright (c) 1998-2005 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + */ +#ifndef QMC_GROUP_H +#define QMC_GROUP_H + +#include "qmc.h" +#include "qmc_context.h" + +#include <qlist.h> +#include <qstring.h> +#include <qtextstream.h> + +class QmcGroup +{ +public: + enum TimeZoneFlag { localTZ, userTZ, groupTZ, unknownTZ }; + +public: + QmcGroup(bool restrictArchives = false); + ~QmcGroup(); + + int mode() const { return my.mode; } + + unsigned int numContexts() const { return my.contexts.size(); } + + // Return a handle to the contexts + QmcContext* context() const { return my.contexts[my.use]; } + QmcContext* context(unsigned int index) const { return my.contexts[index]; } + + // Index to the active context + unsigned int contextIndex() const { return my.use; } + + int use(int type, const QString &source, int flags = 0); + int use(unsigned int index) { my.use = index; return useContext(); } + bool defaultDefined() const { return (numContexts() > 0); } + int useDefault(); + + void createLocalContext(); + + // Add a new metric to the group + QmcMetric* addMetric(char const* str, double theScale = 0.0, + bool active = false); + QmcMetric* addMetric(pmMetricSpec* theMetric, double theScale = 0.0, + bool active = false); + + // Fetch all the metrics in this group + // By default, do all rate conversions and counter wraps + int fetch(bool update = true); + + // Set the archive position and mode + int setArchiveMode(int mode, const struct timeval *when, int interval); + + int useTZ(); // Use TZ of current context as default + int useTZ(const QString &tz); // Use this TZ as default + int useLocalTZ(); // Use local TZ as default + void defaultTZ(QString &label, QString &tz); + + TimeZoneFlag defaultTZ() const { return my.tzFlag; } + int useDefaultTZ(); + + struct timeval const& logStart() const { return my.timeStart; } + struct timeval const& logEnd() const { return my.timeEnd; } + void updateBounds(); // Determine the archive start and finish times + + void dump(QTextStream &os); + +private: + struct { + QList<QmcContext*> contexts; // List of all contexts in this group + bool restrictArchives; // Only one archive per host + int mode; // Default context type + int use; // Context in use + QmcSource *localSource; // Localhost source desc + + TimeZoneFlag tzFlag; // default TZ type + int tzDefault; // handle to default TZ + int tzUser; // handle to user defined TZ + QString tzUserString; // user defined TZ; + int tzGroupIndex; // index to group context used for + // current timezone + struct timeval timeStart; // Start of first archive + struct timeval timeEnd; // End of last archive + double timeEndReal; // End of last archive + } my; + + // Timezone for localhost from environment + static bool tzLocalInit; // got TZ from environment + static int tzLocal; // handle to environment TZ + static QString tzLocalString; // environment TZ string + static QString localHost; // name of localhost + + int useContext(); +}; + +#endif // QMC_GROUP_H diff --git a/src/libpcp_qmc/src/qmc_indom.cpp b/src/libpcp_qmc/src/qmc_indom.cpp new file mode 100644 index 0000000..67dd260 --- /dev/null +++ b/src/libpcp_qmc/src/qmc_indom.cpp @@ -0,0 +1,481 @@ +/* + * Copyright (c) 1997,2005 Silicon Graphics, Inc. All Rights Reserved. + * Copyright (c) 2007 Aconex. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library 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 Lesser General Public + * License for more details. + */ + +#include "qmc_indom.h" +#include "qmc_desc.h" +#include <ctype.h> +#include <QVector> +#include <QStringList> + +QmcInstance::QmcInstance() +{ + my.inst = PM_IN_NULL; + my.refCount = 0; + my.index = -1; + my.active = false; +} + +QmcInstance::QmcInstance(int id, const char* name) +{ + my.inst = id; + my.name = name; + my.refCount = 0; + my.index = -1; + my.active = true; +} + +void +QmcInstance::deactivate(int nullIndex) +{ + my.inst = PM_IN_NULL; + my.name = ""; + my.refCount = 0; + my.index = nullIndex; + my.active = false; +} + +QmcInstance const& +QmcInstance::operator=(QmcInstance const& rhs) +{ + if (this != &rhs) { + my.inst = rhs.inst(); + my.name = rhs.name(); + my.refCount = rhs.refCount(); + my.index = rhs.index(); + my.active = rhs.active(); + } + return *this; +} + +QmcIndom::QmcIndom(int type, QmcDesc &desc) +{ + int *instList; + char **nameList; + + my.type = type; + my.id = desc.desc().indom; + my.profile = false; + my.changed = false; + my.updated = true; + my.count = 0; + my.nullCount = 0; + my.nullIndex = UINT_MAX; + my.numActive = 0; + my.numActiveRef = 0; + + if (my.id == PM_INDOM_NULL) + my.status = PM_ERR_INDOM; + else if (my.type == PM_CONTEXT_HOST || my.type == PM_CONTEXT_LOCAL) + my.status = pmGetInDom(my.id, &instList, &nameList); + else if (my.type == PM_CONTEXT_ARCHIVE) + my.status = pmGetInDomArchive(my.id, &instList, &nameList); + else + my.status = PM_ERR_NOCONTEXT; + + if (my.status > 0) { + for (int i = 0; i < my.status; i++) + my.instances.append(QmcInstance(instList[i], nameList[i])); + my.numActive = my.status; + free(instList); + free(nameList); + + if (pmDebug & DBG_TRACE_INDOM) { + QTextStream cerr(stderr); + cerr << "QmcIndom::QmcIndom: indom "; + } + } + else if (my.status < 0 && pmDebug & DBG_TRACE_PMC) { + QTextStream cerr(stderr); + cerr << "QmcIndom::QmcIndom: unable to lookup " + << pmInDomStr(my.id) << " from " + << (my.type == PM_CONTEXT_ARCHIVE ? "archive" : "host/local") + << " source: " << pmErrStr(my.status) << endl; + } +} + +int +QmcIndom::lookup(QString const &name) +{ + int i; + bool ok; + QStringList list; + + for (i = 0; i < my.instances.size(); i++) { + if (my.instances[i].null()) + continue; + if (my.instances[i].name().compare(name) == 0) { + if (my.instances[i].refCount() == 0) { + my.profile = true; + my.count++; + if (my.instances[i].active()) + my.numActiveRef++; + } + my.instances[i].refCountInc(); + return i; + } + } + + // Match up to the first space + // Need this for proc and similiar agents + + for (i = 0; i < my.instances.size(); i++) { + if (my.instances[i].null()) + continue; + list = my.instances[i].name().split(QChar(' ')); + if (list.size() <= 1) + continue; + if (name.compare(list.at(0)) == 0) { + if (pmDebug & DBG_TRACE_PMC) { + QTextStream cerr(stderr); + cerr << "QmcIndom::lookup: inst \"" << name << "\"(" << i + << ") matched to \"" << my.instances[i].name() << "\"(" + << i << ')' << endl; + } + if (my.instances[i].refCount() == 0) { + my.profile = true; + my.count++; + if (my.instances[i].active()) + my.numActiveRef++; + } + my.instances[i].refCountInc(); + return i; + } + } + + // If the instance requested is numeric, then ignore leading + // zeros in the instance up to the first space + int nameNumber = name.toInt(&ok); + + // The requested instance is numeric + if (ok) { + for (i = 0; i < my.instances.size(); i++) { + if (my.instances[i].null()) + continue; + + list = my.instances[i].name().split(QChar(' ')); + if (list.size() <= 1) + continue; + int instNumber = list.at(0).toInt(&ok); + if (!ok) + continue; + if (instNumber == nameNumber) { + if (pmDebug & DBG_TRACE_PMC) { + QTextStream cerr(stderr); + cerr << "QmcIndom::lookup: numerical inst \"" + << name << " matched to \"" << my.instances[i].name() + << "\"(" << i << ')' << endl; + } + if (my.instances[i].refCount() == 0) { + my.profile = true; + my.count++; + if (my.instances[i].active()) + my.numActiveRef++; + } + my.instances[i].refCountInc(); + return i; + } + } + } + + return -1; // we don't know about that instance +} + +void +QmcIndom::refAll(bool active) +{ + my.numActiveRef = 0; + + for (int i = 0; i < my.instances.size(); i++) { + if (my.instances[i].null() || (active && !my.instances[i].active())) + continue; + + if (my.instances[i].refCount() == 0) + my.profile = true; + if (my.instances[i].active()) + my.numActiveRef++; + + my.instances[i].refCountInc(); + } + my.count = my.instances.size() - my.nullCount; +} + +void +QmcIndom::removeRef(uint index) +{ + Q_ASSERT(my.instances[index].refCount()); + + my.instances[index].refCountDec(); + if (my.instances[index].refCount() == 0) { + my.profile = true; + my.count--; + if (my.instances[index].active()) + my.numActiveRef--; + } +} + +int +QmcIndom::genProfile() +{ + int i, j; + int sts = 0; + int *ptr = NULL; + QVector<int> list; + const char *action = NULL; + + // If all instances are referenced or there are no instances + // then request all instances + if (my.numActiveRef == my.numActive || my.numActive == 0) { + sts = pmAddProfile(my.id, 0, NULL); + action = "ALL"; + } + // If the number of referenced instances is less than the number + // of unreferenced active instances, then the smallest profile + // is to add all the referenced instances + else if (my.count < (my.numActive - my.numActiveRef)) { + action = "ADD"; + sts = pmDelProfile(my.id, 0, NULL); + if (sts >= 0) { + list.resize(my.count); + for (i = 0, j = 0; i < my.instances.size(); i++) + if (!my.instances[i].null() && my.instances[i].refCount()) + list[j++] = my.instances[i].inst(); + ptr = list.data(); + sts = pmAddProfile(my.id, list.size(), ptr); + } + } + // Delete those active instances that are not referenced + else { + action = "DELETE"; + sts = pmAddProfile(my.id, 0, NULL); + if (sts >= 0) { + list.resize(my.instances.size() - my.count); + for (i = 0, j = 0; i < my.instances.size(); i++) + if (!my.instances[i].null() && + my.instances[i].refCount() == 0 && + my.instances[i].active()) + list[j++] = my.instances[i].inst(); + ptr = list.data(); + sts = pmDelProfile(my.id, list.size(), ptr); + } + } + + if (pmDebug & (DBG_TRACE_PMC | DBG_TRACE_INDOM | DBG_TRACE_PROFILE)) { + QTextStream cerr(stderr); + cerr << "QmcIndom::genProfile: id = " << my.id << ", count = " + << my.count << ", numInsts = " << numInsts() << ", active = " + << my.numActive << ", activeRef = " << my.numActiveRef + << ": " << action << " ptr = " << ptr; + if (sts < 0) + cerr << ", sts = " << sts << ": " << pmErrStr(sts); + cerr << endl; + } + + if (sts >= 0) + my.profile = false; + return sts; +} + +void +QmcIndom::dump(QTextStream &os) const +{ + os << pmInDomStr(my.id) << ": " << numInsts() << " instances (" + << my.nullCount << " NULL)" << endl; + for (int i = 0; i < my.instances.size(); i++) + if (!my.instances[i].null()) + os << " [" << my.instances[i].inst() << "] = \"" + << my.instances[i].name() << "\" (" + << my.instances[i].refCount() << " refs) " + << (my.instances[i].active() ? "active" : "inactive") << endl; + else + os << " NULL -> " << my.instances[i].index() << endl; +} + +int +QmcIndom::update() +{ + int *instList; + char **nameList; + int i, j, count; + int oldLen = my.instances.size(); + uint oldNullCount = my.nullCount; + int sts = 0; + + // If the indom has already been updated, just check that all instances + // are referenced and remove any that have gone away. + if (!my.changed || my.updated) { + for (i = 0; i < oldLen; i++) { + QmcInstance &inst = my.instances[i]; + if (inst.refCount() || inst.null() || inst.active()) + continue; + inst.deactivate(my.nullIndex); + my.nullIndex = i; + my.nullCount++; + my.profile = true; + } + if (pmDebug & DBG_TRACE_INDOM && my.nullCount != oldNullCount) { + QTextStream cerr(stderr); + cerr << "QmcIndom::update: Cleaning indom " << pmInDomStr(my.id) + << ": Removed " << my.nullCount - oldNullCount + << " instances" << endl; + } + return 0; + } + + my.updated = true; + + if (my.type == PM_CONTEXT_ARCHIVE) + return 0; + + if (my.type == PM_CONTEXT_HOST || my.type == PM_CONTEXT_LOCAL) + sts = pmGetInDom(my.id, &instList, &nameList); + + my.numActive = 0; + my.numActiveRef = 0; + + if (sts > 0) { + count = sts; + if (pmDebug & DBG_TRACE_PMC) { + QTextStream cerr(stderr); + cerr << "QmcIndom::update: Updating indom " << pmInDomStr(my.id) + << ": Got " << count << " instances (vs " << numInsts() + << ")" << endl; + } + + // Any instances which are not in the new indom AND are not + // referenced can be removed + for (i = 0; i < oldLen; i++) { + QmcInstance &inst = my.instances[i]; + inst.setActive(false); + + if (inst.refCount() || inst.null()) + continue; + j = 0; + if (i < count && inst.inst() == instList[i]) { + if (inst.name().compare(nameList[i]) == 0) + continue; + else + j = count; + } + for (; j < count; j++) { + if (inst.inst() == instList[j]) { + if (inst.name().compare(nameList[j]) == 0) + break; + else + j = count; + } + } + + // If j >= count, then instance i has either changed or gone away + if (j >= count) { + inst.deactivate(my.nullIndex); + my.nullIndex = i; + my.nullCount++; + my.profile = true; + } + } + + for (i = 0; i < count; i++) { + // Quick check to see if they are the same + if (i < my.instances.size() && + my.instances[i].inst() == instList[i] && + my.instances[i].name().compare(nameList[i]) == 0) { + if (pmDebug & DBG_TRACE_INDOM) { + QTextStream cerr(stderr); + cerr << "QmcIndom::update: Unchanged \"" << nameList[i] + << "\"(" << instList[i] << ')' << endl; + } + my.instances[i].setActive(true); + my.numActive++; + if (my.instances[i].refCount()) + my.numActiveRef++; + continue; + } + + for (j = 0; j < oldLen; j++) { + if (my.instances[j].null()) + continue; + + if (my.instances[j].inst() == instList[i]) { + // Same instance and same external name but different + // order, mark as active. If it has a different + // external name just ignore it + if (my.instances[j].name().compare(nameList[i]) == 0) { + my.instances[j].setActive(true); + my.numActive++; + if (my.instances[j].refCount()) + my.numActiveRef++; + } + else if (pmDebug & DBG_TRACE_PMC) { + QTextStream cerr(stderr); + cerr << "QmcIndom::update: Ignoring \"" + << nameList[i] + << "\" with identical internal identifier (" + << instList[i] << ")" << endl; + } + break; + } + } + + if (j == oldLen) { + if (pmDebug & DBG_TRACE_INDOM) { + QTextStream cerr(stderr); + cerr << "QmcIndom::update: Adding \"" << nameList[i] + << "\"(" << instList[i] << ")" << endl; + } + if (my.nullCount) { + uint newindex = my.instances[my.nullIndex].index(); + my.instances[my.nullIndex] = QmcInstance(instList[i], + nameList[i]); + my.nullIndex = newindex; + my.nullCount--; + } + else + my.instances.append(QmcInstance(instList[i], nameList[i])); + my.profile = true; + my.numActive++; + } + } + + free(instList); + free(nameList); + + if (pmDebug & DBG_TRACE_INDOM) { + QTextStream cerr(stderr); + if (my.instances.size() == oldLen && my.nullCount == oldNullCount) + cerr << "QmcIndom::update: indom size unchanged" << endl; + else { + cerr << "QmcIndom::update: indom changed from " + << oldLen - oldNullCount << " to " << numInsts() << endl; + dump(cerr); + } + } + } + else { + for (i = 0; i < my.instances.size(); i++) + my.instances[i].setActive(false); + + if (pmDebug & DBG_TRACE_PMC) { + QTextStream cerr(stderr); + if (sts == 0) + cerr << "QmcIndom::update: indom empty!" << endl; + else + cerr << "QmcIndom::update: unable to lookup " + << pmInDomStr(my.id) << " from host/local source: " + << pmErrStr(sts) << endl; + } + } + + return sts; +} diff --git a/src/libpcp_qmc/src/qmc_indom.h b/src/libpcp_qmc/src/qmc_indom.h new file mode 100644 index 0000000..265f3ad --- /dev/null +++ b/src/libpcp_qmc/src/qmc_indom.h @@ -0,0 +1,137 @@ +/* + * Copyright (c) 1997-2005 Silicon Graphics, Inc. All Rights Reserved. + * Copyright (c) 2007 Aconex. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library 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 Lesser General Public + * License for more details. + */ +#ifndef QMC_INDOM_H +#define QMC_INDOM_H + +#include "qmc.h" + +#include <qlist.h> +#include <qstring.h> +#include <qtextstream.h> + +class QmcInstance +{ +public: + QmcInstance(); + QmcInstance(int id, const char* name); + QmcInstance const& operator=(QmcInstance const&); + + void deactivate(int index); + int inst() const { return my.inst; } + bool null() const { return (my.inst == (int)PM_IN_NULL); } + QString name() const { return my.name; } + int refCount() const { return my.refCount; } + int refCountInc() { return ++my.refCount; } + int refCountDec() { return --my.refCount; } + bool active() const { return my.active; } + void setActive(bool active) { my.active = active; } + int index() const { return my.index; } + void setIndex(int index) { my.index = index; } + +private: + struct { + int inst; // Instance internal id + QString name; // Instance external id + int refCount; + int index; // Index into pmResult of last fetch + // May also be used to index the next NULL inst + bool active; // Instance was listed in last indom lookup + } my; +}; + +class QmcIndom +{ +public: + QmcIndom(int type, QmcDesc &desc); + + int status() const { return my.status; } + int id() const { return my.id; } + + int numInsts() const { return my.instances.size() - my.nullCount; } + int numActiveInsts() const { return my.numActive; } + + // Length of instance list - some of the instances may be NULL hence + // this may be larger than numInsts() + int listLen() const { return my.instances.size(); } + + // Internal instance id for instance <index> + int inst(uint index) const { return my.instances[index].inst(); } + + // External instance name for instance <index> + const QString name(uint index) const { return my.instances[index].name(); } + + bool nullInst(uint index) const { return my.instances[index].null(); } + + // Was this instance listed in the last update? + bool activeInst(uint index) const { return my.instances[index].active(); } + + // Is this instance referenced by any metrics? + bool refInst(uint index) const { return my.instances[index].refCount()>0; } + + // Return index into table for instance with external <name> + // Also adds a reference to this instance for the profile + int lookup(QString const& name); + + // Add a reference to all instances in this indom + // Id <active> is set, only reference active instances + void refAll(bool active = false); + + // Remove a reference to an instance + void removeRef(uint index); + + // Number of instances referenced + uint refCount() const { return my.count; } + + // Was the indom different on the last fetch? + bool changed() const { return my.changed; } + + // About to fetch, so mark this indom as unchanged + void newFetch() { my.changed = false; my.updated = true; } + + // Mark that the indom was different in a previous fetch + void hasChanged() { my.changed = true; my.updated = false; } + + int update(); // Update indom with latest instances + + // Has profile changed since last call to genProfile + bool diffProfile() const { return my.profile; } + + int genProfile(); // Generate profile for current context + + // Likely index into pmResult for instance <inst> + int index(uint inst) const { return my.instances[inst].index(); } + void setIndex(uint inst, int index) { my.instances[inst].setIndex(index); } + + // Dump some debugging output for this indom + void dump(QTextStream &os) const; + +private: + struct { + int status; + int type; + pmInDom id; + QList<QmcInstance> instances; // Sparse list of instances + bool profile; // Does the profile need to be updated + bool changed; // Did indom change in the last fetch? + bool updated; // Has the indom been updated? + uint count; // Number of referenced instances + uint nullCount; // Count of NULL instances + uint nullIndex; // Index to first NULL instance + uint numActive; // Number of active instances + uint numActiveRef; // Number of active referenced insts + } my; +}; + +#endif // QMC_INDOM_H diff --git a/src/libpcp_qmc/src/qmc_metric.cpp b/src/libpcp_qmc/src/qmc_metric.cpp new file mode 100644 index 0000000..85d5016 --- /dev/null +++ b/src/libpcp_qmc/src/qmc_metric.cpp @@ -0,0 +1,1248 @@ +/* + * Copyright (c) 1997,2005 Silicon Graphics, Inc. All Rights Reserved. + * Copyright (c) 2007 Aconex. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + */ + +#include <strings.h> +#include "qmc_metric.h" +#include "qmc_group.h" + +QmcMetricValue::QmcMetricValue() +{ + my.value = 0.0; + my.currentValue = 0.0; + my.previousValue = 0.0; + my.error = PM_ERR_VALUE; + my.currentError = PM_ERR_VALUE; + my.previousError = PM_ERR_VALUE; + my.instance = PM_ERR_INST; +} + +QmcMetricValue const& +QmcMetricValue::operator=(QmcMetricValue const& rhs) +{ + if (this != &rhs) { + my.instance = rhs.my.instance; + my.value = rhs.my.value; + my.currentValue = rhs.my.currentValue; + my.previousValue = rhs.my.previousValue; + my.stringValue = rhs.my.stringValue; + my.error = rhs.my.error; + my.currentError = rhs.my.currentError; + my.previousError = rhs.my.previousError; + } + return *this; +} + +QmcMetric::QmcMetric(QmcGroup *group, const char *string, + double scale, bool active) +{ + pmMetricSpec *metricSpec; + char *msg; + + my.status = 0; + my.group = group; + my.scale = scale; + my.idIndex = UINT_MAX; + my.indomIndex = UINT_MAX; + my.contextIndex = UINT_MAX; + my.explicitInst = false; + my.active = active; + + my.status = pmParseMetricSpec(string, 0, NULL, &metricSpec, &msg); + if (my.status < 0) { + pmprintf("%s: Error: Unable to parse metric spec:\n%s\n", + pmProgname, msg); + my.name = QString::null; + free(msg); + } + else { + my.name = QString(metricSpec->metric); + setup(group, metricSpec); + free(metricSpec); + } +} + +QmcMetric::QmcMetric(QmcGroup *group, pmMetricSpec *metricSpec, + double scale, bool active) +{ + my.pmid = PM_ID_NULL; + my.status = 0; + my.name = QString(metricSpec->metric); + my.group = group; + my.scale = scale; + my.contextIndex = UINT_MAX; + my.idIndex = 0; + my.indomIndex = UINT_MAX; + my.explicitInst = false; + my.active = active; + setup(group, metricSpec); +} + +void +QmcMetric::setup(QmcGroup* group, pmMetricSpec *metricSpec) +{ + if (my.status >= 0) + setupDesc(group, metricSpec); + if (my.status >= 0) + setupIndom(metricSpec); + if (my.status < 0) + return; + if (pmDebug & DBG_TRACE_PMC) + dumpAll(); +} + +QmcMetric::~QmcMetric() +{ + if (hasInstances()) + for (int i = 0; i < my.values.size(); i++) + indom()->removeRef(my.values[i].instance()); +} + +void +QmcMetric::setupDesc(QmcGroup* group, pmMetricSpec *metricSpec) +{ + int contextType = PM_CONTEXT_HOST; + int descType; + char *src = NULL; + char *name = NULL; + + if (metricSpec->isarch == 1) + contextType = PM_CONTEXT_ARCHIVE; + else if (metricSpec->isarch == 2) + contextType = PM_CONTEXT_LOCAL; + + QString source = QString(metricSpec->source); + my.status = group->use(contextType, source); + + if (my.status >= 0) { + my.contextIndex = group->contextIndex(); + contextType = context()->source().type(); + my.status = context()->lookupPMID(metricSpec->metric, my.pmid); + if (my.status >= 0) + my.status = context()->lookupInDom(my.pmid, my.indomIndex); + if (my.status < 0) { + name = strdup(nameAscii()); + src = strdup(context()->source().sourceAscii()); + pmprintf("%s: Error: %s%c%s: %s\n", + pmProgname, + contextType == PM_CONTEXT_LOCAL ? "@" : src, + contextType == PM_CONTEXT_ARCHIVE ? '/' : ':', + name, pmErrStr(my.status)); + } + } + else { + // do nothing, error already reported via pmprintf from + // QmcGroup::use() + ; + } + + if (my.status >= 0) { + descType = desc().desc().type; + if (descType == PM_TYPE_NOSUPPORT) { + my.status = PM_ERR_CONV; + name = strdup(nameAscii()); + src = strdup(context()->source().sourceAscii()); + pmprintf("%s: Error: %s%c%s is not supported on %s\n", + pmProgname, contextType == PM_CONTEXT_LOCAL ? "@" : src, + (contextType == PM_CONTEXT_ARCHIVE ? '/' : ':'), + name, context()->source().hostAscii()); + } + else if (descType == PM_TYPE_AGGREGATE || + descType == PM_TYPE_AGGREGATE_STATIC || + descType == PM_TYPE_UNKNOWN) { + my.status = PM_ERR_CONV; + name = strdup(nameAscii()); + src = strdup(context()->source().sourceAscii()); + pmprintf("%s: Error: %s%c%s has type \"%s\"," + " which is not a number or a string\n", + pmProgname, contextType == PM_CONTEXT_LOCAL ? "@" : src, + (contextType == PM_CONTEXT_ARCHIVE ? '/' : ':'), + name, pmTypeStr(descType)); + } + } + + if (name) + free(name); + if (src) + free(src); +} + +void +QmcMetric::setupIndom(pmMetricSpec *metricSpec) +{ + int i, j; + QmcIndom *indomPtr = indom(); + + if (!hasIndom()) { + if (metricSpec->ninst > 0) { + my.status = PM_ERR_INST; + dumpErr(metricSpec->inst[0]); + } + else + setupValues(1); + } + else if (metricSpec->ninst) { + Q_ASSERT(hasInstances()); + setupValues(metricSpec->ninst); + + for (i = 0 ; i < metricSpec->ninst && my.status >= 0; i++) { + j = indomPtr->lookup(metricSpec->inst[i]); + if (j >= 0) + my.values[i].setInstance(j); + else { + my.status = PM_ERR_INST; + my.values[i].setInstance(PM_ERR_INST); + dumpErr(metricSpec->inst[i]); + } + } + my.explicitInst = true; + } + else { + Q_ASSERT(hasInstances()); + + if (my.active) { + setupValues(indomPtr->numActiveInsts()); + indomPtr->refAll(my.active); + + for (i = 0, j = 0; i < indomPtr->listLen(); i++) + if (!indomPtr->nullInst(i) && indomPtr->activeInst(i)) + my.values[j++].setInstance(i); + } + else { + setupValues(indomPtr->numInsts()); + indomPtr->refAll(my.active); + + for (i = 0, j = 0; i < indomPtr->listLen(); i++) + if (!indomPtr->nullInst(i)) + my.values[j++].setInstance(i); + } + } +} + +void +QmcMetric::setupValues(int num) +{ + int i, oldLen = my.values.size(); + + if (num == 0) + my.values.clear(); + else { + if (my.values.size() > num) + for (i = num; i < my.values.size(); i++) + my.values.removeAt(i); + for (i = oldLen; i < num; i++) + my.values.append(QmcMetricValue()); + } +} + +QString +QmcMetric::spec(bool srcFlag, bool instFlag, uint instance) const +{ + QString str; + int i, len = 4; + + if (srcFlag) + len += context()->source().source().size(); + len += name().size(); + if (hasInstances() && instFlag) { + if (instance != UINT_MAX) + len += instName(instance).size() + 2; + else + for (i = 0; i < numInst(); i++) + len += instName(i).size() + 4; + } + + if (srcFlag) { + str.append(context()->source().source()); + if (context()->source().type() == PM_CONTEXT_ARCHIVE) + str.append(QChar('/')); + else + str.append(QChar(':')); + } + str.append(name()); + if (hasInstances() && instFlag) { + str.append(QChar('[')); + str.append(QChar('\"')); + if (instance != UINT_MAX) + str.append(instName(instance)); + else if (numInst()) { + str.append(instName(0)); + for (i = 1; i < numInst(); i++) { + str.append("\", \""); + str.append(instName(i)); + } + } + str.append("\"]"); + } + + return str; +} + +void +QmcMetric::dumpSource(QTextStream &os) const +{ + switch(context()->source().type()) { + case PM_CONTEXT_LOCAL: + os << "@:"; + break; + case PM_CONTEXT_HOST: + os << context()->source().source() << ':'; + break; + case PM_CONTEXT_ARCHIVE: + os << context()->source().source() << '/'; + break; + } +} + +void +QmcMetric::dumpValue(QTextStream &stream, uint inst) const +{ + if (error(inst) < 0) + stream << pmErrStr(error(inst)); + else if (!real()) + stream << stringValue(inst); + else if (!event()) + stream << value(inst) << " " << desc().units(); +} + +void +QmcMetric::dump(QTextStream &stream, bool srcFlag, uint instance) const +{ + if (event()) + dumpEventMetric(stream, srcFlag, instance); + else + dumpSampledMetric(stream, srcFlag, instance); +} + +void +QmcMetric::dumpSampledMetric(QTextStream &stream, bool srcFlag, uint instance) const +{ + Q_ASSERT(!event()); + + stream << name(); + + if (srcFlag == true) + dumpSource(stream); + + if (my.status < 0) + stream << ": " << pmErrStr(my.status) << endl; + else if (hasInstances()) { + if (instance == UINT_MAX) { + if (numInst() == 1) + stream << ": 1 instance"; + else + stream << ": " << numInst() << " instances"; + if (indom()->changed()) + stream << " (indom has changed)"; + stream << endl; + + for (int i = 0; i < numInst(); i++) { + stream << " [" << instID(i) << " or \"" << instName(i) + << "\" (" << my.values[i].instance() << ")] = "; + dumpValue(stream, i); + stream << endl; + } + } + else { + stream << '[' << instID(instance) << " or \"" << instName(instance) + << "\" (" << my.values[instance].instance() << ")] = "; + dumpValue(stream, instance); + stream << endl; + } + } + else { + stream << " = "; + dumpValue(stream, 0); + stream << endl; + } +} + +QTextStream& +operator<<(QTextStream &stream, const QmcMetric &metric) +{ + metric.dumpSource(stream); + stream << metric.name(); + if (metric.numInst()) { + stream << "[\"" << metric.instName(0); + for (int i = 1; i < metric.numValues(); i++) + stream << "\", \"" << metric.instName(i); + stream << "\"]"; + } + return stream; +} + +void +QmcMetric::setScaleUnits(pmUnits const& units) +{ + QmcContext *context = my.group->context(my.contextIndex); + QmcDesc &desc = context->desc(my.pmid); + desc.setScaleUnits(units); +} + +int +QmcMetric::update() +{ + uint i, err = 0; + uint num = numValues(); + int sts; + pmAtomValue ival, oval; + double delta = context()->timeDelta(); + static int wrap = -1; + + if (num == 0 || my.status < 0) + return my.status; + + // PCP_COUNTER_WRAP in environment enables "counter wrap" logic + if (wrap == -1) + wrap = (getenv("PCP_COUNTER_WRAP") != NULL); + + for (i = 0; i < num; i++) { + my.values[i].setError(my.values[i].currentError()); + if (my.values[i].error() < 0) + err++; + if (pmDebug & DBG_TRACE_VALUE) { + QTextStream cerr(stderr); + if (my.values[i].error() < 0) + cerr << "QmcMetric::update: " << spec(true, true, i) + << ": " << pmErrStr(my.values[i].error()) << endl; + } + } + + if (!real()) + return err; + + if (desc().desc().sem == PM_SEM_COUNTER) { + for (i = 0; i < num; i++) { + QmcMetricValue& value = my.values[i]; + + if (value.error() < 0) { // we already know we + value.setValue(0.0); // don't have this value + continue; + } + if (value.previousError() < 0) { // we need two values + value.setValue(0.0); // for a rate + value.setError(value.previousError()); + err++; + if (pmDebug & DBG_TRACE_VALUE) { + QTextStream cerr(stderr); + cerr << "QmcMetric::update: Previous: " + << spec(true, true, i) << ": " + << pmErrStr(value.error()) << endl; + } + continue; + } + + value.setValue(value.currentValue() - value.previousValue()); + + // wrapped going forwards + if (value.value() < 0 && delta > 0) { + if (wrap) { + switch(desc().desc().type) { + case PM_TYPE_32: + case PM_TYPE_U32: + value.addValue((double)UINT_MAX+1); + break; + case PM_TYPE_64: + case PM_TYPE_U64: + value.addValue((double)ULONGLONG_MAX+1); + break; + } + } + else { // counter not monotonic + value.setValue(0.0); // increasing + value.setError(PM_ERR_VALUE); + err++; + continue; + } + } + // wrapped going backwards + else if (value.value() > 0 && delta < 0) { + if (wrap) { + switch(desc().desc().type) { + case PM_TYPE_32: + case PM_TYPE_U32: + value.subValue((double)UINT_MAX+1); + break; + case PM_TYPE_64: + case PM_TYPE_U64: + value.subValue((double)ULONGLONG_MAX+1); + break; + } + } + else { // counter not monotonic + value.setValue(0.0); // increasing + value.setError(PM_ERR_VALUE); + err++; + continue; + } + } + + if (delta != 0) // sign of delta and v + value.divValue(delta); // should be the same + else + value.setValue(0.0); // nothing can have happened + } + } + else { + for (i = 0; i < num; i++) { + QmcMetricValue& value = my.values[i]; + if (value.error() < 0) + value.setValue(0.0); + else + value.setValue(value.currentValue()); + } + } + + if (my.scale != 0.0) { + for (i = 0; i < num; i++) { + if (my.values[i].error() >= 0) + my.values[i].divValue(my.scale); + } + } + + if (desc().useScaleUnits()) { + for (i = 0; i < num; i++) { + if (my.values[i].error() < 0) + continue; + ival.d = my.values[i].value(); + pmUnits units = desc().desc().units; + sts = pmConvScale(PM_TYPE_DOUBLE, &ival, &units, + &oval, (pmUnits *)&(desc().scaleUnits())); + if (sts < 0) + my.values[i].setError(sts); + else { + my.values[i].setValue(oval.d); + if (pmDebug & DBG_TRACE_VALUE) { + QTextStream cerr(stderr); + cerr << "QmcMetric::update: scaled " << my.name + << " from " << ival.d << " to " << oval.d + << endl; + } + } + } + } + + return err; +} + +void +QmcMetric::dumpAll() const +{ + QTextStream cerr(stderr); + cerr << *this << " from " << context()->source().desc() + << " with scale = " << my.scale << " and units = " << desc().units() + << endl; +} + +void +QmcMetric::dumpErr() const +{ + pmprintf("%s: Error: %s: %s\n", pmProgname, + (const char *)spec(true).toAscii(), pmErrStr(my.status)); +} + +// Instance list may not be valid, so pass inst as a string rather than +// as an index + +void +QmcMetric::dumpErr(const char *inst) const +{ + pmprintf("%s: Error: %s[%s]: %s\n", pmProgname, + (const char *)spec(true).toAscii(), inst, pmErrStr(my.status)); +} + +const char * +QmcMetric::formatNumber(double value) +{ + static char buf[8]; + + if (value >= 0.0) { + if (value > 99950000000000.0) + strcpy(buf, " inf?"); + else if (value > 99950000000.0) + sprintf(buf, "%5.2fT", value / 1000000000000.0); + else if (value > 99950000.0) + sprintf(buf, "%5.2fG", value / 1000000000.0); + else if (value > 99950.0) + sprintf(buf, "%5.2fM", value / 1000000.0); + else if (value > 99.95) + sprintf(buf, "%5.2fK", value / 1000.0); + else if (value > 0.005) + sprintf(buf, "%5.2f ", value); + else + strcpy(buf, " 0.00 "); + } + else { + if (value < -9995000000000.0) + strcpy(buf, " -inf?"); + else if (value < -9995000000.0) + sprintf(buf, "%.2fT", value / 1000000000000.0); + else if (value < -9995000.0) + sprintf(buf, "%.2fG", value / 1000000000.0); + else if (value < -9995.0) + sprintf(buf, "%.2fM", value / 1000000.0); + else if (value < -9.995) + sprintf(buf, "%.2fK", value / 1000.0); + else if (value < -0.005) + sprintf(buf, "%.2f ", value); + else + strcpy(buf, " 0.00 "); + } + + return buf; +} + +void +QmcMetric::shiftValues() +{ + for (int i = 0; i < my.values.size(); i++) + my.values[i].shiftValues(); +} + +void +QmcMetric::setError(int sts) +{ + for (int i = 0; i < numValues(); i++) { + QmcMetricValue &value = my.values[i]; + value.setCurrentError(sts); + } +} + +void +QmcMetric::extractValues(pmValueSet const* set) +{ + int i, j, index, inst; + pmValue const *value = NULL; + bool found; + QmcIndom *indomPtr = indom(); + + Q_ASSERT(set->pmid == desc().id()); + + if (set->numval > 0) { + if (hasIndom()) { + // If the number of instances are not the expected number + // then mark the indom as changed + if (!my.explicitInst && (my.values.size() != set->numval)) { + if (pmDebug & DBG_TRACE_INDOM) { + QTextStream cerr(stderr); + cerr << "QmcMetric::extractValues: implicit indom " + << pmInDomStr(indomPtr->id()) << " changed (" + << set->numval << " != " << my.values.size() << ')' + << endl; + } + indomPtr->hasChanged(); + updateIndom(); + } + + for (i = 0; i < numInst(); i++) { + QmcMetricValue &valueRef = my.values[i]; + inst = my.values[i].instance(); + index = indomPtr->index(inst); + found = false; + + // If the index is within range, try it first + if (index >= 0 && index < set->numval) { + value = &(set->vlist[index]); + + // Found it in the same spot as last time + if (value->inst == indomPtr->inst(inst)) + found = true; + } + + // Search for it from the top + for (j = 0; found == false && j < set->numval; j++) { + if (set->vlist[j].inst == indomPtr->inst(inst)) { + index = j; + value = &(set->vlist[j]); + indomPtr->setIndex(inst, j); + found = true; + } + } + + if (found) { + if (real()) + extractNumericMetric(set, value, valueRef); + else if (!event()) + extractArrayMetric(set, value, valueRef); + else + extractEventMetric(set, index, valueRef); + } + else { // Cannot find it + if (pmDebug & DBG_TRACE_OPTFETCH) { + QTextStream cerr(stderr); + cerr << "QmcMetric::extractValues: " + << spec(true, true, i) << ": " + << pmErrStr(PM_ERR_VALUE) << endl; + } + + if (valueRef.previousError() != PM_ERR_VALUE) + indomPtr->hasChanged(); + + valueRef.setCurrentError(PM_ERR_VALUE); + } + } + } + else if (set->numval == 1) { + // We have no instances at this point in time + if (my.values.size() == 0 && hasInstances()) + indomPtr->hasChanged(); + else { + QmcMetricValue &valueRef = my.values[0]; + value = &(set->vlist[0]); + + if (real()) + extractNumericMetric(set, value, valueRef); + else if (!event()) + extractArrayMetric(set, value, valueRef); + else + extractEventMetric(set, 0, valueRef); + } + } + else { // Did not expect any instances + if (pmDebug & DBG_TRACE_OPTFETCH) { + QTextStream cerr(stderr); + cerr << "QmcMetric::extractValues: " << spec(true) + << " is a singular metric but result contained " + << set->numval << " values" << endl; + } + setError(PM_ERR_VALUE); + } + } + else if (set->numval == 0) { + if (!(hasInstances() && numInst() == 0)) { + if (pmDebug & DBG_TRACE_OPTFETCH) { + QTextStream cerr(stderr); + cerr << "QmcMetric::extractValues: numval == 0: " + << spec(true, false) << ": " << pmErrStr(PM_ERR_VALUE) + << endl; + } + setError(PM_ERR_VALUE); + if (hasInstances()) + indomPtr->hasChanged(); + } + } + else { + if (pmDebug & DBG_TRACE_OPTFETCH) { + QTextStream cerr(stderr); + cerr << "QmcMetric::extractValues: numval < 0: " + << spec(true, false) + << ": " << pmErrStr(set->numval) << endl; + } + setError(set->numval); + if (hasInstances()) + indomPtr->hasChanged(); + } +} + +/* + * Display-able aggregate (for diagnostics) - up to + * first 16 characters displayed. Uses a similar + * approach to that taken in pmPrintValue(). + */ +void +QmcMetric::aggregateAsString(pmValue const *vp, char *buffer, int buflen) +{ + char *p = &vp->value.pval->vbuf[0]; + + memset(buffer, '.', buflen); + for (int i = 0; i < (buflen/2)-1; i++, p++) { + if (i < vp->value.pval->vlen - PM_VAL_HDR_SIZE) + sprintf(buffer + (i*2), "%02x", *p & 0xff); + } + buffer[buflen-1] = '\0'; +} + +void +QmcMetric::extractArrayMetric(pmValueSet const *set, pmValue const *vp, QmcMetricValue &valueRef) +{ + pmAtomValue result; + int sts, type = desc().desc().type; + + if (aggregate(type)) { + char buffer[32]; + aggregateAsString(vp, buffer, sizeof(buffer)); + valueRef.setStringValue(buffer); + } + else if ((sts = pmExtractValue(set->valfmt, vp, + type, &result, PM_TYPE_STRING)) >= 0) { + valueRef.setStringValue(result.cp); + if (result.cp) + free(result.cp); + } + else { + valueRef.setCurrentError(sts); + } +} + +void +QmcMetric::extractNumericMetric(pmValueSet const *set, pmValue const *value, QmcMetricValue &valueRef) +{ + pmAtomValue result; + int sts; + + if ((sts = pmExtractValue(set->valfmt, value, + desc().desc().type, &result, PM_TYPE_DOUBLE)) >= 0) + valueRef.setCurrentValue(result.d); + else + valueRef.setCurrentError(sts); +} + +void +QmcMetricValue::extractEventRecords(QmcContext *context, int recordCount, pmResult **result) +{ + pmID parameterID; + pmID missedID = QmcEventRecord::eventMissed(); + pmID flagsID = QmcEventRecord::eventFlags(); + int i, p, r, parameterCount; + + my.eventRecords.resize(recordCount); + + for (r = 0; r < recordCount; r++) { + QmcEventRecord &record = my.eventRecords[r]; + + // count is the size of my.parameter vector (less flags/missed) + parameterCount = 0; + for (i = 0; i < result[r]->numpmid; i++) { + parameterID = result[r]->vset[i]->pmid; + if (parameterID != flagsID && parameterID != missedID) + parameterCount++; + } + + // initialise this record + record.setTimestamp(&result[r]->timestamp); + record.setParameterCount(parameterCount); + record.setMissed(0); + record.setFlags(0); + + // i indexes into result[r], p indexes into my.parameter vector + for (i = p = 0; i < result[r]->numpmid; i++) { + pmValueSet *valueSet = result[r]->vset[i]; + parameterID = valueSet->pmid; + if (parameterID == flagsID) + record.setFlags(valueSet->vlist[0].value.lval); + else if (parameterID == missedID) + record.setMissed(valueSet->vlist[0].value.lval); + else + record.setParameter(p++, parameterID, context, valueSet); + } + + if (pmDebug & DBG_TRACE_VALUE) { + QTextStream cerr(stderr); + pmValueSet *valueSet = result[r]->vset[0]; + record.dump(cerr, valueSet->vlist[0].inst, r); + } + } +} + +void +QmcMetric::extractEventMetric(pmValueSet const *valueSet, int index, QmcMetricValue &valueRef) +{ + pmValueSet *values = (pmValueSet *)valueSet; + pmResult **result; + int sts; + + if ((sts = pmUnpackEventRecords(values, index, &result)) >= 0) { + valueRef.extractEventRecords(context(), sts, result); + pmFreeEventResult(result); + } + else { + valueRef.setCurrentError(sts); + } +} + +int +QmcEventRecord::setParameter(int index, pmID pmid, QmcContext *context, pmValueSet const *vsp) +{ + QString *name; + QmcDesc *desc; + QmcIndom *indom; + int sts, type; + + if (vsp->numval <= 0) // no value or an error + return vsp->numval; + + if ((sts = context->lookup(pmid, &name, &desc, &indom)) < 0) + return sts; + + QmcEventParameter ¶meter = my.parameters[index]; + parameter.setPMID(pmid); + parameter.setNamePtr(name); + parameter.setDescPtr(desc); + parameter.setIndomPtr(indom); + parameter.setValueCount(vsp->numval); + + type = desc->desc().type; + for (int i = 0; i < vsp->numval; i++) { + pmAtomValue result; + const pmValue *vp = &vsp->vlist[i]; + QmcMetricValue *value = parameter.valuePtr(i); + + sts = PM_ERR_TYPE; // no nesting events + if (QmcMetric::real(type) == true) { + if ((sts = pmExtractValue(vsp->valfmt, vp, + type, &result, PM_TYPE_DOUBLE)) >= 0) + value->setCurrentValue(result.d); + } else if (QmcMetric::aggregate(type) == true) { + char buffer[32]; + QmcMetric::aggregateAsString(vp, buffer, sizeof(buffer)); + value->setStringValue(buffer); + } else if (QmcMetric::event(type) == false) { + if ((sts = pmExtractValue(vsp->valfmt, vp, + type, &result, PM_TYPE_STRING)) >= 0) { + value->setStringValue(result.cp); + free(result.cp); + } + } + value->setInstance(vp->inst); + if (sts < 0) + value->setCurrentError(sts); + } + return 0; +} + +QString +QmcEventRecord::parameterAsString(int index) const +{ + QString identifier; + + if (index >= my.parameters.size()) + return QString::null; + + int type = my.parameters[index].type(); + if (QmcMetric::real(type)) + return QString::number(my.parameters.at(index).value(0)); + if (QmcMetric::event(type)) + return QString::null; + return my.parameters.at(index).stringValue(0); +} + +QString +QmcEventRecord::identifier() const +{ + // + // If the ID flag is set, the identifier is always the + // first parameter. + // + if (my.flags & PM_EVENT_FLAG_ID) + return parameterAsString(0); + return QString::null; +} + +QString +QmcEventRecord::parent() const +{ + // + // If PARENT flag is set, then parent identifier is either + // the first parameter (if no ID, bit wierd) or the second + // (thats expected typical usage anyway). + // + if (my.flags & PM_EVENT_FLAG_PARENT) + return parameterAsString((my.flags & PM_EVENT_FLAG_ID) != 0); + return QString::null; +} + +void +QmcEventRecord::parameterSummary(QString &os, int instID) const +{ + for (int i = 0; i < my.parameters.size(); i++) + my.parameters.at(i).summary(os, instID); +} + +void +QmcEventParameter::summary(QString &os, int instID) const +{ + pmDesc desc = my.desc->desc(); + + for (int i = 0; i < my.values.size(); i++) { + QmcMetricValue const &value = my.values.at(i); + + os.append(" ").append(*my.name); + if (desc.indom != PM_INDOM_NULL) { + if (my.values.size() > 1) + os.append("\n").append(" "); + QString name = my.indom->name(instID); + if (name == QString::null) + os.append("[").append(instID).append("]"); + else + os.append("[\"").append(name).append("\"]"); + } + os.append(" "); + + if (QmcMetric::real(desc.type)) + os.append(QString::number(value.currentValue())); + else if (QmcMetric::aggregate(desc.type)) + os.append("[").append(value.stringValue()).append("]"); + else if (QmcMetric::event(desc.type) == false) + os.append("\"").append(value.stringValue()).append("\""); + os.append("\n"); + } +} + +void +QmcEventParameter::setValueCount(int numInst) +{ + my.values.resize(numInst); +} + +QmcMetricValue * +QmcEventParameter::valuePtr(int inst) +{ + return &my.values[inst]; +} + +int +QmcEventParameter::type() const +{ + return my.desc->desc().type; +} + +double +QmcEventParameter::value(int inst) const +{ + return my.values[inst].currentValue(); +} + +QString +QmcEventParameter::stringValue(int inst) const +{ + return my.values[inst].stringValue(); +} + +void +QmcEventParameter::dump(QTextStream &os, int instID) const +{ + pmDesc desc = my.desc->desc(); + + for (int i = 0; i < my.values.size(); i++) { + QmcMetricValue const &value = my.values.at(i); + + os << " " << *my.name; + if (desc.indom != PM_INDOM_NULL) { + if (my.values.size() > 1) + os << endl << " "; + QString name = my.indom->name(instID); + if (name == QString::null) + os << "[" << instID << "]"; + else + os << "[\"" << name << "\"]"; + } + os << " "; + + if (QmcMetric::real(desc.type)) + os << value.currentValue(); + else if (QmcMetric::aggregate(desc.type)) + os << "[" << value.stringValue() << "]"; + else if (QmcMetric::event(desc.type) == false) + os << "\"" << value.stringValue() << "\""; + os << endl; + } +} + +void +QmcEventRecord::dump(QTextStream &os, int instID, uint recordID) const +{ + os << " " << QmcSource::timeStringBrief(&my.timestamp); + os << " --- event record [" << recordID << "]"; + if (my.flags) { + os.setIntegerBase(16); + os << " flags 0x" << (uint)my.flags << " (" << pmEventFlagsStr(my.flags) << ")"; + os.setIntegerBase(10); + } + os << " ---" << endl; + if (my.flags & PM_EVENT_FLAG_MISSED) + os << " ==> " << my.missed << " missed event records" << endl; + for (int i = 0; i < my.parameters.size(); i++) + my.parameters.at(i).dump(os, instID); +} + +void +QmcMetricValue::dumpEventRecords(QTextStream &os, int instID) const +{ + os << my.eventRecords.size() << " event records" << endl; + for (int i = 0; i < my.eventRecords.size(); i++) + my.eventRecords.at(i).dump(os, instID, i); +} + +void +QmcMetric::dumpEventMetric(QTextStream &os, bool srcFlag, uint instance) const +{ + Q_ASSERT(event()); + + for (int i = 0; i < my.values.size(); i++) { + int instID; + + if (srcFlag == true) + dumpSource(os); + os << name(); + + instID = PM_IN_NULL; + if (hasInstances()) { + if (instance != UINT_MAX) + instID = (int)instance; + else + instID = my.values[i].instance(); + + QString inst = instName(instID); + if (inst == QString::null) + os << "[" << instID << "]"; + else + os << "[\"" << inst << "\"]"; + } + os << ": "; + my.values[i].dumpEventRecords(os, instID); + } +} + +pmID +QmcEventRecord::eventMissed(void) +{ + static pmID eventMissed = PM_IN_NULL; + static const char *nameMissed[] = { "event.missed" }; + + if (eventMissed == PM_ID_NULL) + if (pmLookupName(1, (char **)nameMissed, &eventMissed) < 0) + eventMissed = PM_ID_NULL; + return eventMissed; +} + +pmID +QmcEventRecord::eventFlags(void) +{ + static pmID eventFlags = PM_IN_NULL; + static const char *nameFlags[] = { "event.flags" }; + + if (eventFlags == PM_ID_NULL) + if (pmLookupName(1, (char **)nameFlags, &eventFlags) < 0) + eventFlags = PM_ID_NULL; + return eventFlags; +} + +bool +QmcMetric::updateIndom(void) +{ + int i = 0, j, oldNum = numInst(), newNum, newInst; + QmcIndom *indomPtr = indom(); + + if (status() < 0 || !hasIndom()) + return false; + + if (indomPtr->changed()) + indomPtr->update(); + + my.explicitInst = false; + + newNum = my.active ? indomPtr->numActiveInsts() : indomPtr->numInsts(); + + // If the number of instances are the same then we know that no + // modifications to the metric instance list is required as these + // instances are all referenced in the indom + // + // If the instance list is only active instances, then we need to + // check all the instances as the number may be the same + // + if (newNum == oldNum) { + if (my.active) { + for (i = 0; i < my.values.size(); i++) + if (!indomPtr->activeInst(my.values[i].instance())) + break; + } + if (!my.active || i == my.values.size()) { + if (pmDebug & DBG_TRACE_INDOM) { + QTextStream cerr(stderr); + cerr << "QmcMetric::updateIndom: No change required" << endl; + } + return false; + } + } + + // Duplicate the current values + // Replace the old index into the indom instance list with the + // internal instance identifiers so that these can be correlated + // if the order of instances changes + QList<QmcMetricValue> oldValues = my.values; + for (i = 0; i < oldNum; i++) { + oldValues[i].setInstance(indomPtr->inst(my.values[i].instance())); + indomPtr->removeRef(my.values[i].instance()); + } + + setupValues(newNum); + indomPtr->refAll(my.active); + + if (my.active) { + for (i = 0, j = 0; i < indomPtr->listLen(); i++) + if (!indomPtr->nullInst(i) && indomPtr->activeInst(i)) + my.values[j++].setInstance(i); + } + else { + for (i = 0, j = 0; i < indomPtr->listLen(); i++) + if (!indomPtr->nullInst(i)) + my.values[j++].setInstance(i); + } + + // Copy values of instances that have not gone away + // Note that their position may have changed + for (i = 0; i < my.values.size(); i++) { + if (i < oldValues.size() && + indomPtr->inst(my.values[i].instance()) == + oldValues[i].instance()) { + newInst = my.values[i].instance(); + my.values[i] = oldValues[i]; + my.values[i].setInstance(newInst); + continue; + } + for (j = 0; j < oldValues.size(); j++) + if (indomPtr->inst(my.values[i].instance()) == + oldValues[j].instance()) { + newInst = my.values[i].instance(); + my.values[i] = oldValues[j]; + my.values[i].setInstance(newInst); + break; + } + + // Need to set all error flags to avoid problems with rate conversion + if (j == oldValues.size()) + my.values[i].setAllErrors(PM_ERR_VALUE); + } + + if (pmDebug & DBG_TRACE_PMC) { + QTextStream cerr(stderr); + cerr << "QmcMetric::updateIndom: " << spec(true) << ": Had " + << oldNum << " instances, now have " << numInst() << endl; + } + + indomPtr->update(); + + return true; +} + +int +QmcMetric::addInst(QString const& name) +{ + if (my.status < 0) + return my.status; + + if (!hasInstances()) + return PM_ERR_INDOM; + + int i = indom()->lookup(name); + if (i >= 0) { + setupValues(my.values.size() + 1); + my.values.last().setInstance(i); + } + + return i; +} + +void +QmcMetric::removeInst(uint index) +{ + Q_ASSERT(hasInstances()); + indom()->removeRef(my.values[index].instance()); + my.values.removeAt(index); +} diff --git a/src/libpcp_qmc/src/qmc_metric.h b/src/libpcp_qmc/src/qmc_metric.h new file mode 100644 index 0000000..7bca55b --- /dev/null +++ b/src/libpcp_qmc/src/qmc_metric.h @@ -0,0 +1,336 @@ +/* + * Copyright (c) 2012-2014 Red Hat, Inc. + * Copyright (c) 2007 Aconex. All Rights Reserved. + * Copyright (c) 1998-2005 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + */ +#ifndef QMC_METRIC_H +#define QMC_METRIC_H + +#include "qmc.h" +#include "qmc_desc.h" +#include "qmc_group.h" +#include "qmc_context.h" + +#include <qlist.h> +#include <qvector.h> +#include <qstring.h> +#include <qtextstream.h> + +class QmcMetricValue; + +class QmcEventParameter +{ +public: + QmcEventParameter() { my.pmid = PM_ID_NULL; } + + void setPMID(pmID pmid) { my.pmid = pmid; } + void setNamePtr(QString *name) { my.name = name; } + void setDescPtr(QmcDesc *desc) { my.desc = desc; } + void setIndomPtr(QmcIndom *indom) { my.indom = indom; } + + void setValueCount(int numInst); + QmcMetricValue *valuePtr(int inst); + + int type() const; + double value(int inst) const; + QString stringValue(int inst) const; + + void summary(QString &os, int instID) const; + void dump(QTextStream &os, int instID) const; + +private: + struct { + pmID pmid; // pmid for the parameter to an event + QString *name; // direct pointer into external cache + QmcDesc *desc; // direct pointer into external cache + QmcIndom *indom; // direct pointer into external cache + QVector<QmcMetricValue> values; + } my; +}; + +class QmcEventRecord +{ +public: + QmcEventRecord() { my.missed = my.flags = 0; } + + const struct timeval *timestamp() const { return &my.timestamp; } + void setTimestamp(struct timeval *tv) { my.timestamp = *tv; } + + int flags() const { return my.flags; } + void setFlags(int flags) { my.flags = flags; } + + int missed() const { return my.missed; } + void setMissed(int missed) { my.missed = missed; } + + void setParameterCount(int numParams) + { my.parameters.resize(numParams); } + int setParameter(int n, pmID pmid, QmcContext *cp, pmValueSet const *vp); + + QString parent() const; + QString identifier() const; + + void parameterSummary(QString &os, int instID) const; + void dump(QTextStream &os, int instID, uint recordID) const; + + static pmID eventFlags(); + static pmID eventMissed(); + +private: + QString parameterAsString(int index) const; + + struct { + struct timeval timestamp; + int missed; + int flags; + QVector<QmcEventParameter> parameters; + } my; +}; + +class QmcMetricValue +{ +public: + QmcMetricValue(); + QmcMetricValue const& operator=(QmcMetricValue const& rhs); + + int instance() const { return my.instance; } + void setInstance(int instance) { my.instance = instance; } + + int error() const { return my.error; } + void setError(int error) { my.error = error; } + void setAllErrors(int error) + { my.error = my.currentError = my.previousError = error; } + + double value() const { return my.value; } + void setValue(double value) { my.value = value; } + void divValue(double value) { my.value /= value; } + void addValue(double value) { my.value += value; } + void subValue(double value) { my.value -= value; } + + QString stringValue() const { return my.stringValue; } + void setStringValue(const char *s) { my.stringValue = s; } + + int currentError() const { return my.currentError; } + void setCurrentError(int error) + { my.currentError = error; resetCurrentValue(); } + double currentValue() const { return my.currentValue; } + void setCurrentValue(double value) { my.currentValue = value; } + + int previousError() const { return my.previousError; } + double previousValue() const { return my.previousValue; } + void shiftValues() { my.previousValue = my.currentValue; + my.previousError = my.currentError; + my.currentError = 0; } + + QVector<QmcEventRecord> const &eventRecords() const { return my.eventRecords; } + void extractEventRecords(QmcContext *context, int recordCount, pmResult **result); + void dumpEventRecords(QTextStream &os, int instid) const; + +private: + void resetCurrentValue() + { my.currentValue = 0.0; my.stringValue = QString::null; my.eventRecords.clear(); } + + struct { + int instance; + int error; + double value; + double previousValue; + double currentValue; + int currentError; + int previousError; + QString stringValue; + QVector<QmcEventRecord> eventRecords; + } my; +}; + +class QmcMetric +{ + friend class QmcGroup; + friend class QmcContext; + +public: + QmcMetric(QmcGroup *group, const char *str, double theScale = 0.0, + bool active = false); + QmcMetric(QmcGroup *group, pmMetricSpec *theMetric, double theScale = 0.0, + bool active = false); + ~QmcMetric(); + + int status() const { return my.status; } + pmID metricID() const { return my.pmid; } + const QString name() const { return my.name; } + char *nameAscii() const { return strdup((const char *)my.name.toAscii()); } + QmcContext *context() const + { return my.group->context(my.contextIndex); } + const QmcDesc &desc() const + { return context()->desc(my.pmid); } + bool hasIndom() const + { return desc().desc().indom != PM_INDOM_NULL; } + bool hasInstances() const + { return (my.status >= 0 && my.indomIndex < UINT_MAX); } + + // Were the instances explicitly listed? + bool explicitInsts() const { return my.explicitInst; } + + // Are only active instances referenced + bool activeInsts() const { return my.active; } + + int numInst() const + { return (my.status >= 0 && my.indomIndex < UINT_MAX) ? + my.values.size() : 0; } + + // How many values does it have (will not equal number of instances + // if singular) + int numValues() const { return (my.status >= 0) ? my.values.size() : 0; } + + // The metric indom + QmcIndom *indom() const + { return (my.indomIndex == UINT_MAX) ? NULL : + &(my.group->context(my.contextIndex)->indom(my.indomIndex)); } + + // Internal instance id for instance <index> + int instID(int index) const + { return my.group->context(my.contextIndex)->indom(my.indomIndex).inst(my.values[index].instance()); } + + // External instance name for instance <index> + const QString instName(int index) const + { return my.group->context(my.contextIndex)->indom(my.indomIndex).name(my.values[index].instance()); } + + // Return the index for the instance in the indom list + int instIndex(uint index) const { return my.values[index].instance(); } + + // Update the metric to include new instances + // Returns true if the instance list changed + // Metrics with implicit instances will be extended to include those + // new instances. The position of instances may change. + // If <active> is set, only those instances in the latest indom will + // be listed, other instances will be removed + bool updateIndom(); + + int addInst(QString const& name); + void removeInst(uint index); + + // Scaling modifier applied to metric values + double scale() const { return my.scale; } + + // Metric has event records (as opposed to real/string/aggregate values) + bool event() const { return event(desc().desc().type); } + static bool event(int type) + { return type == PM_TYPE_EVENT || type == PM_TYPE_HIGHRES_EVENT; } + + bool aggregate() const { return aggregate(desc().desc().type); } + static void aggregateAsString(pmValue const *, char *, int); + static bool aggregate(int type) + { return type == PM_TYPE_AGGREGATE || type == PM_TYPE_AGGREGATE_STATIC; } + + // Metric has real values (as opposed to event/string/aggregate values) + bool real() const { return real(desc().desc().type); } + static bool real(int type) + { return type > PM_TYPE_NOSUPPORT && type < PM_TYPE_STRING; } + + // Current rate-converted and scaled real value + double value(int index) const { return my.values[index].value(); } + + double realValue(int index) const // Current rate-converted value + { return my.values[index].value() * my.scale; } + + double currentValue(int index) const // Current raw value + { return my.values[index].currentValue(); } + + QString stringValue(int index) const // Current string value + { return my.values[index].stringValue(); } + + QVector<QmcEventRecord> const &eventRecords(int index) const + { return my.values[index].eventRecords(); } + + int error(int index) const // Current error code (after rate-conversion) + { return my.values[index].error(); } + + int currentError(int index) const // Current raw error code + { return my.values[index].currentError(); } + + void shiftValues(); // Shift values in preparation for next fetch + + void setError(int sts); // Set error code for all instances + + void extractValues(pmValueSet const* set); // Extract values after a fetch + + uint contextIndex() const // Index for context in group list + { return my.contextIndex; } + + // Index for metric into pmResult + uint idIndex() const { return my.idIndex; } + + // Index for indom in context list + uint indomIndex() const { return my.indomIndex; } + + // Set the canonical units + void setScaleUnits(pmUnits const& units); + + // Generate a metric spec + QString spec(bool srcFlag = false, + bool instFlag = false, + uint instance = UINT_MAX) const; + + // Dump out the metric and its current value(s) + void dump(QTextStream &os, bool srcFlag = false, + uint instance = UINT_MAX) const; + + // Dump out the current value + void dumpValue(QTextStream &os, uint instance) const; + + // Dump out the metric source + void dumpSource(QTextStream &os) const; + + // Format a value into a fixed width format + static const char *formatNumber(double value); + + // Determine the current errors and rate-converted scaled values + int update(); + + friend QTextStream &operator<<(QTextStream &os, const QmcMetric &metric); + +private: + struct { + pmID pmid; + int status; + QString name; + QmcGroup *group; + QList<QmcMetricValue> values; + double scale; + + uint contextIndex; // Index into the context list for the group + uint idIndex; // Index into the pmid list for the context. + uint indomIndex; // Index into the indom list for the context. + + bool explicitInst; // Instances explicitly specified + bool active; // Use only active implicit insts + } my; + + void setup(QmcGroup *group, pmMetricSpec *theMetric); + void setupDesc(QmcGroup *group, pmMetricSpec *theMetric); + void setupIndom(pmMetricSpec *theMetric); + void setupValues(int num); + + void extractNumericMetric(pmValueSet const *vset, pmValue const *v, QmcMetricValue &vref); + void extractArrayMetric(pmValueSet const *vset, pmValue const *v, QmcMetricValue &vref); + void extractEventMetric(pmValueSet const *vset, int index, QmcMetricValue &vref); + + void setIdIndex(uint index) { my.idIndex = index; } + + // Dump error messages + void dumpAll() const; + void dumpErr() const; + void dumpErr(const char *inst) const; + + // Dump out different metric flavours and their current value(s) + void dumpEventMetric(QTextStream &os, bool srcFlag = false, + uint instance = UINT_MAX) const; + void dumpSampledMetric(QTextStream &os, bool srcFlag = false, + uint instance = UINT_MAX) const; +}; + +#endif // QMC_METRIC_H diff --git a/src/libpcp_qmc/src/qmc_source.cpp b/src/libpcp_qmc/src/qmc_source.cpp new file mode 100644 index 0000000..3935217 --- /dev/null +++ b/src/libpcp_qmc/src/qmc_source.cpp @@ -0,0 +1,425 @@ +/* + * Copyright (c) 2013 Red Hat. + * Copyright (c) 2007 Aconex. All Rights Reserved. + * Copyright (c) 1998,2005 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + */ +#include "qmc_source.h" + +QString QmcSource::localHost; +QList<QmcSource*> QmcSource::sourceList; + +QmcSource::QmcSource(int type, QString &source, int flags) +{ + my.status = -1; + my.flags = flags; + my.type = type; + my.tz = 0; + my.dupFlag = false; + + if (localHost.length() == 0) { + char buf[MAXHOSTNAMELEN]; + gethostname(buf, MAXHOSTNAMELEN); + buf[MAXHOSTNAMELEN-1] = '\0'; + localHost = buf; + } + + this->retryConnect(type, source); +} + +void +QmcSource::retryConnect(int type, QString &source) +{ + int oldTZ; + int oldContext; + int offset; + int sts; + char *tzs; + QString hostSpec; + + my.attrs = QString::null; + switch(type) { + case PM_CONTEXT_LOCAL: + my.desc = "Local context"; + my.host = my.source = localHost; + my.proxy = ""; + break; + + case PM_CONTEXT_HOST: + my.desc = "host \""; + my.desc.append(source); + my.desc.append(QChar('\"')); + my.host = source; + my.proxy = getenv("PMPROXY_HOST"); + if ((offset = my.host.indexOf('?')) >= 0) { + my.attrs = my.host; + my.attrs.remove(0, offset+1); + my.host.truncate(offset); + } + if ((offset = my.host.indexOf('@')) >= 0) { + my.proxy = my.host; + my.proxy.remove(0, offset+1); + } + my.source = my.host; + break; + + case PM_CONTEXT_ARCHIVE: + my.desc = "archive \""; + my.desc.append(source); + my.desc.append(QChar('\"')); + my.source = source; + my.proxy = ""; + break; + } + + oldContext = pmWhichContext(); + + hostSpec = source; + if (my.attrs != QString::null) + hostSpec.append("?").append(my.attrs); + + my.status = pmNewContext(type | my.flags, (const char *)hostSpec.toAscii()); + if (my.status >= 0) { + my.handles.append(my.status); + + // Fetch the server-side host name for this context, properly as of pcp 3.8.3+. + my.context_hostname = pmGetContextHostName (my.status); // NB: may leak memory + if (my.context_hostname == "") // may be returned for errors or PM_CONTEXT_LOCAL + my.context_hostname = localHost; + + if (my.type == PM_CONTEXT_ARCHIVE) { + pmLogLabel lp; + sts = pmGetArchiveLabel(&lp); + if (sts < 0) { + pmprintf("%s: Unable to obtain log label for \"%s\": %s\n", + pmProgname, (const char *)my.desc.toAscii(), + pmErrStr(sts)); + my.host = "unknown?"; + my.status = sts; + goto done; + } + else { + my.host = lp.ll_hostname; + my.start = lp.ll_start; + } + sts = pmGetArchiveEnd(&my.end); + if (sts < 0) { + pmprintf("%s: Unable to determine end of \"%s\": %s\n", + pmProgname, (const char *)my.desc.toAscii(), + pmErrStr(sts)); + my.status = sts; + goto done; + } + } + else { + gettimeofday(&my.start, NULL); + my.end = my.start; + } + + if (pmDebug & DBG_TRACE_PMC) { + QTextStream cerr(stderr); + cerr << "QmcSource::QmcSource: Created context " + << my.handles.last() << " to " << my.desc << endl; + } + + oldTZ = pmWhichZone(&tzs); + my.tz = pmNewContextZone(); + if (my.tz < 0) + pmprintf("%s: Warning: Unable to obtain timezone for %s: %s\n", + pmProgname, (const char *)my.desc.toAscii(), + pmErrStr(my.tz)); + else { + sts = pmWhichZone(&tzs); + if (sts >= 0) + my.timezone = tzs; + else + pmprintf("%s: Warning: Unable to obtain timezone for %s: %s\n", + pmProgname, (const char *)my.desc.toAscii(), + pmErrStr(sts)); + } + + if (oldTZ >= 0) { + sts = pmUseZone(oldTZ); + if (sts < 0) { + pmprintf("%s: Warning: Unable to switch timezones." + " Using timezone for %s: %s\n", + pmProgname, (const char *)my.desc.toAscii(), + pmErrStr(sts)); + } + } + } + else if (pmDebug & DBG_TRACE_PMC) { + QTextStream cerr(stderr); + cerr << "QmcSource::QmcSource: Context to " << source + << " failed: " << pmErrStr(my.status) << endl; + } + + done: + sourceList.append(this); + + if (oldContext >= 0) { + sts = pmUseContext(oldContext); + if (sts < 0) { + pmprintf("%s: Warning: Unable to switch contexts." + " Using context to %s: %s\n", + pmProgname, (const char *)my.desc.toAscii(), + pmErrStr(sts)); + } + } +} + +QmcSource::~QmcSource() +{ + int i; + + for (i = 0; i < sourceList.size(); i++) + if (sourceList[i] == this) + break; + if (i < sourceList.size()) + sourceList.removeAt(i); +} + +QString +QmcSource::timeString(const struct timeval *timeval) +{ + QString timestring; + char timebuf[32], *ddmm, *year; + struct tm tmp; + time_t secs = (time_t)timeval->tv_sec; + + ddmm = pmCtime(&secs, timebuf); + ddmm[10] = '\0'; + year = &ddmm[20]; + year[4] = '\0'; + pmLocaltime(&secs, &tmp); + + timestring.sprintf("%02d:%02d:%02d.%03d", + tmp.tm_hour, tmp.tm_min, tmp.tm_sec, (int)(timeval->tv_usec/1000)); + timestring.prepend(" "); + timestring.prepend(ddmm); + timestring.append(" "); + timestring.append(year); + return timestring; +} + +QString +QmcSource::timeStringBrief(const struct timeval *timeval) +{ + QString timestring; + struct tm tmp; + time_t secs = (time_t)timeval->tv_sec; + + pmLocaltime(&secs, &tmp); + timestring.sprintf("%02d:%02d:%02d.%03d", + tmp.tm_hour, tmp.tm_min, tmp.tm_sec, (int)(timeval->tv_usec/1000)); + return timestring; +} + +bool +QmcSource::compare(int type, QString &source, int flags) +{ + if (this->type() != type) + return false; + if (this->flags() != flags) + return false; + return this->source() == source; +} + +QmcSource* +QmcSource::getSource(int type, QString &source, int flags, bool matchHosts) +{ + int i; + QmcSource *src = NULL; + + for (i = 0; i < sourceList.size(); i++) { + src = sourceList[i]; + if (matchHosts && type == PM_CONTEXT_HOST) { + if (src->type() == PM_CONTEXT_ARCHIVE && src->host() == source) { + if (pmDebug & DBG_TRACE_PMC) { + QTextStream cerr(stderr); + cerr << "QmcSource::getSource: Matched host " + << source << " to archive " << src->source() + << " (source " << i << ")" << endl; + } + break; + } + } + else if (src->compare(type, source, flags)) { + if (pmDebug & DBG_TRACE_PMC) { + QTextStream cerr(stderr); + cerr << "QmcSource::getSource: Matched " << source + << " to source " << i << endl; + } + if (src->status() < 0) + src->retryConnect(type, source); + break; + } + } + + if (i == sourceList.size() && + !(matchHosts == true && type == PM_CONTEXT_HOST)) { + if (pmDebug & DBG_TRACE_PMC) { + QTextStream cerr(stderr); + if (type != PM_CONTEXT_LOCAL) + cerr << "QmcSource::getSource: Creating new source for " + << source << endl; + else + cerr << "QmcSource::getSource: Creating new local context" + << endl; + } + src = new QmcSource(type, source, flags); + } + + if (src == NULL && pmDebug & DBG_TRACE_PMC) { + QTextStream cerr(stderr); + cerr << "QmcSource::getSource: Unable to map host " + << source << " to an arch context" << endl; + } + + return src; +} + +int +QmcSource::dupContext() +{ + int sts = 0; + + if (my.status < 0) + return my.status; + + if (my.dupFlag == false && my.handles.size() == 1) { + sts = pmUseContext(my.handles[0]); + if (sts >= 0) { + sts = my.handles[0]; + my.dupFlag = true; + if (pmDebug & DBG_TRACE_PMC) { + QTextStream cerr(stderr); + cerr << "QmcSource::dupContext: Using original context for " + << my.desc << endl; + } + } + else + pmprintf("%s: Error: Unable to switch to context for \"%s\": %s\n", + pmProgname, (const char *)my.desc.toAscii(), + pmErrStr(sts)); + } + else if (my.handles.size()) { + sts = pmUseContext(my.handles[0]); + if (sts >= 0) { + sts = pmDupContext(); + if (sts >= 0) { + my.handles.append(sts); + if (pmDebug & DBG_TRACE_PMC) { + QTextStream cerr(stderr); + cerr << "QmcSource::dupContext: " << my.desc + << " duplicated, handle[" << my.handles.size() - 1 + << "] = " << sts << endl; + } + } + else + pmprintf("%s: Error: " + "Unable to duplicate context to \"%s\": %s\n", + pmProgname, (const char *)my.desc.toAscii(), + pmErrStr(sts)); + } + else + pmprintf("%s: Error: Unable to switch to context for \"%s\": %s\n", + pmProgname, (const char *)my.desc.toAscii(), + pmErrStr(sts)); + } + // No active contexts, create a new context + else { + sts = pmNewContext(my.type, sourceAscii()); + if (sts >= 0) { + my.handles.append(sts); + if (pmDebug & DBG_TRACE_PMC) { + QTextStream cerr(stderr); + cerr << "QmcSource::dupContext: new context to " << my.desc + << " created, handle = " << sts << endl; + } + } + } + + if (sts < 0 && pmDebug & DBG_TRACE_PMC) { + QTextStream cerr(stderr); + cerr << "QmcSource::dupContext: context to " << my.desc + << " failed: " << pmErrStr(my.status) << endl; + } + + return sts; +} + +int +QmcSource::delContext(int handle) +{ + int i; + int sts; + + for (i = 0; i < my.handles.size(); i++) + if (my.handles[i] == handle) + break; + + if (i == my.handles.size()) { + if (pmDebug & DBG_TRACE_PMC) { + QTextStream cerr(stderr); + cerr << "QmcSource::delContext: Attempt to delete " << handle + << " from list for " << my.desc << ", but it is not listed" + << endl; + } + return PM_ERR_NOCONTEXT; + } + + sts = pmDestroyContext(my.handles[i]); + my.handles.removeAt(i); + + // If this is a valid source, but no more contexts remain, + // then we should delete ourselves + if (my.handles.size() == 0 && my.status >= 0) { + if (pmDebug & DBG_TRACE_PMC) { + QTextStream cerr(stderr); + cerr << "QmcSource::delContext: No contexts remain, removing " + << my.desc << endl; + } + delete this; + } + + return sts; +} + +QTextStream& +operator<<(QTextStream &stream, const QmcSource &rhs) +{ + stream << rhs.my.desc; + return stream; +} + +void +QmcSource::dump(QTextStream &stream) +{ + stream << " sts = " << my.status << ", type = " << my.type + << ", source = " << my.source << endl + << " host = " << my.host << ", timezone = " << my.timezone + << ", tz hndl = " << my.tz << endl; + if (my.status >= 0) + stream << " start = " << timeString(&my.start) << ", end = " + << timeString(&my.end) << ", dupFlag = " + << (my.dupFlag == true ? "true" : "false") << endl << " " + << my.handles.size() << " contexts: "; + for (int i = 0; i < my.handles.size(); i++) + stream << my.handles[i] << ' '; + stream << endl; +} + +void +QmcSource::dumpList(QTextStream &stream) +{ + stream << sourceList.size() << " sources:" << endl; + for (int i = 0; i < sourceList.size(); i++) { + stream << '[' << i << "] " << *(sourceList[i]) << endl; + sourceList[i]->dump(stream); + } +} diff --git a/src/libpcp_qmc/src/qmc_source.h b/src/libpcp_qmc/src/qmc_source.h new file mode 100644 index 0000000..02b8f12 --- /dev/null +++ b/src/libpcp_qmc/src/qmc_source.h @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2013 Red Hat, Inc. + * Copyright (c) 2007 Aconex. All Rights Reserved. + * Copyright (c) 1998,2005 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + */ +#ifndef QMC_SOURCE_H +#define QMC_SOURCE_H + +#include "qmc.h" + +#include <qlist.h> +#include <qstring.h> +#include <qtextstream.h> + +class QmcSource +{ +public: + QmcSource(int type, QString &source, int flags = 0); + ~QmcSource(); + + // Get the source description by searching the list of existing sources + // and returning a new source only if required. + // If matchHosts is true, then it will attempt to map a live context + // to an archive source. If no matching archive context is found, + // a NULL pointer is returned. + static QmcSource* getSource(int type, QString &source, int flags = 0, + bool matchHosts = false); + + int status() const { return my.status; } + int flags() const { return my.flags; } + int type() const { return my.type; } + bool isArchive() const { return my.type == PM_CONTEXT_ARCHIVE; } + QString source() const { return my.source; } + char *sourceAscii() const { return strdup((const char*)my.source.toAscii()); } + QString host() const { return my.host; } + char *hostAscii() const { return strdup((const char *)my.host.toAscii()); } + QString proxy() const { return my.proxy; } + char *proxyAscii() const { return strdup((const char *)my.proxy.toAscii()); } + int tzHandle() const { return my.tz; } + QString timezone() const { return my.timezone; } + struct timeval start() const { return my.start; } + QString startTime() { return timeString(&my.start); } + struct timeval end() const { return my.end; } + QString endTime() { return timeString(&my.end); } + QString desc() const { return my.desc; } + char *descAscii() const { return strdup((const char *)my.desc.toAscii()); } + QString context_hostname() const { return my.context_hostname; } + + // Number of active contexts to this source + uint numContexts() const { return my.handles.size(); } + + // Create a new context to this source + int dupContext(); + + // Delete context to this source + int delContext(int handle); + + // Output the source + friend QTextStream &operator<<(QTextStream &os, const QmcSource &rhs); + + // Dump all info about a source + void dump(QTextStream &os); + + // Dump list of known sources + static void dumpList(QTextStream &os); + + // Local host name (from gethostname(2)) + static QString localHost; + + // Convert a time to a string (long and short forms) + static QString timeString(const struct timeval *timeval); + static QString timeStringBrief(const struct timeval *timeval); + +protected: + // retry context/connection (e.g. if it failed in the constructor) + void retryConnect(int type, QString &source); + + // compare two sources - static so getSource() can make use of it + bool compare(int type, QString &source, int flags); + +private: + struct { + int status; + int type; + QString source; + QString proxy; + QString attrs; + QString host; + QString context_hostname; // from pmcd/archive, not from -h/-a argument + QString desc; + QString timezone; + QList<int> handles; // Contexts created for this source + struct timeval start; + struct timeval end; + int tz; + bool dupFlag; // Dup has been called and 1st context is in use + int flags; + } my; + + static QList<QmcSource*> sourceList; +}; + +#endif // QMC_SOURCE_H diff --git a/src/libpcp_qmc/src/qmc_time.cpp b/src/libpcp_qmc/src/qmc_time.cpp new file mode 100644 index 0000000..b59db0d --- /dev/null +++ b/src/libpcp_qmc/src/qmc_time.cpp @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2006, Ken McDonell. All Rights Reserved. + * 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 <QtGui/QIcon> +#include "qmc_time.h" + +// +// Map icon type name to QIcon +// +extern QIcon *QmcTime::icon(QmcTime::Icon type) +{ + static QIcon icons[QmcTime::IconCount]; + static int setup; + + if (!setup) { + setup = 1; + icons[QmcTime::ForwardOn] = QIcon(":images/play_on.png"); + icons[QmcTime::ForwardOff] = QIcon(":images/play_off.png"); + icons[QmcTime::StoppedOn] = QIcon(":images/stop_on.png"); + icons[QmcTime::StoppedOff] = QIcon(":images/stop_off.png"); + icons[QmcTime::BackwardOn] = QIcon(":images/back_on.png"); + icons[QmcTime::BackwardOff] = QIcon(":images/back_off.png"); + icons[QmcTime::FastForwardOn] = QIcon(":images/fastfwd_on.png"); + icons[QmcTime::FastForwardOff] = QIcon(":images/fastfwd_off.png"); + icons[QmcTime::FastBackwardOn] = QIcon(":images/fastback_on.png"); + icons[QmcTime::FastBackwardOff] = QIcon(":images/fastback_off.png"); + icons[QmcTime::StepForwardOn] = QIcon(":images/stepfwd_on.png"); + icons[QmcTime::StepForwardOff] = QIcon(":images/stepfwd_off.png"); + icons[QmcTime::StepBackwardOn] = QIcon(":images/stepback_on.png"); + icons[QmcTime::StepBackwardOff] = QIcon(":images/stepback_off.png"); + } + return &icons[type]; +} + +// +// Test for not-zeroed timeval +// +int QmcTime::timevalNonZero(struct timeval *a) +{ + return (a->tv_sec != 0 || a->tv_usec != 0); +} + +// +// a := a + b for struct timevals +// +void QmcTime::timevalAdd(struct timeval *a, struct timeval *b) +{ + a->tv_usec += b->tv_usec; + if (a->tv_usec > 1000000) { + a->tv_usec -= 1000000; + a->tv_sec++; + } + a->tv_sec += b->tv_sec; +} + +// +// a := a - b for struct timevals, result is never less than zero +// +void QmcTime::timevalSub(struct timeval *a, struct timeval *b) +{ + a->tv_usec -= b->tv_usec; + if (a->tv_usec < 0) { + a->tv_usec += 1000000; + a->tv_sec--; + } + a->tv_sec -= b->tv_sec; + if (a->tv_sec < 0) { + /* clip negative values at zero */ + a->tv_sec = 0; + a->tv_usec = 0; + } +} + +// +// a : b for struct timevals ... <0 for a<b, ==0 for a==b, >0 for a>b +// +int QmcTime::timevalCompare(struct timeval *a, struct timeval *b) +{ + int res = (int)(a->tv_sec - b->tv_sec); + if (res == 0) + res = (int)(a->tv_usec - b->tv_usec); + return res; +} + +// +// Conversion from seconds (double precision) to struct timeval +// +void QmcTime::secondsToTimeval(double value, struct timeval *tv) +{ + tv->tv_sec = (time_t)value; + tv->tv_usec = (long)(((value - (double)tv->tv_sec) * 1000000.0)); +} + +// +// Conversion from struct timeval to seconds (double precision) +// +double QmcTime::secondsFromTimeval(struct timeval *tv) +{ + return (double)tv->tv_sec + ((double)tv->tv_usec / 1000000.0); +} + +// +// Conversion from other time units into seconds +// +double QmcTime::unitsToSeconds(double value, QmcTime::DeltaUnits units) +{ + if (units == QmcTime::Milliseconds) + return value / 1000.0; + else if (units == QmcTime::Minutes) + return value * 60.0; + else if (units == QmcTime::Hours) + return value * (60.0 * 60.0); + else if (units == QmcTime::Days) + return value * (60.0 * 60.0 * 24.0); + else if (units == QmcTime::Weeks) + return value * (60.0 * 60.0 * 24.0 * 7.0); + return value; +} + +// +// Conversion from seconds into other time units +// +double QmcTime::secondsToUnits(double value, QmcTime::DeltaUnits units) +{ + switch (units) { + case Milliseconds: + value = value * 1000.0; + break; + case Minutes: + value = value / 60.0; + break; + case Hours: + value = value / (60.0 * 60.0); + break; + case Days: + value = value / (60.0 * 60.0 * 24.0); + break; + case Weeks: + value = value / (60.0 * 60.0 * 24.0 * 7.0); + break; + case Seconds: + default: + break; + } + return value; +} + +double QmcTime::deltaValue(QString delta, QmcTime::DeltaUnits units) +{ + return QmcTime::secondsToUnits(delta.trimmed().toDouble(), units); +} + +QString QmcTime::deltaString(double value, QmcTime::DeltaUnits units) +{ + QString delta; + + value = QmcTime::secondsToUnits(value, units); + if ((double)(int)value == value) + delta.sprintf("%.2f", value); + else + delta.sprintf("%.6f", value); + return delta; +} diff --git a/src/libpcp_qmc/src/qmc_time.h b/src/libpcp_qmc/src/qmc_time.h new file mode 100644 index 0000000..0baf59d --- /dev/null +++ b/src/libpcp_qmc/src/qmc_time.h @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2006-2009, 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. + */ +#ifndef QMC_TIME_H +#define QMC_TIME_H + +#include <sys/time.h> + +class QIcon; + +class QmcTime +{ +public: + typedef enum { + StoppedState = 0, + ForwardState = 1, + BackwardState = 2, + } State; + + typedef enum { + StepMode = 0, + NormalMode = 1, + FastMode = 2, + } Mode; + + typedef enum { + NoSource = -1, + HostSource = 0, + ArchiveSource = 1, + } Source; + + typedef enum { + Set = (1<<0), // client -> server + Step = (1<<1), // server -> clients + TZ = (1<<2), // server -> clients + VCRMode = (1<<3), // server -> clients + VCRModeDrag = (1<<4), // server -> clients + GUIShow = (1<<5), // client -> server + GUIHide = (1<<6), // client -> server + Bounds = (1<<7), // client -> server + ACK = (1<<8), // client -> server (except handshake) + } Command; + + static const unsigned int Magic = 0x54494D45; // "TIME" + + typedef struct { + unsigned int magic; + unsigned int length; + QmcTime::Command command; + QmcTime::Source source; + QmcTime::State state; + QmcTime::Mode mode; + struct timeval delta; + struct timeval position; + struct timeval start; // archive only + struct timeval end; // archive only + unsigned char data[0]; // arbitrary length (e.g. $TZ) + } Packet; + + typedef enum { + ForwardOn, ForwardOff, + StoppedOn, StoppedOff, + BackwardOn, BackwardOff, + FastForwardOn, FastForwardOff, + FastBackwardOn, FastBackwardOff, + StepForwardOn, StepForwardOff, + StepBackwardOn, StepBackwardOff, + IconCount + } Icon; + + typedef enum { + Milliseconds, + Seconds, + Minutes, + Hours, + Days, + Weeks, + } DeltaUnits; + + typedef enum { + DebugApp = 0x1, + DebugProtocol = 0x2, + } DebugOptions; + + static const int BasePort = 43334; + static const int FastModeDelay = 100; // milliseconds + static const int DefaultDelta = 2; // seconds + + static QIcon *icon(QmcTime::Icon); + static double defaultSpeed(double delta) + { return 2.0 * delta; } // num deltas per second + static double minimumSpeed(double delta) + { return 0.1 * delta; } // min deltas per second + static double maximumSpeed(double delta) + { return 1000.0 * delta; } // max deltas per second + + static void timevalAdd(struct timeval *a, struct timeval *b); + static void timevalSub(struct timeval *a, struct timeval *b); + static int timevalNonZero(struct timeval *a); + static int timevalCompare(struct timeval *a, struct timeval *b); + + static void secondsToTimeval(double value, struct timeval *tv); + static double secondsFromTimeval(struct timeval *tv); + + static double unitsToSeconds(double value, DeltaUnits units); + static double secondsToUnits(double value, DeltaUnits units); + static QString deltaString(double value, DeltaUnits units); + static double deltaValue(QString delta, DeltaUnits units); +}; + +#endif // QMC_TIME_H |