Skip to content

Commit 5f6ffc0

Browse files
authored
Merge pull request #49170 from gartung/gartung-PerTools-AllocMonitorAnalyze
PerfTools/ModuleAllocMonitor: Add source c++type to moduleAllocMonitor.log, sum all source activity for each event in source module
2 parents 39e16a5 + a34af7f commit 5f6ffc0

File tree

3 files changed

+131
-48
lines changed

3 files changed

+131
-48
lines changed

PerfTools/AllocMonitor/plugins/moduleAlloc_setupFile.cc

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -451,9 +451,12 @@ namespace edm::service::moduleAlloc {
451451

452452
auto sourceCtrPtr = std::make_shared<ModuleCtrDtr>();
453453
auto& sourceCtr = *sourceCtrPtr;
454-
iRegistry.watchPreSourceConstruction([&sourceCtr, beginTime, iFilter](auto const&) {
454+
auto sourceTypePtr = std::make_shared<std::string>("Unknown");
455+
iRegistry.watchPreSourceConstruction([&sourceCtr, sourceTypePtr, beginTime, iFilter](auto const& md) {
455456
auto const t = duration_cast<duration_t>(now() - beginTime).count();
456457
sourceCtr.beginConstruction = t;
458+
// Capture source module type information
459+
*sourceTypePtr = md.moduleName();
457460
iFilter->startOnThread();
458461
});
459462
iRegistry.watchPostSourceConstruction([&sourceCtr, beginTime, iFilter](auto const&) {
@@ -495,6 +498,7 @@ namespace edm::service::moduleAlloc {
495498
esModuleTypesPtr,
496499
moduleCtrDtrPtr,
497500
sourceCtrPtr,
501+
sourceTypePtr = std::move(sourceTypePtr),
498502
beginTime,
499503
beginModuleAlloc,
500504
addDataInDtr](auto&) mutable {
@@ -515,6 +519,14 @@ namespace edm::service::moduleAlloc {
515519
esModuleLabelsPtr.reset();
516520
esModuleTypesPtr.reset();
517521
}
522+
{
523+
std::ostringstream oss;
524+
std::vector<std::string> sourceTypeList{1, *sourceTypePtr};
525+
std::vector<std::string> sourceNames{1, "source"};
526+
moduleIdToLabelAndType(oss, sourceNames, sourceTypeList, 'S', "Source ID", "Source label", "Source type");
527+
logFile->write(oss.str());
528+
sourceTypePtr.reset();
529+
}
518530
{
519531
auto const moduleAllocStart = duration_cast<duration_t>(beginModuleAlloc - beginTime).count();
520532
auto msg = assembleMessage<Step::preFrameworkTransition>(

PerfTools/AllocMonitor/scripts/edmModuleAllocJsonToCircles.py

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,46 @@ def processModuleTransition(moduleLabel, moduleType, moduleInfo, transitionType,
3838
Any missing field defaults to 0.
3939
4040
Note: Entries with record names are excluded as they belong to EventSetup transition only.
41+
42+
For the "source" module and "event" transition, sum all alloc records with the same
43+
run, lumi, and event number.
4144
"""
4245
moduleKey = UniqueKey(moduleLabel, moduleType, "")
4346
moduleTransition[moduleKey] = {"cpptype": moduleType, "allocs": []}
44-
for entry in moduleInfo:
45-
# Only process entries that match the transition type AND don't have record names
46-
# (entries with record names are EventSetup only)
47-
if (entry.get("transition", None) == transitionType and
48-
not ("record" in entry and "name" in entry["record"])):
49-
moduleTransition[moduleKey]["allocs"].append(entry.get("alloc", {}))
47+
48+
# Special handling for "source" module with "event" transition
49+
if moduleLabel == "source" and transitionType == "event":
50+
# Group entries by (run, lumi, event)
51+
event_groups = {}
52+
for entry in moduleInfo:
53+
if (entry.get("transition", None) == transitionType and
54+
not ("record" in entry and "name" in entry["record"])):
55+
sync = entry.get("sync", {})
56+
key = (sync.get("run", 0), sync.get("lumi", 0), sync.get("event", 0))
57+
if key not in event_groups:
58+
event_groups[key] = []
59+
event_groups[key].append(entry.get("alloc", {}))
60+
61+
# Sum allocations for each event group
62+
for event_key, allocs in event_groups.items():
63+
summed_alloc = {
64+
"added": sum(a.get("added", 0) for a in allocs),
65+
"nAlloc": sum(a.get("nAlloc", 0) for a in allocs),
66+
"nDealloc": sum(a.get("nDealloc", 0) for a in allocs),
67+
"maxTemp": sum(a.get("maxTemp", 0) for a in allocs),
68+
"max1Alloc": sum(a.get("max1Alloc", 0) for a in allocs)
69+
}
70+
moduleTransition[moduleKey]["allocs"].append(summed_alloc)
71+
else:
72+
# Original processing for other modules/transitions
73+
for entry in moduleInfo:
74+
# Only process entries that match the transition type AND don't have record names
75+
# (entries with record names are EventSetup only)
76+
if (entry.get("transition", None) == transitionType and
77+
not ("record" in entry and "name" in entry["record"]) and
78+
entry.get("activity") == "process"):
79+
moduleTransition[moduleKey]["allocs"].append(entry.get("alloc", {}))
80+
5081
moduleTransition[moduleKey]["nTransitions"] = len(moduleTransition[moduleKey]["allocs"])
5182

5283
def processESModuleTransition(moduleLabel, moduleType, moduleInfo, moduleTransition):
@@ -206,6 +237,7 @@ def formatToCircles(moduleTransitions):
206237
eventCount = moduleTransitions['event'].get(eventKey, {}).get("nTransitions", 0)
207238
# Set events to 1 if it's 0 to prevent NaNs in Circles visualization
208239
module["events"] = max(eventCount, 1)
240+
doc["total"]["events"] = max(doc["total"]["events"], module["events"])
209241
doc["modules"].append(module)
210242

211243
return doc
@@ -237,8 +269,9 @@ def main(args):
237269
for moduleLabel, moduleInfo in doc["modules"].items():
238270
processESModuleTransition(moduleLabel, moduleTypes[moduleLabel], moduleInfo, moduleTransition)
239271
else:
272+
# Process the "source" module
273+
processModuleTransition("source", moduleTypes["source"], doc["source"], transition, moduleTransition)
240274
# Regular transition processing
241-
processModuleTransition("source", "PoolSource", doc["source"], transition, moduleTransition)
242275
for moduleLabel, moduleInfo in doc["modules"].items():
243276
processModuleTransition(moduleLabel, moduleTypes[moduleLabel], moduleInfo, transition, moduleTransition)
244277
moduleTransitions[transition] = moduleTransition

PerfTools/AllocMonitor/scripts/edmModuleAllocMonitorAnalyze.py

Lines changed: 78 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -19,29 +19,29 @@ def printHelp():
1919
This script will output a more human readable form of the data in the log file.'''
2020
return s
2121

22-
#these values come from moduleALloc_setupFile.cc
23-
#enum class Step : char {
24-
# preSourceTransition = 'S',
25-
# postSourceTransition = 's',
26-
# preModulePrefetching = 'P',
27-
# postModulePrefetching = 'p',
28-
# preModuleEventAcquire = 'A',
29-
# postModuleEventAcquire = 'a',
30-
# preModuleTransition = 'M',
31-
# preEventReadFromSource = 'R',
32-
# postEventReadFromSource = 'r',
33-
# preModuleEventDelayedGet = 'D',
34-
# postModuleEventDelayedGet = 'd',
35-
# postModuleTransition = 'm',
36-
# preESModulePrefetching = 'Q',
37-
# postESModulePrefetching = 'q',
38-
# preESModule = 'N',
39-
# postESModule = 'n',
40-
# preESModuleAcquire = 'B',
41-
# postESModuleAcquire = 'b',
42-
# preFrameworkTransition = 'F',
43-
# postFrameworkTransition = 'f'
44-
#};
22+
# These values come from moduleAlloc_setupFile.cc
23+
# enum class Step : char {
24+
# preSourceTransition = 'S',
25+
# postSourceTransition = 's',
26+
# preModulePrefetching = 'P',
27+
# postModulePrefetching = 'p',
28+
# preModuleEventAcquire = 'A',
29+
# postModuleEventAcquire = 'a',
30+
# preModuleTransition = 'M',
31+
# preEventReadFromSource = 'R',
32+
# postEventReadFromSource = 'r',
33+
# preModuleEventDelayedGet = 'D',
34+
# postModuleEventDelayedGet = 'd',
35+
# postModuleTransition = 'm',
36+
# preESModulePrefetching = 'Q',
37+
# postESModulePrefetching = 'q',
38+
# preESModule = 'N',
39+
# postESModule = 'n',
40+
# preESModuleAcquire = 'B',
41+
# postESModuleAcquire = 'b',
42+
# preFrameworkTransition = 'F',
43+
# postFrameworkTransition = 'f'
44+
# };
4545

4646

4747
kMicroToSec = 0.000001
@@ -192,6 +192,16 @@ def textPrefix_(time, indentLevel):
192192
return f'{time:>11} '+"++"*indentLevel
193193

194194
class AllocInfo(object):
195+
"""Container for memory allocation information from CMSSW module transitions.
196+
197+
Attributes:
198+
nAllocs: Number of memory allocations
199+
nDeallocs: Number of memory deallocations
200+
added: Net memory added (in bytes)
201+
minTemp: Minimum temporary memory usage
202+
maxTemp: Maximum temporary memory usage
203+
max1Alloc: Largest single allocation
204+
"""
195205
def __init__(self,payload):
196206
self.nAllocs = int(payload[0])
197207
self.nDeallocs = int(payload[1])
@@ -333,6 +343,7 @@ def toSimpleDict(self):
333343
l = None
334344
if m == 'source':
335345
l = dct['source']
346+
cpptypes[m]=self._cpptypes[m]
336347
elif m == 'clearEvent':
337348
l = dct['clearEvent']
338349
else:
@@ -526,7 +537,7 @@ def jsonVisInfo(self, data):
526537

527538

528539
class SourceTransitionParser(object):
529-
def __init__(self, payload):
540+
def __init__(self, payload, sourceInfos):
530541
self.transition = int(payload[0])
531542
if self.transition == Phase.getNextTransition:
532543
self.time = int(payload[1])
@@ -556,9 +567,10 @@ def text(self, context):
556567
return f'{self.textPrefix()} {self.textSpecial()}: {self.textPostfix()}'
557568

558569
class PreSourceTransitionParser(SourceTransitionParser):
559-
def __init__(self, payload, moduleCentric):
570+
def __init__(self, payload, sourceInfos, moduleCentric):
560571
self._moduleCentric = moduleCentric
561-
super().__init__(payload)
572+
super().__init__(payload, sourceInfos)
573+
self._sourceInfo = sourceInfos[0]
562574
def textSpecial(self):
563575
return "starting"
564576
def jsonInfo(self, syncs, temp, data):
@@ -607,22 +619,23 @@ def jsonVisInfo(self, data):
607619
data.findOpenSlotInModGlobals(index,0).append(container[-1])
608620

609621
class PostSourceTransitionParser(SourceTransitionParser):
610-
def __init__(self, payload, moduleCentric):
611-
super().__init__(payload)
622+
def __init__(self, payload, sourceInfos, moduleCentric):
623+
super().__init__(payload, sourceInfos)
612624
if self.index == -1:
613625
self.allocInfo = AllocInfo(payload[2:])
614626
else:
615627
self.allocInfo = AllocInfo(payload[3:])
616628
self._moduleCentric = moduleCentric
629+
self._sourceInfo = sourceInfos[0]
617630
def textSpecial(self):
618631
return "finished"
619632
def jsonInfo(self, syncs, temp, data):
620633
start = temp.findTime("source", self.transition, self.index)
621634
#we do not know the sync yet so have to wait until the framework transition
622635
if self.transition in [ Phase.construction, Phase.getNextTransition, Phase.destruction, Phase.openFile]:
623-
data.insert( "source" , "PoolSource", start, self.time, self.transition, self.index, (0,) , Activity.process, self.allocInfo)
636+
data.insert( "source" , self._sourceInfo._cpptype, start, self.time, self.transition, self.index, (0,) , Activity.process, self.allocInfo)
624637
else:
625-
data.insert( "source" , "PoolSource", start, self.time, self.transition, self.index, self.index , Activity.process, self.allocInfo)
638+
data.insert( "source" , self._sourceInfo._cpptype, start, self.time, self.transition, self.index, self.index , Activity.process, self.allocInfo)
626639
def jsonVisInfo(self, data):
627640
index = self.index
628641
if self.transition == Phase.Event:
@@ -812,7 +825,7 @@ def jsonVisInfo(self, data):
812825
return self._postJsonVis(data, self.allocInfo)
813826
def jsonInfo(self, syncs, temp, data):
814827
start = temp.findTime(self.moduleInfo._name+'source', self.transition, self.index)
815-
data.insert( "source" , "PoolSource", start, self.time, self.transition, self.index, syncs.get(self.transition, self.index) , Activity.delayedGet, self.allocInfo)
828+
data.insert( "source" , self.moduleInfo._cpptype, start, self.time, self.transition, self.index, syncs.get(self.transition, self.index) , Activity.delayedGet, self.allocInfo)
816829

817830
class ESModuleTransitionParser(object):
818831
def __init__(self, payload, moduleInfos, esModuleInfos, recordNames):
@@ -900,16 +913,16 @@ def jsonVisInfo(self, data):
900913
return self._postJsonVis(data, self.allocInfo)
901914

902915

903-
def lineParserFactory (step, payload, moduleInfos, esModuleInfos, recordNames, moduleCentric):
916+
def lineParserFactory (step, payload, moduleInfos, esModuleInfos, sourceInfos, recordNames, moduleCentric):
904917
if step == 'F':
905918
parser = PreFrameworkTransitionParser(payload)
906919
return parser
907920
if step == 'f':
908921
return PostFrameworkTransitionParser(payload)
909922
if step == 'S':
910-
return PreSourceTransitionParser(payload, moduleCentric)
923+
return PreSourceTransitionParser(payload, sourceInfos, moduleCentric)
911924
if step == 's':
912-
return PostSourceTransitionParser(payload, moduleCentric)
925+
return PostSourceTransitionParser(payload, sourceInfos, moduleCentric)
913926
if step == 'M':
914927
return PreEDModuleTransitionParser(payload, moduleInfos, moduleCentric)
915928
if step == 'm':
@@ -934,18 +947,18 @@ def lineParserFactory (step, payload, moduleInfos, esModuleInfos, recordNames, m
934947
return PreESModuleAcquireParser(payload, moduleInfos, esModuleInfos, recordNames)
935948
if step == 'b':
936949
return PostESModuleAcquireParser(payload, moduleInfos, esModuleInfos, recordNames)
937-
raise LogicError("Unknown step '{}'".format(step))
950+
raise ValueError("Unknown step '{}'".format(step))
938951

939952
#----------------------------------------------
940-
def processingStepsFromFile(f, moduleInfos, esModuleInfos, recordNames, moduleCentric):
953+
def processingStepsFromFile(f, moduleInfos, esModuleInfos, sourceInfos, recordNames, moduleCentric):
941954
for rawl in f:
942955
l = rawl.strip()
943956
if not l or l[0] == '#':
944957
continue
945958
(step,payload) = tuple(l.split(None,1))
946959
payload=payload.split()
947960

948-
parser = lineParserFactory(step, payload, moduleInfos, esModuleInfos, recordNames, moduleCentric)
961+
parser = lineParserFactory(step, payload, moduleInfos, esModuleInfos, sourceInfos, recordNames, moduleCentric)
949962
if parser:
950963
yield parser
951964
return
@@ -958,6 +971,7 @@ def __init__(self,f, moduleCentric):
958971
moduleInfos = {}
959972
esModuleInfos = {}
960973
recordNames = {}
974+
sourceInfos = {}
961975
for rawl in f:
962976
l = rawl.strip()
963977
if l and l[0] == 'M':
@@ -978,6 +992,10 @@ def __init__(self,f, moduleCentric):
978992
(id,name,mType)=tuple(l[2:].split())
979993
esModuleInfos[int(id)] = ModuleInfo(name,mType)
980994
continue
995+
if len(l) > 5 and l[0:2] == "#S":
996+
(id,name,sType)=tuple(l[2:].split())
997+
sourceInfos[0] = ModuleInfo(name,sType)
998+
continue
981999
if len(l) > 5 and l[0:2] == "#R":
9821000
(id,name)=tuple(l[2:].split())
9831001
recordNames[int(id)] = name
@@ -990,6 +1008,7 @@ def __init__(self,f, moduleCentric):
9901008
self.numStreams =numStreams
9911009
self._moduleInfos = moduleInfos
9921010
self._esModuleInfos = esModuleInfos
1011+
self._sourceInfos = sourceInfos
9931012
self._recordNames = recordNames
9941013
self.maxNameSize =0
9951014
for n in moduleInfos.items():
@@ -1004,12 +1023,21 @@ def processingSteps(self):
10041023
Using a generator reduces the memory overhead when parsing a large file.
10051024
"""
10061025
self._f.seek(0)
1007-
return processingStepsFromFile(self._f,self._moduleInfos, self._esModuleInfos, self._recordNames, self._moduleCentric)
1026+
return processingStepsFromFile(self._f,self._moduleInfos, self._esModuleInfos, self._sourceInfos, self._recordNames, self._moduleCentric)
10081027

10091028
def textOutput( parser ):
10101029
context = {}
10111030
for p in parser.processingSteps():
10121031
print(p.text(context))
1032+
1033+
def showSourceTypes( parser ):
1034+
print("Source Module Types:")
1035+
print("===================")
1036+
if hasattr(parser, '_sourceInfos') and parser._sourceInfos:
1037+
for id, info in parser._sourceInfos.items():
1038+
print(f" Source ID {id}: {info._name} (type: {info._cpptype})")
1039+
else:
1040+
print(" No source module types found in log file.")
10131041

10141042
class VisualizationContainers(object):
10151043
def __init__(self):
@@ -1281,7 +1309,7 @@ def jsonVisualizationInfo(parser):
12811309
time += t["finish"]-t["start"]
12821310
modules[-1]['time']=time
12831311
modules.sort(key= lambda x : x['time'], reverse=True)
1284-
final['transitions'].append({"name": "source", "slots":sourceSlot})
1312+
final['transitions'].append({"name": "source", "cpptype": parser._sourceInfos[0]._cpptype, "slots":sourceSlot})
12851313
for m in modules:
12861314
final['transitions'].append(m)
12871315

@@ -1327,6 +1355,7 @@ def incr(t):
13271355
'#R 1 Record',
13281356
'#M 1 Module ModuleType',
13291357
'#N 1 ESModule ESModuleType',
1358+
'#S 1 Source SourceType',
13301359
f'F {Phase.startTracing} 0 0 0 0 {incr(t)}',
13311360
f'S {Phase.construction} 0 {incr(t)}',
13321361
f's {Phase.construction} 0 {incr(t)} 1 1 10 0 10 10',
@@ -1493,6 +1522,7 @@ def runTests():
14931522
formatter_class=argparse.RawDescriptionHelpFormatter,
14941523
epilog=printHelp())
14951524
parser.add_argument('filename',
1525+
nargs='?', # Make filename optional
14961526
type=argparse.FileType('r'), # open file
14971527
help='file to process')
14981528
parser.add_argument('-j', '--json',
@@ -1501,7 +1531,7 @@ def runTests():
15011531
parser.add_argument('-s', '--sortBy',
15021532
default = '',
15031533
type = str,
1504-
help="sort modules by attribute. Alloed values 'nAllocs', 'nDeallocs', 'added', 'minTemp', maxTemp', and 'max1Alloc'")
1534+
help="sort modules by attribute. Allowed values 'nAllocs', 'nDeallocs', 'added', 'minTemp', 'maxTemp', and 'max1Alloc'")
15051535
# parser.add_argument('-w', '--web',
15061536
# action='store_true',
15071537
# help='''Writes data.js file that can be used with the web based inspector. To use, copy directory ${CMSSW_RELEASE_BASE}/src/FWCore/Services/template/web to a web accessible area and move data.js into that directory.''')
@@ -1511,11 +1541,17 @@ def runTests():
15111541
parser.add_argument('-T', '--test',
15121542
action='store_true',
15131543
help='''Run internal tests.''')
1544+
parser.add_argument('--showSourceTypes',
1545+
action='store_true',
1546+
help='''Display source module types.''')
15141547

15151548
args = parser.parse_args()
15161549

15171550
if args.test:
15181551
runTests()
1552+
elif args.filename is None:
1553+
parser.print_help()
1554+
sys.exit(1)
15191555
else :
15201556
parser = ModuleAllocCompactFileParser(args.filename, not args.timeOrdered)
15211557
# if args.json or args.web:
@@ -1528,5 +1564,7 @@ def runTests():
15281564
# f.close()
15291565
elif args.sortBy:
15301566
print(json.dumps(sortByAttribute(parser, args.sortBy), indent=2))
1567+
elif args.showSourceTypes:
1568+
showSourceTypes(parser)
15311569
else:
15321570
textOutput(parser)

0 commit comments

Comments
 (0)