/* Web Polygraph http://www.web-polygraph.org/ * (C) 2003-2006 The Measurement Factory * Licensed under the Apache License, Version 2.0 */ #include "base/polygraph.h" #include "xstd/h/iomanip.h" #include "base/ErrorRec.h" #include "base/ErrorStat.h" #include "base/polyLogCats.h" #include "runtime/HttpDate.h" #include "xml/XmlAttr.h" #include "xml/XmlTable.h" #include "xml/XmlParagraph.h" #include "xml/XmlText.h" #include "loganalyzers/ReportBlob.h" #include "loganalyzers/BlobDb.h" #include "loganalyzers/HistogramFigure.h" #include "loganalyzers/RptmHistFig.h" #include "loganalyzers/SizeHistFig.h" #include "loganalyzers/PointTraceFig.h" #include "loganalyzers/RptmTraceFig.h" #include "loganalyzers/LevelTraceFig.h" #include "loganalyzers/LoadTraceFig.h" #include "loganalyzers/ScatteredFig.h" #include "loganalyzers/InfoScopes.h" #include "loganalyzers/Stex.h" #include "loganalyzers/PointStex.h" #include "loganalyzers/HistStex.h" #include "loganalyzers/LevelStex.h" #include "loganalyzers/LoadStexes.h" #include "loganalyzers/PhaseTrace.h" #include "loganalyzers/TestInfo.h" #include "loganalyzers/ProcInfo.h" #include "loganalyzers/SideInfo.h" Stex *SideInfo::TheAllReps = 0; Array SideInfo::TheStex; static XmlAttr algnLeft("align", "left"); static XmlAttr algnRight("align", "right"); SideInfo::SideInfo(int aLogCat): theLogCat(aLogCat) { Assert(theLogCat == lgcCltSide || theLogCat == lgcSrvSide); theScope.name("all phases"); theScope.addSide(name()); } SideInfo::~SideInfo() { while (theProcs.count()) { theProcs.last()->side(0); delete theProcs.pop(); } while (thePhases.count()) delete thePhases.pop(); } void SideInfo::test(TestInfo *t) { Assert(!theTest ^ !t); theTest = t; } int SideInfo::logCat() const { return theLogCat; } const String &SideInfo::name() const { static String cltName = "client"; static String srvName = "server"; return theLogCat == lgcCltSide ? cltName : srvName; } const String &SideInfo::otherName() const { static String cltName = "client"; static String srvName = "server"; return theLogCat == lgcCltSide ? srvName : cltName; } const String &SideInfo::benchmarkVersion() const { return theBenchmarkVersion; } const String &SideInfo::pglCfg() const { return thePglCfg; } Time SideInfo::startTime() const { return theStartTime; } int SideInfo::scopes(InfoScopes &res) const { res.add(execScope()); res.add(theScope); for (int p = 0; p < thePhases.count(); ++p) { const String &pname = thePhases[p]->name(); Scope *scope = new Scope(theScope.onePhase(pname)); scope->name(pname); res.absorb(scope); } return res.count(); } const InfoScope &SideInfo::execScope() const { Assert(theTest); if (!theExecScope) { theExecScope = theTest->execScope().oneSide(name()); theExecScope.name("baseline"); } return theExecScope; } const StatPhaseRec &SideInfo::execScopeStats() const { return theExecScopePhase.stats(); } int SideInfo::repCount(const Scope &scope) const { int count = 0; for (int i = 0; i < theProcs.count(); ++i) { count += theProcs[i]->repCount(scope); } return count; } int SideInfo::hitCount(const Scope &scope) const { int count = 0; for (int i = 0; i < theProcs.count(); ++i) { count += theProcs[i]->hitCount(scope); } return count; } int SideInfo::offeredHitCount(const Scope &scope) const { int count = 0; for (int i = 0; i < theProcs.count(); ++i) { count += theProcs[i]->offeredHitCount(scope); } return count; } BigSize SideInfo::repVolume(const Scope &scope) const { BigSize volume = 0; for (int i = 0; i < theProcs.count(); ++i) { volume += theProcs[i]->repVolume(scope); } return volume; } BigSize SideInfo::hitVolume(const Scope &scope) const { BigSize volume = 0; for (int i = 0; i < theProcs.count(); ++i) { volume += theProcs[i]->hitVolume(scope); } return volume; } BigSize SideInfo::offeredHitVolume(const Scope &scope) const { BigSize volume = 0; for (int i = 0; i < theProcs.count(); ++i) { volume += theProcs[i]->offeredHitVolume(scope); } return volume; } void SideInfo::add(ProcInfo *p) { Assert(p); p->side(this); theProcs.append(p); // sync phases thePhases.stretch(p->phaseCount()); for (int i = 0; i < p->phaseCount(); ++i) addPhase(p->phase(i)); } void SideInfo::addPhase(const PhaseInfo &procPhase) { const String &name = procPhase.name(); PhaseInfo *accPhase = findPhase(name); if (!accPhase) { accPhase = new PhaseInfo(); thePhases.append(accPhase); theScope.addPhase(name); } accPhase->merge(procPhase); } ProcInfo &SideInfo::proc(int idx) { Assert(0 <= idx && idx < theProcs.count()); return *theProcs[idx]; } int SideInfo::procCount() const { return theProcs.count(); } const PhaseInfo &SideInfo::phase(const Scope &scope) const { if (scope.phases().count() == 1) return phase(*scope.phases().last()); if (scope.phases().count() == thePhases.count()) return theAllPhasesPhase; return theExecScopePhase; // what else can it be? } const PhaseInfo &SideInfo::phase(const String &name) const { const PhaseInfo *p = findPhase(name); Assert(p); return *p; } const PhaseInfo *SideInfo::findPhase(const String &name) const { for (int i = 0; i < thePhases.count(); ++i) { if (thePhases[i]->name() == name) return thePhases[i]; } return 0; } PhaseInfo *SideInfo::findPhase(const String &name) { for (int i = 0; i < thePhases.count(); ++i) { if (thePhases[i]->name() == name) return thePhases[i]; } return 0; } const PhaseInfo &SideInfo::phase(int idx) const { Assert(0 <= idx && idx < thePhases.count()); return *thePhases[idx]; } int SideInfo::phaseCount() const { return thePhases.count(); } void SideInfo::checkCommonPglCfg() { Assert(!thePglCfg); if (procCount()) { const ProcInfo &p = proc(0); bool mismatch = false; for (int i = 1; i < procCount(); ++i) { if (p.pglCfg() != proc(i).pglCfg()) { mismatch = true; cerr << "PGL configuration in " << p.name() << " differs from the one in " << proc(i).name() << endl; } } if (!mismatch) thePglCfg = p.pglCfg(); } } void SideInfo::checkCommonBenchmarkVersion() { Assert(!theBenchmarkVersion); if (procCount()) { const ProcInfo &p = proc(0); bool mismatch = false; for (int i = 1; i < procCount(); ++i) { if (p.benchmarkVersion() != proc(i).benchmarkVersion()) { mismatch = true; cerr << "benchmark version in " << p.name() << " differs from the one in " << proc(i).name() << endl; } } if (!mismatch) theBenchmarkVersion = p.benchmarkVersion(); } } void SideInfo::checkCommonStartTime() { Time firstTime, lastTime; String firstName, lastName; for (int i = 0; i < procCount(); ++i) { const Time t = proc(i).startTime(); if (t < 0) continue; if (firstTime < 0 || t < firstTime) { firstTime = t; firstName = proc(i).name(); } if (lastTime < 0 || lastTime < t) { lastTime = t; lastName = proc(i).name(); } } const Time diff = lastTime - firstTime; if (diff > Time::Sec(5*60)) { cerr << "warning: " << name() << "-side processes were started" " with a " << diff << " gap" << endl; cerr << "\tfirst process to start: " << firstName; HttpDatePrint(cerr << " at ", firstTime); cerr << "\tlast process to start: " << lastName; HttpDatePrint(cerr << " at ", lastTime); } theStartTime = firstTime; // regardless of the diff? } void SideInfo::checkCommonPhases() { if (procCount()) { const ProcInfo &p = proc(0); bool mismatch = false; for (int i = 1; i < procCount(); ++i) { if (p.phaseCount() != proc(i).phaseCount()) { mismatch = true; cerr << p.name() << " has " << p.phaseCount() << " phases" << " while " << proc(i).name() << " has " << proc(i).phaseCount() << endl; } const int pCount = Min(p.phaseCount(), proc(i).phaseCount()); for (int n = 0; n < pCount; ++n) { if (p.phase(n).name() != proc(i).phase(n).name()) { mismatch = true; cerr << "phase " << n << " in " << p.name() << " is named " << p.phase(n).name() << " while" << " phase " << n << " in " << proc(i).name() << " is named " << proc(i).phase(n).name() << endl; } } } if (mismatch) { cerr << "phase mismatch detected; any report information based" << " on phase aggregation is likely to be wrong" << endl; } } } void SideInfo::checkConsistency() { for (int i = 0; i < procCount(); ++i) proc(i).checkConsistency(); checkCommonBenchmarkVersion(); checkCommonPglCfg(); checkCommonStartTime(); checkCommonPhases(); } void SideInfo::CompileEmptyStats(BlobDb &db, const Scope &scope) { static const String tlTitle = "side stats"; ReportBlob blob(BlobDb::Key("summary", scope), tlTitle); XmlParagraph para; XmlText text; text.buf() << "no side information was extracted from the logs"; para << text; blob << para; db << blob; } void SideInfo::compileStats(BlobDb &db) { clog << "compiling statistics for the " << name() << " side" << endl; for (int i = 0; i < theProcs.count(); ++i) { theProcs[i]->compileStats(db); theExecScopePhase.merge(theProcs[i]->execScopePhase()); theAllPhasesPhase.merge(theProcs[i]->allPhasesPhase()); } bool gotExecScope = false; for (int i = 0; i < phaseCount(); ++i) { PhaseInfo &phase = *thePhases[i]; Scope phScope = scope().onePhase(phase.name()); phScope.name(phase.name()); gotExecScope = gotExecScope || phScope.image() == execScope().image(); compileStats(db, phase, phScope); } if (!gotExecScope) compileStats(db, theExecScopePhase, execScope()); // else should copy existing phase(i) stats? compileStats(db, theAllPhasesPhase, theScope); cmplSideSum(db); } void SideInfo::compileStats(BlobDb &db, const PhaseInfo &phase, const Scope &scope) { const String sfx = BlobDb::KeySuffix(scope); const StatIntvlRec &stats = phase.availStats(); clog << "\t scope: " << '"' << scope.name() << '"' << endl; if (!phase.hasStats()) clog << "\t\twarning: no phase statistics stored in this scope" << endl; addMeasBlob(db, "xact.count" + sfx, stats.xactCnt(), "xact", "transaction count"); addMeasBlob(db, "xact.error.count" + sfx, stats.theXactErrCnt, "xact", "erroneous xaction count"); addMeasBlob(db, "xact.error.ratio" + sfx, stats.errPercent(), "%", "portion of erroneous transactions"); addMeasBlob(db, "duration" + sfx, stats.theDuration, "test duration"); addMeasBlob(db, "offered.hit.ratio.obj" + sfx, stats.theIdealHR.dhp(), "%", "offered document hit ratio"); addMeasBlob(db, "offered.hit.ratio.byte" + sfx, stats.theIdealHR.bhp(), "%", "offered byte hit ratio"); addMeasBlob(db, "hit.ratio.obj" + sfx, stats.theRealHR.dhp(), "%", "measured document hit ratio"); addMeasBlob(db, "hit.ratio.byte" + sfx, stats.theRealHR.bhp(), "%", "measured byte hit ratio"); addMeasBlob(db, "req.rate" + sfx, stats.reqRate(), "xact/sec", "offered request rate"); addMeasBlob(db, "rep.rate" + sfx, stats.repRate(), "xact/sec", "measured response rate"); addMeasBlob(db, "req.bwidth" + sfx, stats.reqRate()*stats.repSize().mean()/(1024*1024/8), "Mbits/sec", "request bandwidth"); addMeasBlob(db, "rep.bwidth" + sfx, stats.repBwidth()/(1024*1024/8), "Mbits/sec", "response bandwidth"); addMeasBlob(db, "rep.rptm.mean" + sfx, Time::Secd(stats.repTime().mean()/1000.), "mean response time"); addMeasBlob(db, "conn.count" + sfx, stats.theConnUseCnt.count(), "conn", "connection count"), addMeasBlob(db, "conn.pipeline.count" + sfx, stats.theConnPipelineDepth.count(), "conn", "pipelined connection count"); addMeasBlob(db, "conn.pipeline.ratio" + sfx, Percent(stats.theConnPipelineDepth.count(), stats.theConnUseCnt.count()), "%", "portion of pipelined connections"); addMeasBlob(db, "conn.pipeline.depth.min" + sfx, stats.theConnPipelineDepth.min(), "xact/pipe", "minimum transactions in pipeline"); addMeasBlob(db, "conn.pipeline.depth.max" + sfx, stats.theConnPipelineDepth.max(), "xact/pipe", "maximum transactions in pipeline"); addMeasBlob(db, "conn.pipeline.depth.mean" + sfx, stats.theConnPipelineDepth.mean(), "xact/pipe", "mean transactions in pipeline"); cmplLoadBlob(db, scope); cmplRptmFigure(db, scope); cmplRptmVsLoadFigure(db, phase, scope); cmplHitRatioTable(db, scope); cmplXactLevelTable(db, phase, scope); cmplConnLevelTable(db, phase, scope); cmplConnPipelineBlob(db, scope); cmplPopulLevelTable(db, phase, scope); cmplStreamTable(db, phase, scope); cmplObjectTable(db, phase, scope); cmplErrorTable(db, phase, scope); cmplObjectBlobs(db, phase, scope); } void SideInfo::cmplLoadBlob(BlobDb &db, const Scope &scope) { ReportBlob blob(BlobDb::Key("load", scope), ReportBlob::NilTitle); blob << XmlAttr("vprimitive", "Load"); cmplLoadTable(db, blob, scope); cmplLoadFigure(db, blob, scope); { XmlTag descr("description"); XmlTextTag p1; p1.buf() << "The load table shows offered and measured load from " << name() << " side point of view. Offered load statistics " << "are based on the request stream. Measured load statistics " << "are based on reply messages. The 'count' column depicts the " << "number of requests or responses. "; descr << p1; XmlTextTag p2; p2.buf() << "The 'volume' column is a little bit more tricky to " << "interpret. Offered volume is " << "reply bandwidth that would have been required to support " << "offered load. This volume is computed as request rate " << "multiplied by measured mean response size. " << "Measured volume is the actual or measured reply bandwidth."; descr << p2; blob << descr; } db << blob; } void SideInfo::cmplLoadTable(BlobDb &db, ReportBlob &parent, const Scope &scope) { ReportBlob blob(BlobDb::Key("load.table", scope), name() + " load table"); XmlTable table; table << XmlAttr::Int("border", 1) << XmlAttr::Int("cellspacing", 1); { XmlTableRec tr; tr << XmlTableHeading("Load"); XmlTableHeading dhr("Count"); dhr << XmlTag("br") << XmlText("(xact/sec)"); tr << dhr; XmlTableHeading bhr("Volume"); bhr << XmlTag("br") << XmlText("(Mbits/sec)"); tr << bhr; table << tr; } { XmlTableRec tr; tr << algnLeft << XmlTableHeading("offered"); XmlTableCell cnt; cnt << algnRight << db.quote("req.rate" + scope); tr << cnt; XmlTableCell vol; vol << algnRight << db.quote("req.bwidth" + scope); tr << vol; table << tr; } { XmlTableRec tr; tr << algnLeft << XmlTableHeading("measured"); XmlTableCell cnt; cnt << algnRight << db.quote("rep.rate" + scope); tr << cnt; XmlTableCell vol; vol << algnRight << db.quote("rep.bwidth" + scope); tr << vol; table << tr; } blob << table; db << blob; parent << blob; } void SideInfo::cmplLoadFigure(BlobDb &db, ReportBlob &blob, const Scope &scope) { SideLoadStex stex1("req", "offered", &StatIntvlRec::reqRate, &StatIntvlRec::reqBwidth); SideLoadStex stex2("rep", "measured", &StatIntvlRec::repRate, &StatIntvlRec::repBwidth); LoadTraceFig fig; fig.configure("load.trace" + scope, "load trace"); fig.stats(&stex1, &stex2, &phase(scope)); fig.globalStart(theTest->startTime()); const String &figKey = fig.plot(db).key(); blob << db.include(figKey); } void SideInfo::cmplRptmFigure(BlobDb &db, const Scope &scope) { MissesStex misses("misses", "misses"); HitsStex hits("hits", "hits"); RptmTraceFig fig; fig.configure("rptm.trace" + scope, "Response times trace"); fig.stats(&misses, &phase(scope)); fig.moreStats(TheAllReps); fig.moreStats(&hits); fig.globalStart(theTest->startTime()); //const String &figKey = fig.plot(db).key(); //blob << db.include(figKey); } void SideInfo::cmplRptmVsLoadFigure(BlobDb &db, const PhaseInfo &phase, const Scope &scope) { ReportBlob blob("rptm-load" + scope, "mean response time versus response rate"); blob << XmlAttr("vprimitive", "Mean response time versus response rate"); LoadPointStex load("rep", "response rate", "xact/sec", &StatIntvlRec::repRate); MeanRptmPointStex rptm; ScatteredFig fig; fig.configure("rptm-load.scatt" + scope, "Mean response time versus response rate"); fig.stats(&load, &rptm, &phase); const String &figKey = fig.plot(db).key(); blob << db.include(figKey); db << blob; } void SideInfo::cmplHitRatioTable(BlobDb &db, const Scope &scope) { ReportBlob blob("hit.ratio" + scope, "hit ratios"); blob << XmlAttr("vprimitive", "Hit Ratios"); XmlTable table; table << XmlAttr::Int("border", 1) << XmlAttr::Int("cellspacing", 1); { XmlTableRec tr; tr << XmlTableHeading("Hit Ratios"); XmlTableHeading dhr("DHR"); dhr << XmlTag("br") << XmlText("(%)"); tr << dhr; XmlTableHeading bhr("BHR"); bhr << XmlTag("br") << XmlText("(%)"); tr << bhr; table << tr; } { XmlTableRec tr; tr << algnLeft << XmlTableHeading("offered"); XmlTableCell dhr; dhr << algnRight << db.include("offered.hit.ratio.obj" + scope); tr << dhr; XmlTableCell bhr; bhr << algnRight << db.include("offered.hit.ratio.byte" + scope); tr << bhr; table << tr; } { XmlTableRec tr; tr << algnLeft << XmlTableHeading("measured"); XmlTableCell dhr; dhr << algnRight << db.include("hit.ratio.obj" + scope); tr << dhr; XmlTableCell bhr; bhr << algnRight << db.include("hit.ratio.byte" + scope); tr << bhr; table << tr; } blob << table; { XmlTag descr("description"); if (name() == "client") { XmlTextTag p1; p1.buf() << "The hit ratios table shows offered and measured hit " << "ratios from " << name() << " side point of view. " << "Polygraph counts every repeated request to a cachable " << "object as an offered hit. Measured (cache) hits are " << "detected using Polygraph-specific headers. All hits are " << "counted for 'basic' transactions only (simple HTTP GET " << "requests with '200 OK' responses)."; descr << p1; XmlTextTag p2; p2.buf() << "DHR, Document Hit Ratio, is the ratio of the total " << "number of hits to the number of all basic transactions " << "(hits and misses). BHR, Byte Hit Ratio, is the ratio of " << "the total volume (a sum of response sizes) of hits to the " << "total volume of all basic transactions."; descr << p2; } else { XmlTextTag p1; p1.buf() << "The server-side hit ratios should always be zero. " << "If a request reaches a server, it is, by definition, " << "a miss."; descr << p1; } XmlParagraph p; Scope testScope = scope.oneSide("client"); testScope.addSide("server"); p << XmlText("A better way to measure hit ratio is to compare " "client- and server-side traffic. A hit ratio table " "based on such a comparison is available "); p << db.ptr("hit.ratio" + testScope, XmlText("elsewhere")); p << XmlText("."); descr << p; blob << descr; } cmplHrTraces(db, blob, scope); db << blob; } void SideInfo::cmplHrTraces(BlobDb &db, ReportBlob &blob, const Scope &scope) { cmplDhrTrace(db, blob, scope); cmplBhrTrace(db, blob, scope); } void SideInfo::cmplDhrTrace(BlobDb &db, ReportBlob &blob, const Scope &scope) { DhpPointStex stex1("dhp.ideal", "offered DHR", &StatIntvlRec::theIdealHR); DhpPointStex stex2("dhp.real", "measured DHR", &StatIntvlRec::theRealHR); PointTraceFig fig; fig.configure("dhr.trace" + scope, "Document hit ratio trace"); fig.stats(&stex1, &stex2, &phase(scope)); fig.globalStart(theTest->startTime()); const String &figKey = fig.plot(db).key(); blob << db.include(figKey); } void SideInfo::cmplBhrTrace(BlobDb &db, ReportBlob &blob, const Scope &scope) { BhpPointStex stex1("bhp.ideal", "offered BHR", &StatIntvlRec::theIdealHR); BhpPointStex stex2("bhp.real", "measured BHR", &StatIntvlRec::theRealHR); PointTraceFig fig; fig.configure("bhr.trace" + scope, "Byte hit ratio trace"); fig.stats(&stex1, &stex2, &phase(scope)); fig.globalStart(theTest->startTime()); const String &figKey = fig.plot(db).key(); blob << db.include(figKey); } void SideInfo::cmplConnLevelTable(BlobDb &db, const PhaseInfo &phase, const Scope &scope) { ReportBlob blob("conn.level.table" + scope, "concurrent connection level"); blob << XmlAttr("vprimitive", "Concurrent HTTP/TCP connection level table"); const StatIntvlRec &stats = phase.availStats(); XmlTable table; table << XmlAttr::Int("border", 1) << XmlAttr::Int("cellspacing", 1); { XmlTableRec tr; tr << XmlTableHeading("Connection state", 1, 2); tr << XmlTableHeading("Number of times", 2, 1); tr << XmlTableHeading("Mean concurrency level", 1, 2); table << tr; } { XmlTableRec tr; tr << XmlTableHeading("entered"); tr << XmlTableHeading("left"); table << tr; } cmplLevelTableRec(db, "conn.open.", "open", stats.theOpenLvl, scope, table); cmplLevelTableRec(db, "conn.estb.", "established", stats.theEstbLvl, scope, table); blob << table; { XmlTag descr("description"); XmlTextTag p1; p1.buf() << "TBD."; descr << p1; blob << descr; } // XXX: move cmplConnLevelFigure(db, blob, scope); db << blob; } void SideInfo::cmplConnLevelFigure(BlobDb &db, ReportBlob &blob, const Scope &scope) { LevelStex stex1("open", "open", &StatIntvlRec::theOpenLvl); LevelStex stex2("estb", "established", &StatIntvlRec::theEstbLvl); LevelTraceFig fig; fig.configure("conn.level.trace" + scope, "concurrent HTTP/TCP connection level trace"); fig.stats(&stex1, &stex2, &phase(scope)); fig.globalStart(theTest->startTime()); const String &figKey = fig.plot(db).key(); blob << db.include(figKey); } void SideInfo::cmplConnPipelineBlob(BlobDb &db, const Scope &scope) { ReportBlob blob(BlobDb::Key("pipeline", scope), "Pipelined HTTP connections"); blob << XmlAttr("vprimitive", "Pipelined HTTP connections"); cmplConnPipelineTable(db, blob, scope); cmplConnPipelineHist(db, blob, scope); cmplConnPipelineTrace(db, blob, scope); { XmlTag descr("description"); XmlTextTag p1; p1.buf() << "Connection pipelining stats are based on measurements " << "collected for pipelined HTTP connections. To calculate " << "pipelining probability, a connection is counted as pipelined " << "if it had pipelined (concurrent) requests " << "pending at any given moment of its lifetime."; descr << p1; XmlTextTag p2; p2.buf() << "The pipeline " << "depth varies as new requests are added to the connection and " << "old requests are satisfied by responses. The depth reported " << "her is based on the maximum pipelining depth achieved during " << "a pipelined connection lifetime. That is, the depth stats are " << "collected everytime a pipelined connection is closed, not " << "when a new request is added to or removed from the pipe."; descr << p2; blob << descr; } db << blob; } void SideInfo::cmplConnPipelineTable(BlobDb &db, ReportBlob &parent, const Scope &scope) { ReportBlob blob("conn.pipeline.table" + scope, "HTTP pipelining summary table"); XmlTable table; table << XmlAttr::Int("border", 0) << XmlAttr::Int("cellspacing", 1); { XmlTableRec tr; tr << algnLeft << XmlTableHeading("probability:"); XmlTableCell cell; cell << db.include("conn.pipeline.ratio" + scope); cell << XmlText(" or "); cell << db.include("conn.pipeline.count" + scope); cell << XmlText(" pipelined out of total "); cell << db.include("conn.count" + scope); tr << cell; table << tr; } if (phase(scope).availStats().theConnPipelineDepth.known()) { XmlTableRec tr; tr << algnLeft << XmlTableHeading("depth:"); XmlTableCell cell; cell << db.include("conn.pipeline.depth.min" + scope); cell << XmlText(" min, "); cell << db.include("conn.pipeline.depth.mean" + scope); cell << XmlText(" mean, and "); cell << db.include("conn.pipeline.depth.max" + scope); cell << XmlText(" max"); tr << cell; table << tr; } blob << table; db << blob; parent << blob; } void SideInfo::cmplConnPipelineTrace(BlobDb &db, ReportBlob &blob, const Scope &scope) { PipelineProbPointStex stex1; MeanAggrPointStex stex2("depth", "pipeline depth", "connections", &StatIntvlRec::theConnPipelineDepth); PointTraceFig fig; fig.configure("conn.pipeline.trace" + scope, "HTTP pipelining trace"); fig.stats(&stex1, &stex2, &phase(scope)); fig.globalStart(theTest->startTime()); const String &figKey = fig.plot(db).key(); blob << db.include(figKey); } void SideInfo::cmplConnPipelineHist(BlobDb &db, ReportBlob &blob, const Scope &scope) { PipelineDepthHistStex stex1; HistogramFigure fig; fig.configure("conn.pipeline.depth.histogram" + scope, "HTTP pipelining depth histogram"); fig.stats(&stex1, &phase(scope)); const String &figKey = fig.plot(db).key(); blob << db.include(figKey); } void SideInfo::cmplPopulLevelTable(BlobDb &db, const PhaseInfo &phase, const Scope &scope) { ReportBlob blob("populus.level.table" + scope, "population level"); blob << XmlAttr("vprimitive", "Population level table"); const StatIntvlRec &stats = phase.availStats(); XmlTable table; table << XmlAttr::Int("border", 1) << XmlAttr::Int("cellspacing", 1); { XmlTableRec tr; tr << XmlTableHeading("Number of agents", 2, 1); tr << XmlTableHeading("Mean population level", 1, 2); table << tr; } { XmlTableRec tr; tr << XmlTableHeading("created"); tr << XmlTableHeading("destroyed"); table << tr; } cmplLevelTableRec(db, "agent.", 0, stats.thePopulusLvl, scope, table); blob << table; { XmlTag descr("description"); XmlTextTag p1; p1.buf() << "Populus is a set of all live robot or server agents. " << "While alive, an agent may participate in HTTP transactions " << "or remain idle."; descr << p1; blob << descr; } // XXX: move cmplPopulLevelFigure(db, blob, scope); db << blob; } void SideInfo::cmplPopulLevelFigure(BlobDb &db, ReportBlob &blob, const Scope &scope) { LevelStex stex1("populus", "agents", &StatIntvlRec::thePopulusLvl); LevelTraceFig fig; fig.configure("populus.level.trace" + scope, "population level trace"); fig.stats(&stex1, 0, &phase(scope)); fig.globalStart(theTest->startTime()); const String &figKey = fig.plot(db).key(); blob << db.include(figKey); } void SideInfo::cmplXactLevelTable(BlobDb &db, const PhaseInfo &phase, const Scope &scope) { ReportBlob blob("xact.level.table" + scope, "concurrent transaction level"); blob << XmlAttr("vprimitive", "Concurrent HTTP transaction level table"); const StatIntvlRec &stats = phase.availStats(); XmlTable table; table << XmlAttr::Int("border", 1) << XmlAttr::Int("cellspacing", 1); { XmlTableRec tr; tr << XmlTableHeading("Transaction state", 1, 2); tr << XmlTableHeading("Number of times", 2, 1); tr << XmlTableHeading("Mean concurrency level", 1, 2); table << tr; } { XmlTableRec tr; tr << XmlTableHeading("entered"); tr << XmlTableHeading("left"); table << tr; } cmplLevelTableRec(db, "xact.", "active", stats.theXactLvl, scope, table); cmplLevelTableRec(db, "wait.", "waiting", stats.theWaitLvl, scope, table); blob << table; { XmlTag descr("description"); XmlTextTag p1; p1.buf() << "TBD."; descr << p1; blob << descr; } // XXX: move cmplXactLevelFigure(db, blob, scope); db << blob; } void SideInfo::cmplXactLevelFigure(BlobDb &db, ReportBlob &blob, const Scope &scope) { LevelStex stex1("xact", "active", &StatIntvlRec::theXactLvl); LevelStex stex2("wait", "waiting", &StatIntvlRec::theWaitLvl); LevelTraceFig fig; fig.configure("xact.level.trace" + scope, "concurrent HTTP transaction level trace"); fig.stats(&stex1, &stex2, &phase(scope)); fig.globalStart(theTest->startTime()); const String &figKey = fig.plot(db).key(); blob << db.include(figKey); } void SideInfo::cmplLevelTableRec(BlobDb &db, const String &pfx, const String &state, const LevelStat &stats, const Scope &scope, XmlTable &table) { XmlTableRec tr; if (state) tr << algnLeft << XmlTableHeading(state); const String startedName = pfx + "started" + scope; addMeasBlob(db, startedName, stats.incCnt(), "", "started"); XmlTableCell started; started << algnRight << db.include(startedName); tr << started; const String finishedName = pfx + "finished" + scope; addMeasBlob(db, finishedName, stats.decCnt(), "", "finished"); XmlTableCell finished; finished << algnRight << db.include(finishedName); tr << finished; const String levelName = pfx + "level.mean" + scope; addMeasBlob(db, levelName, stats.mean(), "", "average level"); XmlTableCell level; level << algnRight << db.include(levelName); tr << level; table << tr; } void SideInfo::cmplStreamTable(BlobDb &db, const PhaseInfo &phase, const Scope &scope) { ReportBlob blob("stream.table" + scope, "traffic stream"); blob << XmlAttr("vprimitive", "Traffic stream table"); XmlTable table; table << XmlAttr::Int("border", 1) << XmlAttr::Int("cellspacing", 1); { XmlTableRec tr; tr << XmlTableHeading("Stream", 1, 2); tr << XmlTableHeading("Contribution", 2, 1); tr << XmlTableHeading("Rates", 2, 1); tr << XmlTableHeading("Totals", 2, 1); table << tr; } { XmlTableRec tr; XmlTableHeading cCnt("Count"); cCnt << XmlTag("br") << XmlText("(%)"); tr << cCnt; XmlTableHeading cVol("Volume"); cVol << XmlTag("br") << XmlText("(%)"); tr << cVol; XmlTableHeading rCnt("Count"); rCnt << XmlTag("br") << XmlText("(xact/sec)"); tr << rCnt; XmlTableHeading rVol("Volume"); rVol << XmlTag("br") << XmlText("(Mbits/sec)"); tr << rVol; XmlTableHeading tCnt("Count"); tCnt << XmlTag("br") << XmlText("(xact,M)"); tr << tCnt; XmlTableHeading tVol("Volume"); tVol << XmlTag("br") << XmlText("(Gbyte)"); tr << tVol; table << tr; } for (int s = 0; s < TheStex.count(); ++s) cmplStreamTableRec(db, table, *TheStex[s], phase, scope); blob << table; { XmlTag descr("description"); XmlTextTag p1; p1.buf() << "The 'Stream' table provides count and volume " << "statistics for many classes of transactions and for " << "so-called pages. The " << "'Contribution' columns show count- and volume-based " << "portions of all transactions. The 'Rates' columns show " << "throughput and bandwidth measurements. The 'Totals' " << "columns contain the total number of transactions " << "and the total volume (a sum of individual response " << "sizes) for each stream."; descr << p1; XmlTextTag p2; p2.buf() << "Note that some streams are a combination of other " << "streams. For example, the 'all ims' stream contains " << "transactions with If-Modified-Since requests that resulted in " << "either '200 OK' (the 'ims/304' stream) or " << "'304 Not Modified' (the 'ims/304' stream) responses. "; descr << p2; XmlTextTag p3; p3.buf() << "Many combination streams, such as 'all content types' " << "or 'hits and misses' stream, contribute less than 100% " << "because properties like content type or hit status are " << "distinguished for 'basic' transactions only. A basic " << "transactions is a simple HTTP GET request resulted in " << "a '200 OK' response. Various special transactions such " << "as IMS or aborts do not belong to the 'basic' category."; descr << p3; XmlParagraph p4; p4 << XmlText("The "); p4 << db.ptr("object.table" + scope, XmlText("'Object' table")); p4 << XmlText(" contains corresponding response time and size " "statistics for streams."); descr << p4; blob << descr; } db << blob; } // addMeasBlob() calls should be moved out if we want to support partial reports void SideInfo::cmplStreamTableRec(BlobDb &db, XmlTable &table, const Stex &stex, const PhaseInfo &phase, const Scope &scope) { const String pfx = "stream." + stex.key(); const String ratioCountName = BlobDb::Key(pfx + ".ratio.obj", scope); const String ratioVolumeName = BlobDb::Key(pfx + ".ratio.byte", scope); const String rateCountName = BlobDb::Key(pfx + ".rate", scope); const String rateVolumeName = BlobDb::Key(pfx + ".bwidth", scope); const String totalCountName = BlobDb::Key(pfx + ".size.count", scope); const String totalVolumeName = BlobDb::Key(pfx + ".size.sum", scope); const String ratioCountTitle = "contribution by count"; const String ratioVolumeTitle = "contribution by volume"; const String rateCountTitle = "transaction rate"; const String rateVolumeTitle = "transaction bandwidth"; const String totalCountTitle = "total transaction count"; const String totalVolumeTitle = "total transaction volume"; if (const TmSzStat *recStats = stex.aggr(phase)) { const AggrStat &cstats = recStats->size(); const Time duration = phase.availStats().theDuration; const double rateCountVal = Ratio(cstats.count(), duration.secd()); const double rateVolumeVal = Ratio(cstats.sum()/1024/1024*8, duration.secd()); const double totalCountVal = cstats.count(); const double totalVolumeVal = cstats.sum(); if (stex.parent() || &stex == TheAllReps) { // compute contribution towards "all responses" const AggrStat all = phase.availStats().reps().size(); addMeasBlob(db, ratioCountName, Percent(totalCountVal, all.count()), "%", ratioCountTitle); addMeasBlob(db, ratioVolumeName, Percent(totalVolumeVal, all.sum()), "%", ratioVolumeTitle); } else { addNaMeasBlob(db, ratioCountName, ratioCountTitle); addNaMeasBlob(db, ratioVolumeName, ratioVolumeTitle); } addMeasBlob(db, rateCountName, rateCountVal, "/sec", rateCountTitle); addMeasBlob(db, rateVolumeName, rateVolumeVal, "Mbits/sec", rateVolumeTitle); addMeasBlob(db, totalCountName, totalCountVal/1e6, "M", totalCountTitle); addMeasBlob(db, totalVolumeName, totalVolumeVal/(1024*1024*1024), "GByte", totalVolumeTitle); } else { addNaMeasBlob(db, ratioCountName, ratioCountTitle); addNaMeasBlob(db, ratioVolumeName, ratioVolumeTitle); addNaMeasBlob(db, rateCountName, rateCountTitle); addNaMeasBlob(db, rateVolumeName, rateVolumeTitle); addNaMeasBlob(db, totalCountName, totalCountTitle); addNaMeasBlob(db, totalVolumeName, totalVolumeTitle); } XmlTableRec tr; XmlTableHeading th(stex.name()); th << algnLeft; tr << th; XmlTableCell ratioCountCell; ratioCountCell << algnRight << db.quote(ratioCountName); tr << ratioCountCell; XmlTableCell ratioVolumeCell; ratioVolumeCell << algnRight << db.quote(ratioVolumeName); tr << ratioVolumeCell; XmlTableCell rateCountCell; rateCountCell << algnRight << db.quote(rateCountName); tr << rateCountCell; XmlTableCell rateVolumeCell; rateVolumeCell << algnRight << db.quote(rateVolumeName); tr << rateVolumeCell; XmlTableCell totalCountCell; totalCountCell << algnRight << db.quote(totalCountName); tr << totalCountCell; XmlTableCell totalVolumeCell; totalVolumeCell << algnRight << db.quote(totalVolumeName); tr << totalVolumeCell; table << tr; } void SideInfo::cmplObjectTable(BlobDb &db, const PhaseInfo &phase, const Scope &scope) { { ReportBlob blob(BlobDb::Key("object.table", scope), "response kind stats"); blob << XmlAttr("vprimitive", "Object kind table"); XmlTable table; table << XmlAttr::Int("border", 1) << XmlAttr::Int("cellspacing", 1); { XmlTableRec tr; tr << XmlTableHeading("Object", 1, 2); tr << XmlTableHeading("Response time (msec)", 3, 1); tr << XmlTableHeading("Size (KBytes)", 3, 1); table << tr; } { XmlTableRec tr; XmlNodes nodes; nodes << XmlTableHeading("Min"); nodes << XmlTableHeading("Mean"); nodes << XmlTableHeading("Max"); tr << nodes; tr << nodes; table << tr; } for (int s = 0; s < TheStex.count(); ++s) cmplObjectTableRec(db, table, *TheStex[s], phase, scope); blob << table; { XmlTag descr("description"); XmlTextTag p1; p1.buf() << "The 'Object' table provides response time and response " << "size statistics for many classes of transactions and " << "for so-called pages."; descr << p1; XmlTextTag p2; p2.buf() << "Note that some classes are a combination of other " << "classes. For example, the 'all ims' class contains " << "transactions with If-Modified-Since requests that resulted in " << "either '200 OK' (the 'ims/304' class) or " << "'304 Not Modified' (the 'ims/304' class) responses. "; descr << p2; XmlParagraph p3; p3 << XmlText("Some statistics may not be available because either " "no objects of the corresponding class were seen during the " "test or no facilities to collect the stats exist for " "the class. The former can be verified using a "); p3 << db.ptr("stream.table" + scope, XmlText("'Stream' table")); p3 << XmlText("."); descr << p3; blob << descr; } db << blob; } } void SideInfo::cmplObjectTableRec(BlobDb &db, XmlTable &table, const Stex &stex, const PhaseInfo &phase, const Scope &scope) { XmlTableRec tr; const String pfx = "object." + stex.key(); XmlTableHeading th; th << db.ptr(pfx + scope, XmlText(stex.name())); th << algnLeft; tr << th; const TmSzStat *cstats = stex.aggr(phase); { const String rptmMinName = BlobDb::Key(pfx + ".rptm.min", scope); const String rptmMeanName = BlobDb::Key(pfx + ".rptm.mean", scope); const String rptmMaxName = BlobDb::Key(pfx + ".rptm.max", scope); const String rptmMinTitle = "minimum response time"; const String rptmMeanTitle = "mean response time"; const String rptmMaxTitle = "maximum response time"; if (cstats && cstats->time().count() > 0) { const AggrStat &time = cstats->time(); addMeasBlob(db, rptmMinName, time.min(), "msec", rptmMinTitle); addMeasBlob(db, rptmMeanName, time.mean(), "msec", rptmMeanTitle); addMeasBlob(db, rptmMaxName, time.max(), "msec", rptmMaxTitle); } else { addNaMeasBlob(db, rptmMinName, rptmMinTitle); addNaMeasBlob(db, rptmMeanName, rptmMeanTitle); addNaMeasBlob(db, rptmMaxName, rptmMaxTitle); } XmlTableCell rptmMinCell; rptmMinCell << algnRight << db.quote(rptmMinName); tr << rptmMinCell; XmlTableCell rptmMeanCell; rptmMeanCell << algnRight << db.quote(rptmMeanName); tr << rptmMeanCell; XmlTableCell rptmMaxCell; rptmMaxCell << algnRight << db.quote(rptmMaxName); tr << rptmMaxCell; } { const String sizeMinName = BlobDb::Key(pfx + ".size.min", scope); const String sizeMeanName = BlobDb::Key(pfx + ".size.mean", scope); const String sizeMaxName = BlobDb::Key(pfx + ".size.max", scope); const String sizeMinTitle = "minimum size"; const String sizeMeanTitle = "mean size"; const String sizeMaxTitle = "maximum size"; if (cstats && cstats->size().count() > 0) { const AggrStat &size = cstats->size(); addMeasBlob(db, sizeMinName, size.min()/1024., "KBytes", sizeMinTitle); addMeasBlob(db, sizeMeanName, size.mean()/1024., "KBytes", sizeMeanTitle); addMeasBlob(db, sizeMaxName, size.max()/1024., "KBytes", sizeMaxTitle); } else { addNaMeasBlob(db, sizeMinName, sizeMinTitle); addNaMeasBlob(db, sizeMeanName, sizeMeanTitle); addNaMeasBlob(db, sizeMaxName, sizeMaxTitle); } XmlTableCell sizeMinCell; sizeMinCell << algnRight << db.quote(sizeMinName); tr << sizeMinCell; XmlTableCell sizeMeanCell; sizeMeanCell << algnRight << db.quote(sizeMeanName); tr << sizeMeanCell; XmlTableCell sizeMaxCell; sizeMaxCell << algnRight << db.quote(sizeMaxName); tr << sizeMaxCell; } table << tr; } void SideInfo::cmplErrorTable(BlobDb &db, const PhaseInfo &phase, const Scope &scope) { ReportBlob blob(BlobDb::Key("errors.table", scope), "error stats"); blob << XmlAttr("vprimitive", "Errors"); ErrorStat::Index idx; if (phase.hasStats() && phase.stats().theErrors.index(idx)) { XmlParagraph p; XmlText text; text.buf() << "The total of " << phase.stats().theErrors.count() << " errors detected. Out of those errors, "; p << text << db.include("xact.error.count" + scope); p << XmlText(" or ") << db.include("xact.error.ratio" + scope); p << XmlText(" of all transactions were classified as transaction errors."); blob << p; XmlTable table; table << XmlAttr::Int("border", 1) << XmlAttr::Int("cellspacing", 1); { XmlTableRec tr; tr << XmlTableHeading("Error"); tr << XmlTableHeading("Count"); tr << XmlTableHeading("Contribution (%)"); table << tr; } for (int i = 0; i < idx.count(); ++i) cmplErrorTableRec(db, table, phase.stats().theErrors, *idx[i], scope); blob << table; { XmlTag descr("description"); XmlTextTag p1; p1.buf() << "The 'Errors' table shows detected errors. For each " << "error type, the number of errors and their contribution towards " << "total error count are shown."; descr << p1; blob << descr; } } else if (phase.hasStats()) { blob << XmlTextTag("No errors detected in the given scope."); } else { XmlParagraph p; p << XmlText("The total of ") << db.include("xact.error.count" + scope) << XmlText(" or ") << db.include("xact.error.ratio" + scope); p << XmlText(" transaction errors detected."); blob << p; } db << blob; } void SideInfo::cmplErrorTableRec(BlobDb &, XmlTable &table, const ErrorStat &errors, const ErrorRec &error, const Scope &) { XmlTableRec tr; XmlTableHeading th; XmlText tht; error.print(tht.buf()); th << algnLeft; th << tht; tr << th; XmlTableCell countCell; XmlText countText; countText.buf() << error.count(); countCell << algnRight << countText; tr << countCell; XmlTableCell contribCell; XmlText contribText; contribText.buf() << Percent(error.count(), errors.count()); contribCell << algnRight << contribText; tr << contribCell; table << tr; } void SideInfo::cmplObjectBlobs(BlobDb &db, const PhaseInfo &phase, const Scope &scope) { for (int s = 0; s < TheStex.count(); ++s) cmplObjectBlob(db, *TheStex[s], phase, scope); } const ReportBlob &SideInfo::cmplObjectBlob(BlobDb &db, const Stex &stex, const PhaseInfo &phase, const Scope &scope) { const String pfx = "object." + stex.key(); const String tlTitle = stex.name() + " stats"; ReportBlob blob(pfx + scope, tlTitle); blob << XmlAttr("vprimitive", String("Object '") + stex.name() + "'"); const TmSzStat *aggr = stex.aggr(phase); if (aggr && aggr->count()) { XmlTable table; table << XmlAttr::Int("border", 0); if (stex.parent() || &stex == TheAllReps) { XmlTableRec tr; tr << algnLeft << XmlTableHeading("contribution:"); XmlTableCell cell; cell << db.include("stream." + stex.key() + ".ratio.obj" + scope); cell << XmlText(" by count and "); cell << db.include("stream." + stex.key() + ".ratio.byte" + scope); cell << XmlText(" by volume"); tr << cell; table << tr; } { XmlTableRec tr; tr << algnLeft << XmlTableHeading("rates:"); XmlTableCell cell; cell << db.include("stream." + stex.key() + ".rate" + scope); cell << XmlText(" or "); cell << db.include("stream." + stex.key() + ".bwidth" + scope); tr << cell; table << tr; } { XmlTableRec tr; tr << algnLeft << XmlTableHeading("totals:"); XmlTableCell cell; cell << db.include("stream." + stex.key() + ".size.count" + scope); cell << XmlText(" and "); cell << db.include("stream." + stex.key() + ".size.sum" + scope); tr << cell; table << tr; } { XmlTableRec tr; tr << algnLeft << XmlTableHeading("response time:"); XmlTableCell cell; cell << db.include("object." + stex.key() + ".rptm.min" + scope); cell << XmlText(" min, "); cell << db.include("object." + stex.key() + ".rptm.mean" + scope); cell << XmlText(" mean, and "); cell << db.include("object." + stex.key() + ".rptm.max" + scope); cell << XmlText(" max"); tr << cell; table << tr; } { XmlTableRec tr; tr << algnLeft << XmlTableHeading("response size:"); XmlTableCell cell; cell << db.include("object." + stex.key() + ".size.min" + scope); cell << XmlText(" min, "); cell << db.include("object." + stex.key() + ".size.mean" + scope); cell << XmlText(" mean, and "); cell << db.include("object." + stex.key() + ".size.max" + scope); cell << XmlText(" max"); tr << cell; table << tr; } blob << table; if (stex.hist(phase)) { { RptmHistFig fig; fig.configure(pfx + ".rptm.fig" + scope, "response time distribution"); fig.stats(&stex, &phase); blob << db.include(fig.plot(db).key()); } { SizeHistFig fig; fig.configure(pfx + ".size.fig" + scope, "object size distribution"); fig.stats(&stex, &phase); blob << db.include(fig.plot(db).key()); } } else { blob << XmlTextTag("No response time and size " "histograms were collected or stored for this object class."); } if (stex.trace(phase.availStats())) { LoadTraceFig figLoad; TmSzLoadStex loadStex(&stex); figLoad.configure(pfx + ".load.trace" + scope, "load trace"); figLoad.stats(0, &loadStex, &this->phase(scope)); figLoad.globalStart(theTest->startTime()); const String &figLoadKey = figLoad.plot(db).key(); blob << db.include(figLoadKey); RptmTraceFig figRptm; figRptm.configure(pfx + ".rptm.trace" + scope, "response time trace"); figRptm.stats(&stex, &this->phase(scope)); figRptm.globalStart(theTest->startTime()); const String &figRptmKey = figRptm.plot(db).key(); blob << db.include(figRptmKey); } else { blob << XmlTextTag("No response time and size " "traces are collected for this object class."); } } else { blob << XmlTextTag("No instances of this " "object class were observed or recorded in the given scope."); } { XmlTag descr("description"); XmlNodes nodes; stex.describe(nodes); descr << nodes; blob << descr; } return *db.add(blob); } void SideInfo::cmplSideSum(BlobDb &db) { ReportBlob blob(BlobDb::Key("summary", execScope()), "test side summary"); blob << db.quote("load" + execScope()); blob << db.quote("hit.ratio" + execScope()); blob << db.quote("stream.table" + execScope()); blob << db.quote("object.table" + execScope()); db << blob; } void SideInfo::AddStex(Stex *stex, const Stex *parent) { Assert(stex); if (parent && Should(stex != parent)) stex->parent(parent); TheStex.append(stex); } void SideInfo::AddStex(Stex *stex) { AddStex(stex, stex == TheAllReps ? 0 : TheAllReps); } void SideInfo::ConfigureStex() { TheAllReps = new AllRepsStex("rep", "all replies"); Stex *allContTypes = new AllContTypesStex("cont_type_all", "all content types"); for (int i = 0; i < ContTypeStat::Kinds().count(); ++i) { char buf[128]; ofixedstream s(buf, sizeof(buf)); s << "cont_type_" << i << ends; const String key = buf; const String &cname = *ContTypeStat::Kinds()[i]; const String name = String("\"") + cname + '"'; if (cname[0] != '_') AddStex(new ContTypeStex(key, name, i), allContTypes); } AddStex(allContTypes); Stex *hitsAndMisses = new HitMissesStex("hits_and_misses", "hits and misses"); AddStex(new HitsStex("hits", "hits"), hitsAndMisses); AddStex(new MissesStex("misses", "misses"), hitsAndMisses); AddStex(hitsAndMisses); Stex *allIms = new ImsStex("ims_scAll", "all ims"); AddStex(new Ims200Stex("ims_sc200", "ims/200"), allIms); AddStex(new Ims304Stex("ims_sc304", "ims/304"), allIms); AddStex(allIms); Stex *allCachable = new AllCachableStex("all_cachable", "cachable and not"); AddStex(new CachableStex("cachable", "cachable"), allCachable); AddStex(new UnCachableStex("uncachable", "not cachable"), allCachable); AddStex(allCachable); AddStex(new FillStex("fill", "fill")); AddStex(new SimpleStex("reload", "reload", &StatPhaseRec::theReloadXacts, &StatIntvlRec::theReload)); AddStex(new SimpleStex("abort", "abort", 0, &StatIntvlRec::theAbort)); AddStex(new SimpleStex("redir_req", "redirected request", &StatPhaseRec::theRediredReqXacts, &StatIntvlRec::theRediredReq)); AddStex(new SimpleStex("rep_to_redir", "reply to redirect", &StatPhaseRec::theRepToRedirXacts, &StatIntvlRec::theRepToRedir)); Stex *allMethods = new AllMethodsStex("method_all", "all non-gets"); AddStex(new SimpleStex("method_head", "HEAD", &StatPhaseRec::theHeadXacts, &StatIntvlRec::theHead), allMethods); AddStex(new SimpleStex("method_post", "POST", &StatPhaseRec::thePostXacts, &StatIntvlRec::thePost), allMethods); AddStex(new SimpleStex("method_put", "PUT", &StatPhaseRec::thePutXacts, &StatIntvlRec::thePut), allMethods); AddStex(allMethods); AddStex(TheAllReps); AddStex(new SimpleStex("page", "page", &StatPhaseRec::thePageHist, &StatIntvlRec::thePage), 0); }