/* 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<Stex*> 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<XmlParagraph> 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<XmlParagraph> 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<XmlParagraph> 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<XmlParagraph> 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<XmlParagraph> 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<XmlParagraph> 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<XmlParagraph> 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<XmlParagraph> 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<XmlParagraph> 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<XmlParagraph> 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<XmlParagraph> 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<XmlParagraph> 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<XmlParagraph> 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<XmlParagraph> 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<XmlParagraph> 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<XmlParagraph> 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<XmlParagraph>("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<XmlParagraph>("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<XmlParagraph>("No response time and size "
"traces are collected for this object class.");
}
} else {
blob << XmlTextTag<XmlParagraph>("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);
}
syntax highlighted by Code2HTML, v. 0.9.1