/* 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 #include "xstd/h/iomanip.h" #include "xstd/rndDistrs.h" #include "xstd/StrIdentifier.h" #include "xstd/Ssl.h" #include "base/RndPermut.h" #include "base/OidGenStat.h" #include "pgl/RobotSym.h" #include "pgl/SessionSym.h" #include "pgl/AclSym.h" #include "pgl/GoalSym.h" #include "pgl/PglStaticSemx.h" #include "runtime/AddrMap.h" #include "runtime/HostMap.h" #include "runtime/PubWorld.h" #include "runtime/PopModel.h" #include "runtime/Goal.h" #include "runtime/XactAbortCoord.h" #include "runtime/LogComment.h" #include "runtime/httpHdrs.h" #include "csm/XmlTagIdentifier.h" #include "client/WarmupPlan.h" #include "client/ServerRep.h" #include "client/RegExGroup.h" #include "client/ForeignWorld.h" #include "client/Client.h" #include "client/CltCfg.h" #include "client/CltOpts.h" Memberships CltCfg::TheGlbMemberships; CltCfg::CltCfg(): theRobot(0), theOriginSel(0), theInterestSel(0), theReqTypeSel(0), theReqMethodSel(0), thePopModel(0), theBusyPeriod(0), theIdlePeriodDur(0), theProxyCycleCnt(0), thePipelineDepth(0), theRecurRatio(-1), theEmbedRecurRatio(-1), theAbortProb(0), theAuthError(0), theWaitXactLmt(-1), theIcpPort(-1), theCredentialCycleCnt(0), theForeignWorld(0), theContainerTags(0), theAcceptedContentCodings(0), acceptingGzipContent(false), theCookiesKeepLimitSel(0), theWarmupPlan(0), genUniqUrls(false), didWarmup(false) { } CltCfg::~CltCfg() { while (theCredentials.count()) delete theCredentials.dequeue(); while (theProxies.count()) delete theProxies.dequeue(); delete theContainerTags; delete theAcceptedContentCodings; delete thePipelineDepth; delete theCookiesKeepLimitSel; } void CltCfg::configure(const RobotSym *aRobot) { AgentCfg::configure(aRobot); Assert(!theRobot && aRobot); theRobot = aRobot; theRobot->waitXactLimit(theWaitXactLmt); thePeerHttp = theRobot->peerHttp(); thePeerIcp = theRobot->peerIcp(); theRobot->icpPort(theIcpPort); theRobot->recurRatio(theRecurRatio); theRobot->embedRecurRatio(theEmbedRecurRatio); theRobot->abortProb(theAbortProb); theRobot->authError(theAuthError); theRobot->uniqueUrls(genUniqUrls); theUriThrower = theRobot->rawUriThrower(); if (PopModelSym *pms = theRobot->popModel()) { thePopModel = new PopModel; thePopModel->configure(pms); } configureInterests(); configureReqTypes(); configureReqMethods(); configureOrigins(); configureProxies(); configureCredentials(); configureMemberships(); configureContainerTags(); configureAcceptedContentCodings(); configurePipeDepth(); if (AclSym *acl = theRobot->acl()) theAcl.configure(*acl); if (const String fname = theRobot->foreignTrace()) configureTrace(fname); if (SessionSym *ss = theRobot->session()) { if (GoalSym *bps = ss->busyPeriod()) { theBusyPeriod = new Goal; theBusyPeriod->configure(*bps); } theIdlePeriodDur = ss->idlePeriodDuration(); ss->heartbeatGap(theSessionHeartbitGap); if (!theBusyPeriod != !theIdlePeriodDur) { cerr << theRobot->loc() << "both busy and idle periods " << "should be specified (or not specified) for a robot session" << endl << xexit; } } if (!thePopModel && theRecurRatio > 0) cerr << theRobot->loc() << "popularity model must be specified for" << " positive recurrence ratio (robot " << theRobot->kind() << ')' << endl << xexit; theCookiesKeepLimitSel = theRobot->cookiesKeepLimit(); } int CltCfg::viservLimit() const { return TheHostMap->iterationCount(); } void CltCfg::configureInterests() { static StrIdentifier sidf; if (!sidf.count()) { sidf.add("private", OidGenStat::intPrivate); sidf.add("public", OidGenStat::intPublic); sidf.add("foreign", OidGenStat::intForeign); sidf.optimize(); } theInterestSel = theRobot->interests(sidf); if (!theInterestSel) theInterestSel = new ConstDistr(new RndGen, OidGenStat::intPrivate); // default theInterestSel->rndGen(LclRndGen("client_interests")); } void CltCfg::configureReqTypes() { static StrIdentifier sidf; if (!sidf.count()) { sidf.add("Basic", Client::rqtBasic); sidf.add("Ims200", Client::rqtIms200); sidf.add("Ims304", Client::rqtIms304); sidf.add("Reload", Client::rqtReload); sidf.optimize(); } theReqTypeSel = theRobot->msgTypes(sidf); if (!theReqTypeSel) theReqTypeSel = new ConstDistr(new RndGen, Client::rqtBasic); // default theReqTypeSel->rndGen(LclRndGen("client_req_types")); } void CltCfg::configurePipeDepth() { thePipelineDepth = theRobot->pipelineDepth(); if (thePipelineDepth) thePipelineDepth->rndGen(LclRndGen("client_pipe_depth")); } void CltCfg::configureReqMethods() { static StrIdentifier sidf; if (!sidf.count()) { sidf.add("GET", Client::rqmGet); sidf.add("HEAD", Client::rqmHead); sidf.add("POST", Client::rqmPost); sidf.add("PUT", Client::rqmPut); sidf.optimize(); } theReqMethodSel = theRobot->reqMethods(sidf); if (!theReqMethodSel) theReqMethodSel = new ConstDistr(new RndGen, Client::rqmGet); // default theReqMethodSel->rndGen(LclRndGen("client_req_methods")); } void CltCfg::configureOrigins() { Array origNames; RndDistr *iad = 0; if (!theRobot->origins(origNames, theOriginSel) || !origNames.count()) { if (!theRobot->reqInterArrival(iad) || iad) cerr << theRobot->loc() << "no origin addresses specified for active robot " << theRobot->kind() << endl << xexit; return; } Assert(theOriginSel); theOriginSel->rndGen(LclRndGen("client_origins")); theViservs.stretch(origNames.count()); for (int i = 0; i < origNames.count(); ++i) addOrigName(*origNames[i]); } // converts origin name into viserv idx void CltCfg::addOrigName(const NetAddr &oname) { if (!TheAddrMap->has(oname)) { if (oname.isDomainName()) cerr << here << "visible server name " << oname << " is not found in address maps" << endl << xexit; TheAddrMap->add(oname); } int viserv = -1; if (!TheHostMap->find(oname, viserv)) TheHostMap->addAt(viserv, oname); HostCfg *host = TheHostMap->at(viserv); if (!host->thePubWorld) PubWorld::Add(host, new PubWorld(UniqId::Create())); if (!host->theServerRep) host->theServerRep = new ServerRep(oname, viserv); // quit if an origin entry is repeated because it complicates // private world accounting (two origins would have one world) if (hasViserv(viserv)) { Comment << theRobot->loc() << "error: origin " << oname << " is listed more than once in robot's origins" << endc << xexit; } theViservs.append(viserv); checkTargets(viserv); } // checks content cfg and creates new server representative for viserv void CltCfg::checkTargets(int viserv) { const NetAddr &visName = TheHostMap->at(viserv)->theAddr; int niamIdx; // name in AddrMap index Assert(TheAddrMap->find(visName, niamIdx)); for (AddrMapAddrIter i = TheAddrMap->addrIter(niamIdx); i; ++i) { const NetAddr &addr = i.addr(); int targetIdx = -1; if (!TheHostMap->find(addr, targetIdx) || !TheHostMap->at(targetIdx)->theContent) { Comment << theRobot->loc() << "error: Robot cannot find" << " configuration for server@" << addr; if (addr != i.name()) Comment << " (visible as " << i.name() << ")"; Comment << endc << xexit; } } } void CltCfg::configureProxies() { Array addrs; theRobot->proxies(addrs); theProxies.resize(addrs.count()); while (addrs.count()) theProxies.enqueue(addrs.pop()); if (TheCltOpts.theProxyAddr && theProxies.count()) { Comment << theRobot->loc() << "--proxy option and " << "Robot.proxies field are mutually exclusive" << endc << xexit; } } void CltCfg::configureCredentials() { Array creds; theRobot->credentials(creds); theCredentials.resize(creds.count()); while (creds.count()) theCredentials.enqueue(creds.pop()); } void CltCfg::configureContainerTags() { Array tags; if (!theRobot->containerTags(tags)) tags.append(new String("")); // default theContainerTags = new XmlTagIdentifier(); theContainerTags->configure(tags); } void CltCfg::configureAcceptedContentCodings() { Array codings; if (theRobot->acceptedContentCodings(codings)) { theAcceptedContentCodings = new String; for (int i = 0; i < codings.count(); ++i) { const String &coding = *codings[i]; if (coding.startsWith("*") || coding.startsWith("gzip")) acceptingGzipContent = true; if (*theAcceptedContentCodings) *theAcceptedContentCodings += ", "; *theAcceptedContentCodings += coding; delete codings[i]; } } } void CltCfg::configureMemberships() { // initialize global array once const Array &syms = PglStaticSemx::TheMembershipsToUse; if (TheGlbMemberships.count() != syms.count()) { TheGlbMemberships.stretch(syms.count()); for (int i = 0; i < syms.count(); ++i) { MembershipMap *m = new MembershipMap; m->configure(*syms[i], i+1); TheGlbMemberships.append(m); } } for (int i = 0; i < TheGlbMemberships.count(); ++i) { MembershipMap *g = TheGlbMemberships[i]; bool belongs = false; for (int u = 0; !belongs && u < theCredentials.count(); ++u) { belongs = g->hasMember(*theCredentials.top(u)); } if (belongs) theLclMemberships.append(g); } } void CltCfg::configureTrace(const String &fname) { theForeignWorld = new ForeignWorld; theForeignWorld->configure(fname); } bool CltCfg::hasViserv(int viserv) const { for (int origin = 0; origin < theViservs.count(); ++origin) { if (theViservs[origin] == viserv) return true; } return false; } bool CltCfg::selectCredentials(String &newCred) { if (theCredentials.empty()) return false; // randomize in the beginning of each cycle if (theCredentialCycleCnt >= theCredentials.count()) theCredentialCycleCnt = 0; if (!theCredentialCycleCnt) { static RndGen rng; theCredentials.randomize(rng); } theCredentialCycleCnt++; String *credential = theCredentials.dequeue(); theCredentials.enqueue(credential); newCred = *credential; return true; } int CltCfg::selectInterest() { const int interest = (int)theInterestSel->trial(); Assert(interest == OidGenStat::intPrivate || interest == OidGenStat::intPublic || interest == OidGenStat::intForeign); return interest; } int CltCfg::selectViserv() { if (theWarmupPlan) { const int viservIdx = theWarmupPlan->selectViserv(); if (viservIdx >= 0) return viservIdx; stopWarmup(); } const int viservIdx = (int)theOriginSel->trial(); Assert(0 <= viservIdx && viservIdx < theViservs.count()); return theViservs[viservIdx]; } bool CltCfg::selectProxy(NetAddr &newAddr) { if (TheCltOpts.theProxyAddr) { newAddr = TheCltOpts.theProxyAddr; return true; } if (!theProxies.count()) return false; // randomize in the beginning of each cycle if (theProxyCycleCnt >= theProxies.count()) theProxyCycleCnt = 0; if (!theProxyCycleCnt) { static RndGen rng; theProxies.randomize(rng); } theProxyCycleCnt++; NetAddr *addr = theProxies.dequeue(); theProxies.enqueue(addr); newAddr = *addr; return true; } // XXX: merge this with SrvCfg::selectAbortCoord() void CltCfg::selectAbortCoord(XactAbortCoord &coord) { static RndGen rng1, rng2; // uncorrelated unless theAbortProb is 1 if (rng1.event(theAbortProb)) { const int whether = rng2.state(); (void)rng2.trial(); coord.configure(rng2.state(), whether); } else { const int whether = rng1.state(); (void)rng1.trial(); coord.configure(whether, rng1.state()); } } int CltCfg::findMemberships(const String &user, Memberships &groups) const { if (user) { for (int i = 0; i < theLclMemberships.count(); ++i) { MembershipMap *g = theLclMemberships[i]; if (g->hasMember(user)) groups.append(g); } } return groups.count(); } bool CltCfg::followAllUris(const RepHdr &rep) const { if (theUriThrower.len() > 0) { return theUriThrower == "*" || rep.theServer.startsWith(theUriThrower); } else { return false; } } // may be called multiple times void CltCfg::startWarmup() { if (!theWarmupPlan && !didWarmup) theWarmupPlan = new WarmupPlan(theViservs); } void CltCfg::stopWarmup() { Assert(theWarmupPlan && !didWarmup); delete theWarmupPlan; theWarmupPlan = 0; didWarmup = true; } /* CltSharedCfgs */ CltCfg *CltSharedCfgs::getConfig(const RobotSym *rs) { for (int i = 0; i < count(); ++i) { if (item(i)->theRobot == rs) return item(i); } return addConfig(rs); } CltCfg *CltSharedCfgs::addConfig(const RobotSym *rs) { CltCfg *cfg = new CltCfg; cfg->configure(rs); append(cfg); return cfg; }