/* Web Polygraph http://www.web-polygraph.org/ * (C) 2003-2006 The Measurement Factory * Licensed under the Apache License, Version 2.0 */ #include "pgl/pgl.h" #include "xstd/Rnd.h" #include "xstd/gadgets.h" #include "base/polyVersion.h" #include "xml/XmlAttr.h" #include "xml/XmlText.h" #include "xml/XmlParagraph.h" #include "xml/XmlTable.h" #include "xml/XmlSection.h" #include "pgl/PglPp.h" #include "pgl/PglCtx.h" #include "pgl/PglStaticSemx.h" #include "pgl/PglStringSym.h" #include "pgl/PglDistrSym.h" #include "pgl/AgentArrIter.h" #include "pgl/GoalSym.h" #include "pgl/PhaseSym.h" #include "pgl/MimeSym.h" #include "pgl/ContentSym.h" #include "pgl/PopDistr.h" #include "pgl/PopModelSym.h" #include "pgl/ServerSym.h" #include "pgl/RobotSym.h" struct PglParser {}; // stub for future use static ostream &addAnd(ostream &os) { if (os.tellp()) os << ", "; return os; } static ostream &addAnd(ostream &os, int idx, int count) { if (!idx) return os; if (idx == count-1) return os << (count == 2 ? " and " : ", and "); return os << ", "; } static ostream &addOneOrMany(ostream &os, int count, const String &oneStr, const char *manyStr = 0) { if (count == 1) return os << oneStr; if (manyStr) return os << manyStr; // XXX: 'h' only if 'sh' and 'zh'; return os << oneStr << (strchr("szjh", oneStr.last()) ? "es" : "s"); } static double phaseFactor(const Array &phases, int idx, PhaseSym::FactorFunc fbeg, PhaseSym::FactorFunc fend, bool checkBeg) { if (idx < 0) return 1; const PhaseSym *ph = phases[idx]; const PhaseSym::FactorFunc fcur = checkBeg ? fbeg : fend; double fact = 1; if ((ph->*fcur)(fact)) return fact; if (checkBeg) return phaseFactor(phases, idx-1, fbeg, fend, false); else return phaseFactor(phases, idx, fbeg, fend, true); } static void buildPhaseTable(XmlTable &table, const Array &phases) { static PhaseSym::FactorFunc funcs[6] = { &PhaseSym::populusFactorBeg, &PhaseSym::populusFactorEnd, &PhaseSym::recurFactorBeg, &PhaseSym::recurFactorEnd, &PhaseSym::specialMsgFactorBeg, &PhaseSym::specialMsgFactorEnd }; table << XmlAttr::Int("border", 1) << XmlAttr("bgcolor", "#FFFFFF"); XmlTableRec r1, r2, r3; r1 << XmlTableHeading("Phase", 1, 3) << XmlTableHeading("Factors (%)", 3*2, 1) << XmlTableHeading("Other", 1, 3); r2 << XmlTableHeading("Populus", 2, 1) << XmlTableHeading("Recurrence", 2, 1) << XmlTableHeading("Special Msgs", 2, 1); for (int c = 0; c < 3; ++c) { r3 << XmlTableHeading("beg") << XmlTableHeading("end"); } XmlTableHeader header; header << r1 << r2 << r3; header << XmlAttr("bgcolor", "#BBBBBB"); table << header; // we need higher precision if at least one cell needs it bool highPrec = false; {for (int i = 0; !highPrec && i < phases.count(); ++i) { for (int f = 0; !highPrec && f < 6; ++f) { const double x = 100*(f % 2 ? phaseFactor(phases, i, funcs[f-1], funcs[f], false) : phaseFactor(phases, i, funcs[f], funcs[f+1], true)); highPrec = floor(x) < floor(x + 0.999); } }} for (int i = 0; i < phases.count(); ++i) { XmlTableRec rec; const PhaseSym &phase = *phases[i]; rec << XmlTableHeading(phase.name()); {for (int f = 0; f < 6; ++f) { const double x = 100*(f % 2 ? phaseFactor(phases, i, funcs[f-1], funcs[f], false) : phaseFactor(phases, i, funcs[f], funcs[f+1], true)); XmlTextTag cell; cell << XmlAttr("align", "right"); if (highPrec) cell.buf() << x; else cell.buf() << (int)x; rec << cell; }} XmlTextTag cell; if (phase.rptmstat()) cell.buf() << addAnd << "rptmstat enabled"; bool ls = false; if (phase.logStats(ls)) cell.buf() << addAnd << (ls ? "" : "do not ") << "log stats"; bool wwf = false; if (phase.waitWssFreeze(wwf) && addAnd(cell.buf())) cell.buf() << addAnd << (wwf ? "" : "do not ") << "wait for WSS to freeze"; if (!cell.buf().tellp()) cell.buf() << " "; rec << cell; table << rec; } } static Time testDuration(const Array &phases, int &fixCount, int &varCount) { fixCount = varCount = 0; Time dur(0,0); for (int i = 0; i < phases.count(); ++i) { if (GoalSym *gs = phases[i]->goal()) { const Time d = gs->duration(); if (d >= 0) { fixCount++; dur += d; } } } varCount = phases.count() - fixCount; return dur; } static const char *changeStr(double prev, double cur) { return prev <= cur ? "increases" : "decreases"; } static void explainPhase(XmlTag §, const Array &phases, int idx) { const PhaseSym &phase = *phases[idx]; XmlTextTag p; const Time dur = phase.goal() ? phase.goal()->duration() : Time(); if (dur >= 0) p.buf() << "Phase \"" << phase.name() << "\" lasts for " << dur << ". "; else p.buf() << "Phase \"" << phase.name() << "\" does not have a time-based duration configured. "; double prev, cur; double d; prev = phaseFactor(phases, idx, &PhaseSym::populusFactorBeg, &PhaseSym::populusFactorEnd, true); cur = phaseFactor(phases, idx, &PhaseSym::populusFactorBeg, &PhaseSym::populusFactorEnd, false); if (!phase.populusFactorEnd(d)) { p.buf() << "During this phase, the robot population size remains " << "stable at " << (cur*100) << "% of its peak level. "; } else { p.buf() << "During this phase, the robot population size " << changeStr(prev, cur) << " from " << (prev*100) << "% to " << (cur*100) << "%. "; } prev = phaseFactor(phases, idx, &PhaseSym::loadFactorBeg, &PhaseSym::loadFactorEnd, true); cur = phaseFactor(phases, idx, &PhaseSym::loadFactorBeg, &PhaseSym::loadFactorEnd, false); if (!phase.loadFactorEnd(d)) { p.buf() << "The offered per-robot load remains " << "stable at " << (cur*100) << "% of its peak level. "; } else { p.buf() << "The offered per-robot load level " << changeStr(prev, cur) << " from " << (prev*100) << "% to " << (cur*100) << "%. "; } if (phase.rptmstat()) { p.buf() << "However, load level is also subject to response time " << "constraints since rptmstat controls are enabled. "; // XXX: report rptmstat config } prev = phaseFactor(phases, idx, &PhaseSym::recurFactorBeg, &PhaseSym::recurFactorEnd, true); cur = phaseFactor(phases, idx, &PhaseSym::recurFactorBeg, &PhaseSym::recurFactorEnd, false); if (!phase.recurFactorEnd(d)) { p.buf() << "The recurrence level remains " << "stable at " << (cur*100) << "% of robot recurrence ratios. "; } else { p.buf() << "The offered recurrence level " << changeStr(prev, cur) << " from " << (prev*100) << "% to " << (cur*100) << "% of robot recurrence ratios. "; } prev = phaseFactor(phases, idx, &PhaseSym::specialMsgFactorBeg, &PhaseSym::specialMsgFactorEnd, true); cur = phaseFactor(phases, idx, &PhaseSym::specialMsgFactorBeg, &PhaseSym::specialMsgFactorEnd, false); if (!phase.specialMsgFactorEnd(d)) { p.buf() << "The portion of special messages remains " << "stable at " << (cur*100) << "%. "; } else { p.buf() << "The portion of special messages changes " << changeStr(prev, cur) << " from " << (prev*100) << "% to " << (cur*100) << "%. "; } Array samples; if (phase.statsSamples(samples)) { p.buf() << samples.count() << " samples of per-transaction" << " statistics are collected. "; } bool doIt; if (phase.logStats(doIt) && !doIt) { p.buf() << "No phase statistics is logged for this phase. "; } if (phase.waitWssFreeze(doIt) && doIt) { p.buf() << "The phase will continue until working set size is frozen. "; } sect << p; } static void explainPhases(XmlNodes &code, const PglParser &) { const Array &phases = PglStaticSemx::TheSchedule; XmlSection sect(2, "Phase schedule"); if (phases.count()) { XmlTextTag p; p.buf() << "The workload schedule consists of " << phases.count(); addOneOrMany(p.buf(), phases.count(), " phase") << ". "; int fixDurCount = 0; int varDurCount = 0; const Time fixedDur = testDuration(phases, fixDurCount, varDurCount); p.buf() << "The schedule includes " << fixDurCount << " phases " << "with time-based goals"; if (varDurCount) addOneOrMany(p.buf() << " and " << varDurCount << " other ", varDurCount, "phase"); p.buf() << ". "; if (fixedDur > 0) { p.buf() << "The total test duration (based on the" << " time-based goals) is about " << fixedDur << ". "; } sect << p; XmlTag c("center"); XmlTable table; buildPhaseTable(table, phases); c << table; sect << c; for (int i = 0; i < phases.count(); ++i) { explainPhase(sect, phases, i); } } else { XmlTextTag p; p.buf() << "The workload does not define a schedule. " << "Polygraph will use a default schedule consisting of a " << "single phase with no goal and default factors. "; sect << p; } code << sect; } static void explainAgent(XmlNodes &code, const AgentSym &agent, const String &type) { XmlText t; if (const RndDistr *ul = agent.pconnUseLmt()) { t.buf() << "This " << type << " uses persistent connections. " << "The number of transactions per connection is distributed as "; ul->print(t.buf(), &DistrSym::IntArgPrinter) << ". "; const Time tout = agent.idlePconnTimeout(); if (tout >= 0) { t.buf() << "Idle persistent connections are closed after a " << tout << " timeout. "; } else { t.buf() << "Idle persistent connections are never closed by this " << type << ". "; } } else { t.buf() << "This " << type << " does not use persistent connections. "; } code << t; } static void explainMsgTypes(XmlNodes &code, const AgentSym &agent, const String &msgKind) { XmlText t; Array msgTypes; Array tprobs; if (agent.msgTypes(msgTypes, tprobs)) { t.buf() << "The following " << msgTypes.count() << ' ' << msgKind << ' '; addOneOrMany(t.buf(), msgTypes.count(), "type is", "types are") << " used: "; for (int i = 0; i < msgTypes.count(); ++i) { addAnd(t.buf(), i, msgTypes.count()); t.buf() << '"' << msgTypes[i]->val() << '"' << " (" << (100*tprobs[i]) << "%"; if (!i) t.buf() << " of all possible " << msgKind << " types"; t.buf() << ")"; } t.buf() << ". "; } else { t.buf() << "Only basic " << msgKind << " types are used. "; } code << t; } static void explainServer(XmlTag §, const ServerSym &srv) { XmlTextTag p; Array ccfgs; Array cprobs; if (srv.contents(ccfgs, cprobs)) { p.buf() << "Server \"" << srv.kind() << "\" hosts the following " << ccfgs.count() << " content "; addOneOrMany(p.buf(), ccfgs.count(), "type") << ": "; for (int i = 0; i < ccfgs.count(); ++i) { addAnd(p.buf(), i, ccfgs.count()); p.buf() << '"' << ccfgs[i]->kind() << '"' << " (" << (100*cprobs[i]) << "%"; if (!i) p.buf() << " of all hosted content"; p.buf() << ")"; } p.buf() << ". "; } else { p.buf() << "Server type \"" << srv.kind() << "\" does not host " " any content!"; } Array dacfgs; Array daprobs; if (srv.directAccess(dacfgs, daprobs)) { p.buf() << "The following " << dacfgs.count() << " content "; addOneOrMany(p.buf(), dacfgs.count(), "type") << " can be accessed directly: "; for (int i = 0; i < dacfgs.count(); ++i) { addAnd(p.buf(), i, dacfgs.count()); p.buf() << '"' << dacfgs[i]->kind() << '"'; } p.buf() << ". "; } else { p.buf() << "No hosted content can be accessed direclty. "; } if (RndDistr *xt = srv.xactThink()) { p.buf() << "Server \"think time\" distribution is set to "; xt->print(p.buf(), &DistrSym::TimeArgPrinter) << ". "; } else { p.buf() << "The server responds to all requests without" << " artificial delays. "; } XmlNodes code; explainAgent(code, srv, "server"); explainMsgTypes(code, srv, "reply"); p << code; sect << p; } static void explainServers(XmlNodes &code, const PglParser &) { XmlSection sect(2, "Servers configuration"); Array servers; for (AgentArrIter i(PglStaticSemx::TheAgentsToUse, 0, "Server"); i; i.nextSym()) servers.append(&(ServerSym&)i.agentSym()->cast("Server")); if (servers.count()) { XmlTextTag p; p.buf() << "The workload defines " << servers.count() << " server "; addOneOrMany(p.buf(), servers.count(), "type") << ". "; sect << p; for (int i = 0; i < servers.count(); ++i) { explainServer(sect, *servers[i]); } } else { XmlTextTag p; p.buf() << "The workload does not use any servers. "; sect << p; } code << sect; } static void explainInterests(XmlNodes &code, const RobotSym &robot) { XmlText t; Array interests; Array iprobs; if (robot.interests(interests, iprobs)) { t.buf() << "The robot is interested in the following " << interests.count() << " object groups: "; for (int i = 0; i < interests.count(); ++i) { addAnd(t.buf(), i, interests.count()); t.buf() << '"' << interests[i]->val() << '"' << " (" << (100*iprobs[i]) << "%"; if (!i) t.buf() << " of all requests"; t.buf() << ")"; } t.buf() << ". "; } else { t.buf() << "The robot only requests private objects (i.e., " << "it does not share request URLs with other robots)."; } code << t; } static void explainRobot(XmlTag §, const RobotSym &rbt) { XmlTextTag p1; RndDistr *iad = 0; if (rbt.reqInterArrival(iad)) { if (iad) { p1.buf() << "Robot \"" << rbt.kind() << "\" is a \"constant " << "request rate\" robot "; if (String(iad->pdfName()) == "exp") { const double rate = 1/iad->mean(); p1.buf() << "with request rate of " << rate << " requests per second. "; } else { p1.buf() << "with request inter-arrival distribution set to "; iad->print(p1.buf(), &DistrSym::TimeArgPrinter) << ". "; } } else { p1.buf() << "Robot \"" << rbt.kind() << "\" is a \"passive\" " << "robot that will not submit any requests on its own. "; } } else { p1.buf() << "Robot \"" << rbt.kind() << "\" is a \"best-effort\" " << "robot that will start next HTTP transaction as soon " << "as the previous transaction completes. "; } double ratio; if (rbt.recurRatio(ratio)) { p1.buf() << "This robot revisits " << (100*ratio) << "% of previously " << "requested URLs (offering a hit when a URL is cachable). "; } if (rbt.embedRecurRatio(ratio)) { p1.buf() << "About " << (100*ratio) << "% of embedded objects " << " will be loaded. "; } if (rbt.minimizeNewConn(ratio)) { p1.buf() << "The robot will attempt to minimize the number of new " << "connections (by using substitutes servers) with a probability " << "of " << (100*ratio) << "%. "; } sect << p1; XmlTextTag p2; int lmt; if (rbt.openConnLimit(lmt)) { p2.buf() << "This robot is not allowed to open more than " << lmt; addOneOrMany(p2.buf(), lmt, " connection") << " at any given time, " << "even if that limit causes decrease in request rate or " << "memory exhaustion. "; if (rbt.waitXactLimit(lmt)) { p2.buf() << "However, the robot limits waiting transaction queue "; addOneOrMany(p2.buf() << "to " << lmt, lmt, " transaction") << ", cancelling newly submitted transactions as needed. "; } else { p2.buf() << "Moreover, waiting transaction queue can grow without bounds. "; } } else { p2.buf() << "This robot opens as many connections as needed. "; } if (rbt.privCache(lmt)) { p2.buf() << "Robot's private cache is limited to " << lmt; addOneOrMany(p2.buf(), lmt, " entry", " entries") << ". "; } XmlNodes code; explainAgent(code, rbt, "robot"); explainInterests(code, rbt); explainMsgTypes(code, rbt, "request"); p2 << code; sect << p2; XmlTextTag p3; if (PopModelSym *pm = rbt.popModel()) { const PopDistr *pd = pm->popDistr(); double hotSetFrac; double hotSetProb; if (pd && pm->hotSetFrac(hotSetFrac) && pm->hotSetProb(hotSetProb)) { p3.buf() << "\"" << rbt.kind() << "\" robots direct " << (100*hotSetProb) << "% of all requests to " << (100*hotSetFrac) << "% of the working set, using "; pd->print(p3.buf()) << " popularity distribution. "; } } sect << p3; } static void explainRobots(XmlNodes &code, const PglParser &) { XmlSection sect(2, "Robots configuration"); Array robots; for (AgentArrIter i(PglStaticSemx::TheAgentsToUse, 0, "Robot"); i; i.nextSym()) robots.append(&(RobotSym&)i.agentSym()->cast("Robot")); if (robots.count()) { XmlTextTag p; p.buf() << "The workload defines " << robots.count() << " robot "; addOneOrMany(p.buf(), robots.count(), "type") << ". "; sect << p; for (int i = 0; i < robots.count(); ++i) { explainRobot(sect, *robots[i]); } } else { XmlTextTag p; p.buf() << "The workload does not use any robots. "; sect << p; } code << sect; } static void explainContType(XmlTag §, const ContentSym &ctype) { XmlTextTag p; if (RndDistr *sized = ctype.size()) { p.buf() << "The size distribution for \"" << ctype.kind() << "\" content type is "; sized->print(p.buf(), &DistrSym::SizeArgPrinter); p.buf() << ". "; } else { p.buf() << "Content type \"" << ctype.kind() << "\" has unknown size. "; } double ratio = 0; ctype.cachable(ratio); // default is 0 p.buf() << "About " << (100*ratio) << "% of \"" << ctype.kind() << "\" objects are cachable. "; Array ccfgs; RndDistr *selector; Array cprobs; if (ctype.contains(ccfgs, selector, cprobs) && ccfgs.count()) { p.buf() << "This content type is a container. Objects may " << "contain (embed) the following " << ccfgs.count(); addOneOrMany(p.buf(), ccfgs.count(), " content type") << ": "; for (int i = 0; i < ccfgs.count(); ++i) { addAnd(p.buf(), i, ccfgs.count()); p.buf() << '"' << ccfgs[i]->kind() << '"' << " (" << (100*cprobs[i]) << "%"; if (!i) p.buf() << " of all embedded content"; p.buf() << ")"; } p.buf() << ". "; if (RndDistr *embCnt = ctype.embedCount()) { p.buf() << "The number of embedded objects per container is " << "distributed as "; embCnt->print(p.buf(), &DistrSym::IntArgPrinter) << ". "; } } else { p.buf() << "This content type does not contain other types. "; } if (MimeSym *mime = ctype.mime()) { const String mtype = mime->mimeType(); if (mtype) { p.buf() << "Generated object have \"" << mtype << "\" MIME type. "; } Array pfxs; RndDistr *sel; if (mime->prefixes(pfxs, sel) && pfxs.count()) { p.buf() << "URLs may start with the following " << pfxs.count(); addOneOrMany(p.buf(), pfxs.count(), " string") << ": "; for (int x = 0; x < pfxs.count(); ++x) { addAnd(p.buf(), x, pfxs.count()); p.buf() << '"' << *pfxs[x] << '"'; } p.buf() << ". "; } Array exts; if (mime->extensions(exts, sel) && exts.count()) { p.buf() << "The following " << exts.count(); addOneOrMany(p.buf(), exts.count(), " extension") << " may appear at the end of URLs: "; for (int e = 0; e < exts.count(); ++e) { addAnd(p.buf(), e, exts.count()); p.buf() << '"' << *exts[e] << '"'; } p.buf() << ". "; } } sect << p; } static void buildContTypeTable(XmlTable &table, const Array &ctypes) { table << XmlAttr::Int("border", 1) << XmlAttr("bgcolor", "#FFFFFF"); XmlTableRec hr; hr << XmlTableHeading("Type") << XmlTableHeading("Reply Size") << XmlTableHeading("Cachability") << XmlTableHeading("Extensions"); XmlTableHeader header; header << hr; header << XmlAttr("bgcolor", "#BBBBBB"); table << header; for (int i = 0; i < ctypes.count(); ++i) { XmlTableRec rec; const ContentSym &ctype = *ctypes[i]; rec << XmlTableHeading(ctype.kind()); XmlTextTag cellSize; if (RndDistr *sized = ctype.size()) sized->print(cellSize.buf(), &DistrSym::SizeArgPrinter); else cellSize.buf() << '?'; cellSize << XmlAttr("align", "center"); rec << cellSize; XmlTextTag cellChb; double ratio = 0; ctype.cachable(ratio); cellChb.buf() << (100*ratio) << '%'; // default is zero cellChb << XmlAttr("align", "right"); rec << cellChb; XmlTextTag cellExts; if (MimeSym *mime = ctype.mime()) { Array exts; RndDistr *sel; if (mime->extensions(exts, sel)) { for (int e = 0; e < exts.count(); ++e) { addAnd(cellExts.buf(), e, exts.count()); cellExts.buf() << *exts[e]; } } } if (!cellExts.buf().tellp()) cellExts.buf() << " "; rec << cellExts; table << rec; } } static void explainContTypes(XmlNodes &code, const PglParser &) { XmlSection sect(2, "Content types"); Array ctypes; {for (AgentArrIter i(PglStaticSemx::TheAgentsToUse, 0, "Server"); i; i.nextSym()) { const ServerSym &srv = (const ServerSym&)i.agentSym()->cast("Server"); Array ccfgs; Array cprobs; if (srv.contents(ccfgs, cprobs)) { for (int c = 0; c < ccfgs.count(); ++c) { bool found = false; for (int f = 0; !found && f < ctypes.count(); ++f) found = ctypes[f]->equal(*ccfgs[c]); if (!found) ctypes.append(ccfgs[c]); } } }} if (ctypes.count()) { XmlTextTag p; p.buf() << "The workload uses " << ctypes.count(); addOneOrMany(p.buf(), ctypes.count(), " unique content type") << ". "; sect << p; XmlTag c("center"); XmlTable table; buildContTypeTable(table, ctypes); c << table; sect << c; for (int i = 0; i < ctypes.count(); ++i) { explainContType(sect, *ctypes[i]); } } else { XmlTextTag p; p.buf() << "The workload does not use any content types. "; sect << p; } code << sect; } int main(int argc, char **argv) { (void)PolyVersion(); if (argc < 2) cerr << "usage: " << argv[0] << " [include_dir] ..." << endl << xexit; for (int i = 2; i < argc; ++i) PglPp::TheDirs.append(new String(argv[i])); PglStaticSemx::Interpret(argv[1]); PglParser parser; XmlNodes code; explainPhases(code, parser); explainServers(code, parser); explainRobots(code, parser); explainContTypes(code, parser); configureStream(cout, 2); code.print(cout, ""); return 0; }