File indexing completed on 2026-07-26 08:22:18
0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011 import argparse
0012 import csv
0013
0014
0015 import ROOT
0016
0017
0018 def runOnInputs(inputs, func):
0019 """Function running another function on every row in all input CSVs
0020
0021 Argument(s):
0022 inputs -- List of CSV file name, platform name pairs
0023 func -- The function-like-object to run on every CSV row
0024 """
0025
0026
0027 for input in inputs:
0028
0029 with open(input[0], "r") as csvFile:
0030 csvReader = csv.DictReader(csvFile)
0031
0032 for row in csvReader:
0033
0034 func(row)
0035 pass
0036 pass
0037 pass
0038 return
0039
0040
0041 def getSampleNames(inputs):
0042 """Get the union of "sample names" from the inputs
0043
0044 Argument(s):
0045 inputs -- List of CSV file name, platform name pairs
0046
0047 Return:
0048 A list of unique sample names
0049 """
0050
0051
0052 result = []
0053
0054 def collectDirectory(row):
0055 """Collect the directory names from the CSV rows
0056
0057 Argument(s):
0058 row -- A CSV row coming from a CSV DictReader
0059 """
0060
0061 nonlocal result
0062
0063 dirname = row["directory"]
0064 if not dirname in result:
0065 result.append(row["directory"])
0066 pass
0067 return
0068
0069
0070 runOnInputs(inputs, collectDirectory)
0071
0072
0073 return result
0074
0075
0076 def getMaxThreads(inputs):
0077 """Get the maximum number of CPU threads for which (a) measurement(s) exist
0078
0079 Argument(s):
0080 inputs -- List of CSV file name, platform name pairs
0081
0082 Return:
0083 The maximum number of threads found in the input files
0084 """
0085
0086
0087 result = 1
0088
0089 def updateThread(row):
0090 """Find the maximum number of threads in the CSV rows
0091
0092 Argument(s):
0093 row -- A CSV row coming from a CSV DictReader
0094 """
0095
0096 nonlocal result
0097
0098 threads = int(row["threads"])
0099 if threads > result:
0100 result = threads
0101 pass
0102 return
0103
0104
0105 runOnInputs(inputs, updateThread)
0106
0107
0108 return result
0109
0110
0111 def getMinMaxThroughput(inputs, sample=""):
0112 """Get the minimum/maximum throughput values
0113
0114 Argument(s):
0115 inputs -- List of CSV file name, platform name pairs
0116 sample -- A specific sample to look at, or all samples if left empty
0117
0118 Return:
0119 A pair of minimum-maximum throughput values
0120 """
0121
0122
0123 result = [1e10, 0.0]
0124
0125 def findMinMax(row):
0126 """Find the minimum and maximum throughput value in the input files
0127
0128 Argument(s):
0129 row -- A CSV row coming from a CSV DictReader
0130 """
0131
0132 nonlocal sample
0133 nonlocal result
0134
0135
0136 if sample != "" and row["directory"] != sample:
0137 return
0138
0139 throughput = getThroughput(row)
0140 if throughput < result[0]:
0141 result[0] = throughput
0142 pass
0143 if throughput > result[1]:
0144 result[1] = throughput
0145 pass
0146 return
0147
0148
0149 runOnInputs(inputs, findMinMax)
0150
0151
0152 return result
0153
0154
0155 def getThroughput(csvRow):
0156 """Get the throughput in Hz for a given CSV row
0157
0158 Argument(s):
0159 csvRow -- A CSV row coming from a CSV DictReader
0160
0161 Returns:
0162 The throughput value in Hz
0163 """
0164
0165 return float(csvRow["processed_events"]) / (float(csvRow["processing_time"]) * 1e-9)
0166
0167
0168 def configureProfile(profile, platform, graphicsOptions):
0169 """Configure a TProfile object with the appropriate properties
0170
0171 Argument(s):
0172 profile -- The TProfile object to configure
0173 platform -- The platform that the profile represents
0174 graphicsOptions -- The graphics options to use for the profile
0175
0176 Return:
0177 The configured TProfile object
0178 """
0179
0180
0181 profile.SetMarkerStyle(graphicsOptions[platform]["MarkerStyle"])
0182 profile.SetMarkerColor(graphicsOptions[platform]["MarkerColor"])
0183 profile.SetMarkerSize(graphicsOptions[platform]["MarkerSize"])
0184 profile.SetLineColor(graphicsOptions[platform]["LineColor"])
0185
0186
0187 return profile
0188
0189
0190 def drawPerMuSamplePlots(inputs, samples, canvas, output, maxThreads, graphicsOptions):
0191 """Function drawing the per-mu-sample plots
0192
0193 Argument(s):
0194 inputs -- List of CSV file name, platform name pairs
0195 samples -- Names of the (mu) samples to make plots for
0196 canvas -- The TCanvas to draw on
0197 output -- Output PDF file name
0198 maxThreads -- Maximum number of threads to make the plots for
0199 graphicsOptions -- The graphics options to use for the profile
0200 """
0201
0202
0203 globalCounter = 1
0204
0205
0206 firstPage = True
0207
0208
0209 result = []
0210
0211
0212 for sample in samples:
0213
0214
0215 drawOptions = "E1 P"
0216
0217 inputCounter = 0
0218
0219
0220 profiles = []
0221
0222
0223 min_max_throughput = getMinMaxThroughput(inputs, sample)
0224
0225
0226 legend = ROOT.TLegend(0.5, 0.6, 0.9, 0.9)
0227
0228
0229 for input in inputs:
0230
0231
0232 profile = configureProfile(
0233 ROOT.TProfile(
0234 "throughput_%i" % globalCounter,
0235 'Throughput for "%s";CPU Threads;Events/Second' % sample,
0236 maxThreads,
0237 0.5,
0238 maxThreads + 0.5,
0239 ),
0240 input[1],
0241 graphicsOptions,
0242 )
0243 profile.GetYaxis().SetRangeUser(0.0, min_max_throughput[1] * 1.2)
0244 profiles.append(profile)
0245
0246
0247 legend.AddEntry(profile, input[1], "P")
0248
0249
0250 globalCounter += 1
0251 inputCounter += 1
0252
0253
0254 max_throughput = 0.0
0255
0256
0257 with open(input[0], "r") as csvFile:
0258 csvReader = csv.DictReader(csvFile)
0259
0260 for row in csvReader:
0261
0262 if row["directory"] != sample:
0263 continue
0264
0265 throughput = getThroughput(row)
0266 profile.Fill(float(row["threads"]), throughput)
0267 if throughput > max_throughput:
0268 max_throughput = throughput
0269 pass
0270 pass
0271 pass
0272
0273
0274 result.append([sample, input[1], max_throughput])
0275
0276
0277 profile.Draw(drawOptions)
0278 drawOptions = "SAME %s" % drawOptions
0279 pass
0280
0281
0282 legend.Draw()
0283
0284
0285 if firstPage:
0286 canvas.Print("%s(" % output, "pdf")
0287 firstPage = False
0288 else:
0289 canvas.Print(output, "pdf")
0290 pass
0291 pass
0292
0293
0294 return result
0295
0296
0297 def drawMaxThroughputPlot(
0298 inputs, max_throughputs, samples, canvas, output, graphicsOptions
0299 ):
0300 """Draw a single plot with the maximum throughputs on each platform
0301
0302 Argument(s):
0303 inputs -- List of CSV file name, platform name pairs
0304 max_throughputs -- The throughputs collected in drawPerMuSamplePlots()
0305 samples -- Names of the (mu) samples to make plots for
0306 canvas -- The TCanvas to draw on
0307 output -- Output PDF file name
0308 graphicsOptions -- The graphics options to use for the profile
0309 """
0310
0311
0312 platforms = []
0313 for thr in max_throughputs:
0314 if not thr[1] in platforms:
0315 platforms.append(thr[1])
0316 pass
0317 pass
0318
0319
0320 min_max_throughput = getMinMaxThroughput(inputs)
0321
0322
0323 legend = ROOT.TLegend(0.5, 0.6, 0.9, 0.9)
0324
0325
0326
0327 profiles = []
0328
0329
0330 counter = 0
0331
0332
0333 drawOptions = "E1 P L"
0334
0335
0336 canvas.SetLogy()
0337
0338
0339 for platform in platforms:
0340
0341
0342 profile = configureProfile(
0343 ROOT.TProfile(
0344 "max_throughput_profile_%i" % counter,
0345 "Maximum Throughput;;Events/Second",
0346 len(samples),
0347 0.5,
0348 len(samples) + 0.5,
0349 ),
0350 platform,
0351 graphicsOptions,
0352 )
0353 for i in range(len(samples)):
0354 profile.GetXaxis().SetBinLabel(i + 1, samples[i])
0355 pass
0356 profile.GetYaxis().SetRangeUser(
0357 min_max_throughput[0] * 0.2, min_max_throughput[1] * 5.0
0358 )
0359 profiles.append(profile)
0360 counter += 1
0361
0362
0363 legend.AddEntry(profile, platform, "P")
0364
0365
0366 for thr in max_throughputs:
0367
0368
0369 if thr[1] != platform:
0370 continue
0371
0372
0373 profile.Fill(thr[0], thr[2])
0374 pass
0375
0376
0377 profile.Draw(drawOptions)
0378 drawOptions = "SAME %s" % drawOptions
0379 pass
0380
0381
0382 legend.Draw()
0383
0384
0385 canvas.Print("%s)" % output, "pdf")
0386 return
0387
0388
0389 def main():
0390 """C(++)-style main function"""
0391
0392
0393 parser = argparse.ArgumentParser(description="Multi-Threaded Throghput Plotter")
0394 parser.add_argument("csv_file", nargs="+", help="CSV file name:Platform name")
0395 parser.add_argument(
0396 "-o", "--output", help="Output PDF file name", default="output.pdf"
0397 )
0398 parser.add_argument(
0399 "-t",
0400 "--max-threads",
0401 help="Maximum number of CPU threads to plot for",
0402 type=int,
0403 dest="max_threads",
0404 default=-1,
0405 )
0406 args = parser.parse_args()
0407
0408
0409 inputs = []
0410 for arg in args.csv_file:
0411 inputs.append(arg.split(":", 1))
0412 if len(inputs[-1]) != 2:
0413 raise RuntimeError('Could not parse "%s" as "<filename>:<platform>"' % arg)
0414 pass
0415
0416
0417 samples = getSampleNames(inputs)
0418
0419
0420 maxThreads = args.max_threads
0421 if maxThreads <= 0:
0422 maxThreads = getMaxThreads(inputs)
0423 pass
0424
0425
0426 graphicsOptions = {}
0427 counter = 0
0428 for input in inputs:
0429
0430 color = counter + 1
0431
0432 if color == 5:
0433 color += 1
0434 pass
0435
0436 graphicsOptions[input[1]] = {
0437 "MarkerColor": color,
0438 "MarkerStyle": counter + 20,
0439 "MarkerSize": 1,
0440 "LineColor": color,
0441 }
0442 counter += 1
0443 pass
0444
0445
0446 canvas = ROOT.TCanvas("canvas", "Throughput Canvas", 800, 600)
0447 canvas.cd()
0448
0449
0450
0451 maxThroughputs = drawPerMuSamplePlots(
0452 inputs, samples, canvas, args.output, maxThreads, graphicsOptions
0453 )
0454
0455
0456 drawMaxThroughputPlot(
0457 inputs, maxThroughputs, samples, canvas, args.output, graphicsOptions
0458 )
0459
0460
0461 return 0
0462
0463
0464 if __name__ == "__main__":
0465
0466 ROOT.gROOT.SetBatch()
0467
0468 ROOT.gStyle.SetOptStat(False)
0469
0470 import sys
0471
0472 sys.exit(main())