Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-07-26 08:22:18

0001 #!/usr/bin/env python3
0002 #
0003 # TRACCC library, part of the ACTS project (R&D line)
0004 #
0005 # (c) 2023 CERN for the benefit of the ACTS project
0006 #
0007 # Mozilla Public License Version 2.0
0008 #
0009 
0010 # Python import(s).
0011 import argparse
0012 import csv
0013 
0014 # ROOT import(s).
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     # Loop over the files.
0027     for input in inputs:
0028         # Open this particular file.
0029         with open(input[0], "r") as csvFile:
0030             csvReader = csv.DictReader(csvFile)
0031             # Process each row of the file.
0032             for row in csvReader:
0033                 # Execute the received function on this row.
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     # The result list.
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         # Use the "result" variable from the parent scope.
0061         nonlocal result
0062         # If this directory was not seen yet, add it to the result.
0063         dirname = row["directory"]
0064         if not dirname in result:
0065             result.append(row["directory"])
0066             pass
0067         return
0068 
0069     # Collect the directory names.
0070     runOnInputs(inputs, collectDirectory)
0071 
0072     # Return the list.
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     # The result value.
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         # Use the "result" variable from the parent scope.
0096         nonlocal result
0097         # If we see a value larger than what we saw before, let's use it.
0098         threads = int(row["threads"])
0099         if threads > result:
0100             result = threads
0101             pass
0102         return
0103 
0104     # Find the maximum number of threads.
0105     runOnInputs(inputs, updateThread)
0106 
0107     # Return the value.
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     # The result pair.
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         # Use the variable(s) from the parent scope.
0132         nonlocal sample
0133         nonlocal result
0134         # Skip the current row if it's not coming from the appropriate
0135         # directory/sample.
0136         if sample != "" and row["directory"] != sample:
0137             return
0138         # Update the min/max values.
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     # Find the minimum-maximum throughput values.
0149     runOnInputs(inputs, findMinMax)
0150 
0151     # Return the minimum-maximum pair.
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     # Set all (relevant) properties on the profile object.
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     # Return the configured TProfile.
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     # Dummy counter used in giving unique names to the profile objects.
0203     globalCounter = 1
0204 
0205     # Flag helping with the PDF writing.
0206     firstPage = True
0207 
0208     # The result list with maximum througputs per sample, per platform.
0209     result = []
0210 
0211     # Loop over the samples.
0212     for sample in samples:
0213 
0214         # Helper variable for the draw option(s).
0215         drawOptions = "E1 P"
0216         # Helper counter for the inputs.
0217         inputCounter = 0
0218         # List of temporary profile objects. These need to stay alive until the
0219         # canvas is printed.
0220         profiles = []
0221 
0222         # Get minimum-maximum throghput values for this sample.
0223         min_max_throughput = getMinMaxThroughput(inputs, sample)
0224 
0225         # The legend for all of the platforms used.
0226         legend = ROOT.TLegend(0.5, 0.6, 0.9, 0.9)
0227 
0228         # Loop over the inputs.
0229         for input in inputs:
0230 
0231             # Create a TProfile for the results.
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             # Add the profile to the legend.
0247             legend.AddEntry(profile, input[1], "P")
0248 
0249             # Increment the counter(s).
0250             globalCounter += 1
0251             inputCounter += 1
0252 
0253             # Get the maximum throughput for this sample and platform.
0254             max_throughput = 0.0
0255 
0256             # Open the input file.
0257             with open(input[0], "r") as csvFile:
0258                 csvReader = csv.DictReader(csvFile)
0259                 # Process each row of the file.
0260                 for row in csvReader:
0261                     # Skip measurements from samples other than the current one.
0262                     if row["directory"] != sample:
0263                         continue
0264                     # Add the result to the plot.
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             # Save the max throughput.
0274             result.append([sample, input[1], max_throughput])
0275 
0276             # Draw the profile on the canvas.
0277             profile.Draw(drawOptions)
0278             drawOptions = "SAME %s" % drawOptions
0279             pass
0280 
0281         # Draw the legend.
0282         legend.Draw()
0283 
0284         # Save the page with all the profiles.
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     # Return the max throughputs for each sample and platform.
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     # Collect the platform names, and the minimum-maximum values.
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     # Get the absolute minimum-maximum throughput values.
0320     min_max_throughput = getMinMaxThroughput(inputs)
0321 
0322     # The legend for all of the platforms used.
0323     legend = ROOT.TLegend(0.5, 0.6, 0.9, 0.9)
0324 
0325     # List of temporary profile objects. These need to stay alive until the
0326     # canvas is printed.
0327     profiles = []
0328 
0329     # Helper counter.
0330     counter = 0
0331 
0332     # Helper variable for the draw option(s).
0333     drawOptions = "E1 P L"
0334 
0335     # Display the maximum throughputs in logarithmic scale.
0336     canvas.SetLogy()
0337 
0338     # Create one profile per platform.
0339     for platform in platforms:
0340 
0341         # Create the profile.
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         # Add the profile to the legend.
0363         legend.AddEntry(profile, platform, "P")
0364 
0365         # Fill it with data from the previous maximum collection.
0366         for thr in max_throughputs:
0367 
0368             # Skip other platforms.
0369             if thr[1] != platform:
0370                 continue
0371 
0372             # Fill the profile.
0373             profile.Fill(thr[0], thr[2])
0374             pass
0375 
0376         # Draw the profile on the canvas.
0377         profile.Draw(drawOptions)
0378         drawOptions = "SAME %s" % drawOptions
0379         pass
0380 
0381     # Draw the legend.
0382     legend.Draw()
0383 
0384     # Save the page with all the profiles.
0385     canvas.Print("%s)" % output, "pdf")
0386     return
0387 
0388 
0389 def main():
0390     """C(++)-style main function"""
0391 
0392     # Parse the command line arguments.
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     # Parse the CSV file argument(s).
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     # Get the union of "sample names" from all the inputs.
0417     samples = getSampleNames(inputs)
0418 
0419     # Figure out how many threads to show the results for.
0420     maxThreads = args.max_threads
0421     if maxThreads <= 0:
0422         maxThreads = getMaxThreads(inputs)
0423         pass
0424 
0425     # Associate ROOT graphical properties with all of the platforms.
0426     graphicsOptions = {}
0427     counter = 0
0428     for input in inputs:
0429         # Choose a color.
0430         color = counter + 1
0431         # Skip yellow...
0432         if color == 5:
0433             color += 1
0434             pass
0435         # Save the options.
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     # Create a canvas to draw on.
0446     canvas = ROOT.TCanvas("canvas", "Throughput Canvas", 800, 600)
0447     canvas.cd()
0448 
0449     # Draw the per-mu-sample througput plots. The return value is a list
0450     # of best througput values per mu value for each platform.
0451     maxThroughputs = drawPerMuSamplePlots(
0452         inputs, samples, canvas, args.output, maxThreads, graphicsOptions
0453     )
0454 
0455     # Draw the maximum throughput values for each mu value, for each platform.
0456     drawMaxThroughputPlot(
0457         inputs, maxThroughputs, samples, canvas, args.output, graphicsOptions
0458     )
0459 
0460     # Return gracefully.
0461     return 0
0462 
0463 
0464 if __name__ == "__main__":
0465     # Set ROOT into batch mode.
0466     ROOT.gROOT.SetBatch()
0467     # Set some global ROOT style option(s).
0468     ROOT.gStyle.SetOptStat(False)
0469     # Execute the main() function.
0470     import sys
0471 
0472     sys.exit(main())