void ZArray::initStats(AggregateStat* parentStat) { AggregateStat* objStats = new AggregateStat(); objStats->init("array", "ZArray stats"); statSwaps.init("swaps", "Block swaps in replacement process"); objStats->append(&statSwaps); parentStat->append(objStats); }
void DRAMSimMemory::initStats(AggregateStat* parentStat) { AggregateStat* memStats = new AggregateStat(); memStats->init(name.c_str(), "Memory controller stats"); profReads.init("rd", "Read requests"); memStats->append(&profReads); profWrites.init("wr", "Write requests"); memStats->append(&profWrites); profTotalRdLat.init("rdlat", "Total latency experienced by read requests"); memStats->append(&profTotalRdLat); profTotalWrLat.init("wrlat", "Total latency experienced by write requests"); memStats->append(&profTotalWrLat); parentStat->append(memStats); }
void DDRMemory::initStats(AggregateStat* parentStat) { AggregateStat* memStats = new AggregateStat(); memStats->init(name.c_str(), "Memory controller stats"); profReads.init("rd", "Read requests"); memStats->append(&profReads); profWrites.init("wr", "Write requests"); memStats->append(&profWrites); profTotalRdLat.init("rdlat", "Total latency experienced by read requests"); memStats->append(&profTotalRdLat); profTotalWrLat.init("wrlat", "Total latency experienced by write requests"); memStats->append(&profTotalWrLat); profReadHits.init("rdhits", "Read row hits"); memStats->append(&profReadHits); profWriteHits.init("wrhits", "Write row hits"); memStats->append(&profWriteHits); latencyHist.init("mlh", "latency histogram for memory requests", NUMBINS); memStats->append(&latencyHist); parentStat->append(memStats); }
void NVMainMemory::initStats(AggregateStat* parentStat) { AggregateStat* memStats = new AggregateStat(); memStats->init(name.c_str(), "Memory controller stats"); profIssued.init("issued", "Issued requests"); memStats->append(&profIssued); profReads.init("rd", "Read requests"); memStats->append(&profReads); profWrites.init("wr", "Write requests"); memStats->append(&profWrites); profPUTS.init("PUTS", "Clean Evictions (from lower level)"); memStats->append(&profPUTS); profPUTX.init("PUTX", "Dirty Evictions (from lower level)"); memStats->append(&profPUTX); profTotalRdLat.init("rdlat", "Total latency experienced by read requests"); memStats->append(&profTotalRdLat); profTotalWrLat.init("wrlat", "Total latency experienced by write requests"); memStats->append(&profTotalWrLat); profMemoryFootprint.init("footprint", "Total memory footprint in bytes"); memStats->append(&profMemoryFootprint); profMemoryAddresses.init("addresses", "Total number of distinct memory addresses"); memStats->append(&profMemoryAddresses); latencyHist.init("mlh", "latency histogram for memory requests", NUMBINS); memStats->append(&latencyHist); addressReuseHist.init("addressReuse", "address reuse histogram for memory requests", NUMBINS); memStats->append(&addressReuseHist); parentStat->append(memStats); }
void TimingCache::initStats(AggregateStat* parentStat) { AggregateStat* cacheStat = new AggregateStat(); cacheStat->init(name.c_str(), "Timing cache stats"); initCacheStats(cacheStat); //Stats specific to timing cache profOccHist.init("occHist", "Occupancy MSHR cycle histogram", numMSHRs+1); cacheStat->append(&profOccHist); profHitLat.init("latHit", "Cumulative latency accesses that hit (demand and non-demand)"); profMissRespLat.init("latMissResp", "Cumulative latency for miss start to response"); profMissLat.init("latMiss", "Cumulative latency for miss start to finish (free MSHR)"); cacheStat->append(&profHitLat); cacheStat->append(&profMissRespLat); cacheStat->append(&profMissLat); parentStat->append(cacheStat); }
ProcStats::ProcStats(AggregateStat* parentStat, AggregateStat* _coreStats) : coreStats(_coreStats) { uint32_t maxProcs = zinfo->lineSize; lastUpdatePhase = 0; // Check that coreStats are appropriate assert(coreStats); for (uint32_t i = 0; i < coreStats->size(); i++) { Stat* s = coreStats->get(i); AggregateStat* as = dynamic_cast<AggregateStat*>(s); auto err = [s](const char* reason) { panic("Stat %s is not per-core (%s)", s->name(), reason); }; if (!as) err("not aggregate stat"); if (!as->isRegular()) err("irregular aggregate"); if (as->size() != zinfo->numCores) err("elems != cores"); } // Initialize all the buffers bufSize = StatSize(coreStats); buf = gm_calloc<uint64_t>(bufSize); lastBuf = gm_calloc<uint64_t>(bufSize); // Create the procStats procStats = new AggregateStat(true); procStats->init("procStats", "Per-process stats"); for (uint32_t p = 0; p < maxProcs; p++) { AggregateStat* ps = new AggregateStat(false); const char* name = gm_strdup(("procStats-" + Str(p)).c_str()); ps->init(name, "Per-process stats"); for (uint32_t i = 0; i < coreStats->size(); i++) { AggregateStat* as = dynamic_cast<AggregateStat*>(coreStats->get(i)); assert(as && as->isRegular()); ps->append(replStat(as->get(0), as->name(), as->desc())); } procStats->append(ps); } parentStat->append(procStats); }
AggregateStat* FilterStatsLevel(const AggregateStat* src, const regex& filter, const char* prefix) { string base = prefix? (string(prefix) + src->name() + ".") : ""; //if NULL prefix, omit our name (we're root) vector<Stat*> children; for (uint32_t i = 0; i < src->size(); i++) { Stat* child = src->get(i); if (AggregateStat* as = dynamic_cast<AggregateStat*>(child)) { AggregateStat* fs = FilterStatsLevel(as, filter, base.c_str()); if (fs) children.push_back(fs); } else { string name = base + child->name(); if (regex_match(name, filter)) children.push_back(child); } } if (children.size()) { AggregateStat* res = new AggregateStat(src->isRegular()); res->init(src->name(), src->desc()); for (Stat* c : children) res->append(c); return res; } else { return NULL; } }
Stat* ProcStats::replStat(Stat* s, const char* name, const char* desc) { if (!name) name = s->name(); if (!desc) desc = s->desc(); if (AggregateStat* as = dynamic_cast<AggregateStat*>(s)) { AggregateStat* res = new AggregateStat(as->isRegular()); res->init(name, desc); for (uint32_t i = 0; i < as->size(); i++) { res->append(replStat(as->get(i))); } return res; } else if (dynamic_cast<ScalarStat*>(s)) { Counter* res = new ProcessCounter(this); res->init(name, desc); return res; } else if (VectorStat* vs = dynamic_cast<VectorStat*>(s)) { VectorCounter* res = new ProcessVectorCounter(this); assert(!vs->hasCounterNames()); // FIXME: Implement counter name copying res->init(name, desc, vs->size()); return res; } else { panic("Unrecognized stat type"); return nullptr; } }
void Cache::initStats(AggregateStat* parentStat) { AggregateStat* cacheStat = new AggregateStat(); cacheStat->init(name.c_str(), "Cache stats"); initCacheStats(cacheStat); parentStat->append(cacheStat); }
void SimInit(const char* configFile, const char* outputDir, uint32_t shmid) { zinfo = gm_calloc<GlobSimInfo>(); zinfo->outputDir = gm_strdup(outputDir); Config config(configFile); //Debugging //NOTE: This should be as early as possible, so that we can attach to the debugger before initialization. zinfo->attachDebugger = config.get<bool>("sim.attachDebugger", false); zinfo->harnessPid = getppid(); getLibzsimAddrs(&zinfo->libzsimAddrs); if (zinfo->attachDebugger) { gm_set_secondary_ptr(&zinfo->libzsimAddrs); notifyHarnessForDebugger(zinfo->harnessPid); } PreInitStats(); //Get the number of cores //TODO: There is some duplication with the core creation code. This should be fixed eventually. uint32_t numCores = 0; vector<const char*> groups; config.subgroups("sys.cores", groups); for (const char* group : groups) { uint32_t cores = config.get<uint32_t>(string("sys.cores.") + group + ".cores", 1); numCores += cores; } if (numCores == 0) panic("Config must define some core classes in sys.cores; sys.numCores is deprecated"); zinfo->numCores = numCores; assert(numCores <= MAX_THREADS); //TODO: Is there any reason for this limit? zinfo->numDomains = config.get<uint32_t>("sim.domains", 1); uint32_t numSimThreads = config.get<uint32_t>("sim.contentionThreads", MAX((uint32_t)1, zinfo->numDomains/2)); //gives a bit of parallelism, TODO tune zinfo->contentionSim = new ContentionSim(zinfo->numDomains, numSimThreads); zinfo->contentionSim->initStats(zinfo->rootStat); zinfo->eventRecorders = gm_calloc<EventRecorder*>(numCores); // Global simulation values zinfo->numPhases = 0; zinfo->phaseLength = config.get<uint32_t>("sim.phaseLength", 10000); zinfo->statsPhaseInterval = config.get<uint32_t>("sim.statsPhaseInterval", 100); zinfo->freqMHz = config.get<uint32_t>("sys.frequency", 2000); //Maxima/termination conditions zinfo->maxPhases = config.get<uint64_t>("sim.maxPhases", 0); zinfo->maxMinInstrs = config.get<uint64_t>("sim.maxMinInstrs", 0); zinfo->maxTotalInstrs = config.get<uint64_t>("sim.maxTotalInstrs", 0); uint64_t maxSimTime = config.get<uint32_t>("sim.maxSimTime", 0); zinfo->maxSimTimeNs = maxSimTime*1000L*1000L*1000L; zinfo->maxProcEventualDumps = config.get<uint32_t>("sim.maxProcEventualDumps", 0); zinfo->procEventualDumps = 0; zinfo->skipStatsVectors = config.get<bool>("sim.skipStatsVectors", false); zinfo->compactPeriodicStats = config.get<bool>("sim.compactPeriodicStats", false); //Fast-forwarding and magic ops zinfo->ignoreHooks = config.get<bool>("sim.ignoreHooks", false); zinfo->ffReinstrument = config.get<bool>("sim.ffReinstrument", false); if (zinfo->ffReinstrument) warn("sim.ffReinstrument = true, switching fast-forwarding on a multi-threaded process may be unstable"); zinfo->registerThreads = config.get<bool>("sim.registerThreads", false); zinfo->globalPauseFlag = config.get<bool>("sim.startInGlobalPause", false); zinfo->eventQueue = new EventQueue(); //must be instantiated before the memory hierarchy //Build the scheduler uint32_t parallelism = config.get<uint32_t>("sim.parallelism", 2*sysconf(_SC_NPROCESSORS_ONLN)); if (parallelism < zinfo->numCores) info("Limiting concurrent threads to %d", parallelism); assert(parallelism > 0); //jeez... uint32_t schedQuantum = config.get<uint32_t>("sim.schedQuantum", 10000); //phases zinfo->sched = new Scheduler(EndOfPhaseActions, parallelism, zinfo->numCores, schedQuantum); zinfo->blockingSyscalls = config.get<bool>("sim.blockingSyscalls", false); if (zinfo->blockingSyscalls) { warn("sim.blockingSyscalls = True, will likely deadlock with multi-threaded apps!"); } InitGlobalStats(); //Core stats (initialized here for cosmetic reasons, to be above cache stats) AggregateStat* allCoreStats = new AggregateStat(false); allCoreStats->init("core", "Core stats"); zinfo->rootStat->append(allCoreStats); //Process tree needs this initialized, even though it is part of the memory hierarchy zinfo->lineSize = config.get<uint32_t>("sys.lineSize", 64); assert(zinfo->lineSize > 0); //Address randomization zinfo->addressRandomization = config.get<bool>("sys.addressRandomization", false); //Port virtualization for (uint32_t i = 0; i < MAX_PORT_DOMAINS; i++) zinfo->portVirt[i] = new PortVirtualizer(); //Process hierarchy //NOTE: Due to partitioning, must be done before initializing memory hierarchy CreateProcessTree(config); zinfo->procArray[0]->notifyStart(); //called here so that we can detect end-before-start races zinfo->pinCmd = new PinCmd(&config, NULL /*don't pass config file to children --- can go either way, it's optional*/, outputDir, shmid); //Caches, cores, memory controllers InitSystem(config); //Sched stats (deferred because of circular deps) zinfo->sched->initStats(zinfo->rootStat); zinfo->processStats = new ProcessStats(zinfo->rootStat); //It's a global stat, but I want it to be last... zinfo->profHeartbeats = new VectorCounter(); zinfo->profHeartbeats->init("heartbeats", "Per-process heartbeats", zinfo->lineSize); zinfo->rootStat->append(zinfo->profHeartbeats); bool perProcessDir = config.get<bool>("sim.perProcessDir", false); PostInitStats(perProcessDir, config); zinfo->perProcessCpuEnum = config.get<bool>("sim.perProcessCpuEnum", false); //Odds and ends bool printMemoryStats = config.get<bool>("sim.printMemoryStats", false); if (printMemoryStats) { gm_stats(); } //HACK: Read all variables that are read in the harness but not in init //This avoids warnings on those elements config.get<uint32_t>("sim.gmMBytes", (1 << 10)); if (!zinfo->attachDebugger) config.get<bool>("sim.deadlockDetection", true); config.get<bool>("sim.aslr", false); //Write config out bool strictConfig = config.get<bool>("sim.strictConfig", true); //if true, panic on unused variables config.writeAndClose((string(zinfo->outputDir) + "/out.cfg").c_str(), strictConfig); zinfo->contentionSim->postInit(); info("Initialization complete"); //Causes every other process to wake up gm_set_glob_ptr(zinfo); }
static void InitSystem(Config& config) { unordered_map<string, string> parentMap; //child -> parent unordered_map<string, vector<string>> childMap; //parent -> children (a parent may have multiple children, they are ordered by appearance in the file) //If a network file is specificied, build a Network string networkFile = config.get<const char*>("sys.networkFile", ""); Network* network = (networkFile != "")? new Network(networkFile.c_str()) : NULL; //Build the caches vector<const char*> cacheGroupNames; config.subgroups("sys.caches", cacheGroupNames); string prefix = "sys.caches."; for (const char* grp : cacheGroupNames) { string group(grp); if (group == "mem") panic("'mem' is an invalid cache group name"); if (parentMap.count(group)) panic("Duplicate cache group %s", (prefix + group).c_str()); string parent = config.get<const char*>(prefix + group + ".parent"); parentMap[group] = parent; if (!childMap.count(parent)) childMap[parent] = vector<string>(); childMap[parent].push_back(group); } //Check that all parents are valid: Either another cache, or "mem" for (const char* grp : cacheGroupNames) { string group(grp); string parent = parentMap[group]; if (parent != "mem" && !parentMap.count(parent)) panic("%s has invalid parent %s", (prefix + group).c_str(), parent.c_str()); } //Get the (single) LLC if (!childMap.count("mem")) panic("One cache must have mem as parent, none found"); if (childMap["mem"].size() != 1) panic("One cache must have mem as parent, multiple found"); string llc = childMap["mem"][0]; //Build each of the groups, starting with the LLC unordered_map<string, CacheGroup*> cMap; list<string> fringe; //FIFO fringe.push_back(llc); while (!fringe.empty()) { string group = fringe.front(); fringe.pop_front(); bool isTerminal = (childMap.count(group) == 0); //if no children, connected to cores if (cMap.count(group)) panic("The cache 'tree' has a loop at %s", group.c_str()); cMap[group] = BuildCacheGroup(config, group, isTerminal); if (!isTerminal) for (string child : childMap[group]) fringe.push_back(child); } //Check single LLC if (cMap[llc]->size() != 1) panic("Last-level cache %s must have caches = 1, but %ld were specified", llc.c_str(), cMap[llc]->size()); /* Since we have checked for no loops, parent is mandatory, and all parents are checked valid, * it follows that we have a fully connected tree finishing at the LLC. */ //Build the memory controllers uint32_t memControllers = config.get<uint32_t>("sys.mem.controllers", 1); assert(memControllers > 0); g_vector<MemObject*> mems; mems.resize(memControllers); zinfo->numMemoryControllers = memControllers; zinfo->hasNVMain = (config.get<const char*>("sys.mem.type", "Simple") == string("NVMain")) ? true : false; zinfo->hasDRAMCache = config.get<bool>("sys.mem.hasDRAMCache", false); for (uint32_t i = 0; i < memControllers; i++) { stringstream ss; ss << "mem-" << i; g_string name(ss.str().c_str()); //uint32_t domain = nextDomain(); //i*zinfo->numDomains/memControllers; uint32_t domain = i*zinfo->numDomains/memControllers; mems[i] = BuildMemoryController(config, zinfo->lineSize, zinfo->freqMHz, domain, name); } zinfo->memoryControllers = mems; if (memControllers > 1) { bool splitAddrs = config.get<bool>("sys.mem.splitAddrs", true); if (splitAddrs) { MemObject* splitter = new SplitAddrMemory(mems, "mem-splitter"); mems.resize(1); mems[0] = splitter; } } //Connect everything // mem to llc is a bit special, only one llc uint32_t childId = 0; for (BaseCache* llcBank : (*cMap[llc])[0]) { llcBank->setParents(childId++, mems, network); } // Rest of caches for (const char* grp : cacheGroupNames) { if (childMap.count(grp) == 0) continue; //skip terminal caches CacheGroup& parentCaches = *cMap[grp]; uint32_t parents = parentCaches.size(); assert(parents); //Concatenation of all child caches CacheGroup childCaches; for (string child : childMap[grp]) childCaches.insert(childCaches.end(), cMap[child]->begin(), cMap[child]->end()); uint32_t children = childCaches.size(); assert(children); uint32_t childrenPerParent = children/parents; if (children % parents != 0) { panic("%s has %d caches and %d children, they are non-divisible. " "Use multiple groups for non-homogeneous children per parent!", grp, parents, children); } //HACK FIXME: This solves the L1I+D-L2 connection bug, but it's not very clear. //A long-term solution is to specify whether the children should be interleaved or concatenated. bool terminalChildren = true; for (string child : childMap[grp]) terminalChildren &= (childMap.count(child) == 0 || config.get<bool>("sys.caches." + child + ".isPrefetcher", false)); if (terminalChildren) { info("%s's children are all terminal OR PREFETCHERS, interleaving them", grp); CacheGroup tmp(childCaches); uint32_t stride = children/childrenPerParent; for (uint32_t i = 0; i < children; i++) childCaches[i] = tmp[(i % childrenPerParent)*stride + i/childrenPerParent]; } for (uint32_t p = 0; p < parents; p++) { g_vector<MemObject*> parentsVec; parentsVec.insert(parentsVec.end(), parentCaches[p].begin(), parentCaches[p].end()); //BaseCache* to MemObject* is a safe cast uint32_t childId = 0; g_vector<BaseCache*> childrenVec; for (uint32_t c = p*childrenPerParent; c < (p+1)*childrenPerParent; c++) { for (BaseCache* bank : childCaches[c]) { bank->setParents(childId++, parentsVec, network); childrenVec.push_back(bank); } } for (BaseCache* bank : parentCaches[p]) { bank->setChildren(childrenVec, network); } } } //Check that all the terminal caches have a single bank for (const char* grp : cacheGroupNames) { if (childMap.count(grp) == 0) { uint32_t banks = (*cMap[grp])[0].size(); if (banks != 1) panic("Terminal cache group %s needs to have a single bank, has %d", grp, banks); } } //Tracks how many terminal caches have been allocated to cores unordered_map<string, uint32_t> assignedCaches; for (const char* grp : cacheGroupNames) if (childMap.count(grp) == 0) assignedCaches[grp] = 0; //Instantiate the cores vector<const char*> coreGroupNames; unordered_map <string, vector<Core*>> coreMap; config.subgroups("sys.cores", coreGroupNames); uint32_t coreIdx = 0; for (const char* group : coreGroupNames) { if (parentMap.count(group)) panic("Core group name %s is invalid, a cache group already has that name", group); coreMap[group] = vector<Core*>(); string prefix = string("sys.cores.") + group + "."; uint32_t cores = config.get<uint32_t>(prefix + "cores", 1); string type = config.get<const char*>(prefix + "type", "Simple"); //Build the core group union { SimpleCore* simpleCores; TimingCore* timingCores; OOOCore* oooCores; NullCore* nullCores; }; if (type == "Simple") { simpleCores = gm_memalign<SimpleCore>(CACHE_LINE_BYTES, cores); } else if (type == "Timing") { timingCores = gm_memalign<TimingCore>(CACHE_LINE_BYTES, cores); } else if (type == "OOO") { oooCores = gm_memalign<OOOCore>(CACHE_LINE_BYTES, cores); zinfo->oooDecode = true; //enable uop decoding, this is false by default, must be true if even one OOO cpu is in the system } else if (type == "Null") { nullCores = gm_memalign<NullCore>(CACHE_LINE_BYTES, cores); } else { panic("%s: Invalid core type %s", group, type.c_str()); } if (type != "Null") { string icache = config.get<const char*>(prefix + "icache"); string dcache = config.get<const char*>(prefix + "dcache"); if (!assignedCaches.count(icache)) panic("%s: Invalid icache parameter %s", group, icache.c_str()); if (!assignedCaches.count(dcache)) panic("%s: Invalid dcache parameter %s", group, dcache.c_str()); for (uint32_t j = 0; j < cores; j++) { stringstream ss; ss << group << "-" << j; g_string name(ss.str().c_str()); Core* core; //Get the caches CacheGroup& igroup = *cMap[icache]; CacheGroup& dgroup = *cMap[dcache]; if (assignedCaches[icache] >= igroup.size()) { panic("%s: icache group %s (%ld caches) is fully used, can't connect more cores to it", name.c_str(), icache.c_str(), igroup.size()); } FilterCache* ic = dynamic_cast<FilterCache*>(igroup[assignedCaches[icache]][0]); assert(ic); ic->setSourceId(coreIdx); ic->setFlags(MemReq::IFETCH | MemReq::NOEXCL); assignedCaches[icache]++; if (assignedCaches[dcache] >= dgroup.size()) { panic("%s: dcache group %s (%ld caches) is fully used, can't connect more cores to it", name.c_str(), dcache.c_str(), dgroup.size()); } FilterCache* dc = dynamic_cast<FilterCache*>(dgroup[assignedCaches[dcache]][0]); assert(dc); dc->setSourceId(coreIdx); assignedCaches[dcache]++; //Build the core if (type == "Simple") { core = new (&simpleCores[j]) SimpleCore(ic, dc, name); } else if (type == "Timing") { uint32_t domain = j*zinfo->numDomains/cores; TimingCore* tcore = new (&timingCores[j]) TimingCore(ic, dc, domain, name); zinfo->eventRecorders[coreIdx] = tcore->getEventRecorder(); zinfo->eventRecorders[coreIdx]->setSourceId(coreIdx); core = tcore; } else { assert(type == "OOO"); OOOCore* ocore = new (&oooCores[j]) OOOCore(ic, dc, name, j); zinfo->eventRecorders[coreIdx] = ocore->getEventRecorder(); zinfo->eventRecorders[coreIdx]->setSourceId(coreIdx); core = ocore; } coreMap[group].push_back(core); coreIdx++; } } else { assert(type == "Null"); for (uint32_t j = 0; j < cores; j++) { stringstream ss; ss << group << "-" << j; g_string name(ss.str().c_str()); Core* core = new (&nullCores[j]) NullCore(name); coreMap[group].push_back(core); coreIdx++; } } } //Check that all the terminal caches are fully connected for (const char* grp : cacheGroupNames) { if (childMap.count(grp) == 0 && assignedCaches[grp] != cMap[grp]->size()) { panic("%s: Terminal cache group not fully connected, %ld caches, %d assigned", grp, cMap[grp]->size(), assignedCaches[grp]); } } //Populate global core info assert(zinfo->numCores == coreIdx); zinfo->cores = gm_memalign<Core*>(CACHE_LINE_BYTES, zinfo->numCores); coreIdx = 0; for (const char* group : coreGroupNames) for (Core* core : coreMap[group]) zinfo->cores[coreIdx++] = core; //Init stats: cores, caches, mem for (const char* group : coreGroupNames) { AggregateStat* groupStat = new AggregateStat(true); groupStat->init(gm_strdup(group), "Core stats"); for (Core* core : coreMap[group]) core->initStats(groupStat); zinfo->rootStat->append(groupStat); } for (const char* group : cacheGroupNames) { AggregateStat* groupStat = new AggregateStat(true); groupStat->init(gm_strdup(group), "Cache stats"); for (vector<BaseCache*>& banks : *cMap[group]) for (BaseCache* bank : banks) bank->initStats(groupStat); zinfo->rootStat->append(groupStat); } //Initialize event recorders //for (uint32_t i = 0; i < zinfo->numCores; i++) eventRecorders[i] = new EventRecorder(); AggregateStat* memStat = new AggregateStat(true); memStat->init("mem", "Memory controller stats"); for (auto mem : mems) mem->initStats(memStat); zinfo->rootStat->append(memStat); //Odds and ends: BuildCacheGroup new'd the cache groups, we need to delete them for (pair<string, CacheGroup*> kv : cMap) delete kv.second; cMap.clear(); info("Initialized system"); }