Source code for CIME.get_timing

#!/usr/bin/env python3

"""
Library for implementing getTiming tool which gets timing
information from a run.
"""

from CIME.XML.standard_module_setup import *
from CIME.utils import safe_copy

import datetime, re

logger = logging.getLogger(__name__)


class _GetTimingInfo:
    def __init__(self, name):
        self.name = name
        self.tmin = 0
        self.tmax = 0
        self.adays = 0


class _TimingParser:
    def __init__(self, case, lid="999999-999999"):
        self.case = case
        self.caseroot = case.get_value("CASEROOT")
        self.lid = lid
        self.finlines = None
        self.fout = None
        self.adays = 0
        self._driver = case.get_value("COMP_INTERFACE")
        self.models = {}
        self.ncount = 0
        self.nprocs = 0
        self.version = -1

    def write(self, text):
        self.fout.write(text)

    def prttime(self, label, offset=None, div=None, coff=-999):
        if offset is None:
            offset = self.models["CPL"].offset
        if div is None:
            div = self.adays
        datalen = 20
        cstr = "<---->"
        clen = len(cstr)

        minval, maxval, found = self.gettime(label)
        if div >= 1.0:
            mind = minval / div
            maxd = maxval / div
        else:
            mind = minval
            maxd = maxval

        pstrlen = 25
        if mind >= 0 and maxd >= 0 and found:
            if coff >= 0:
                zoff = pstrlen + coff + int((datalen - clen) / 2)
                csp = offset - coff - int((datalen - clen) / 2)
                self.write(
                    " {label:<{width1}}{cstr:<{width2}} {minv:8.3f}:{maxv:8.3f} \n".format(
                        label=label,
                        width1=zoff,
                        cstr=cstr,
                        width2=csp,
                        minv=mind,
                        maxv=maxd,
                    )
                )
            else:
                zoff = pstrlen + offset
                self.write(
                    " {label:<{width1}} {minv:8.3f}:{maxv:8.3f} \n".format(
                        label=label, width1=zoff, minv=mind, maxv=maxd
                    )
                )

    def gettime2(self, heading_padded):
        if self._driver == "mct" or self._driver == "moab":
            return self._gettime2_mct(heading_padded)
        elif self._driver == "nuopc":
            if self.version < 0:
                self._get_esmf_profile_version()
            return self._gettime2_nuopc()

    def _gettime2_mct(self, heading_padded):
        nprocs = 0
        ncount = 0

        heading = '"' + heading_padded.strip() + '"'
        for line in self.finlines:
            m = re.match(r"\s*{}\s+\S\s+(\d+)\s*\d+\s*(\S+)".format(heading), line)
            if m:
                nprocs = int(float(m.groups()[0]))
                ncount = int(float(m.groups()[1]))
                return (nprocs, ncount)
            else:
                m = re.match(r"\s*{}\s+\S\s+(\d+)\s".format(heading), line)
                if m:
                    nprocs = 1
                    ncount = int(float(m.groups()[0]))
                    return (nprocs, ncount)
        return (0, 0)

    def _gettime2_nuopc(self):
        self.nprocs = 0
        self.ncount = 0
        if self.version < 0:
            self._get_esmf_profile_version()
        if self.version == 0:
            expression = re.compile(r"\s*\[ATM]\s*RunPhase1\s+(\d+)\s+(\d+)")
        else:
            expression = re.compile(r"\s*\[ATM]\s*RunPhase1\s+\d+\s+(\d+)\s+(\d+)")

        for line in self.finlines:
            match = expression.match(line)
            if match:
                self.nprocs = int(match.group(1))
                self.ncount = int(match.group(2))
                return (self.nprocs, self.ncount)

        return (0, 0)

    def gettime(self, heading_padded):
        if self._driver == "mct" or self._driver == "moab":
            return self._gettime_mct(heading_padded)
        elif self._driver == "nuopc":
            if self.version < 0:
                self._get_esmf_profile_version()
            return self._gettime_nuopc(heading_padded)

    def _gettime_mct(self, heading_padded):
        found = False
        heading = '"' + heading_padded.strip() + '"'
        minval = 0
        maxval = 0
        for line in self.finlines:
            m = re.match(
                r"\s*{}\s+\S\s+\d+\s*\d+\s*\S+\s*\S+\s*(\d*\.\d+)\s*\(.*\)\s*(\d*\.\d+)\s*\(.*\)".format(
                    heading
                ),
                line,
            )
            if m:
                maxval = float(m.groups()[0])
                minval = float(m.groups()[1])
                found = True
                return (minval, maxval, found)
        return (0, 0, False)

    def _get_esmf_profile_version(self):
        """
        Prior to ESMF8_3_0_beta_snapshot_04 the PEs column was not in ESMF_Profile.summary
        this routine looks for that in the header field to determine if this file was produced
        by a newer (version 1) or older (version 0) ESMF library.
        """
        expect(self.finlines, " No ESMF_Profile.summary file found")
        for line in self.finlines:
            if line.startswith("Region"):
                if "PEs" in line:
                    self.version = 1
                else:
                    self.version = 0

    def _gettime_nuopc(self, heading, instance="0001"):
        if instance == "":
            instance = "0001"
        minval = 0
        maxval = 0
        m = None
        timeline = []
        #  PETs   Count    Mean (s)    Min (s)     Min PET Max (s)     Max PET
        timeline.append(
            re.compile(
                r"\s*{}\s+\d+\s+\d+\s+(\d*\.\d+)\s+(\d*\.\d+)\s+\d+\s+(\d*\.\d+)\s+\d+".format(
                    re.escape(heading)
                )
            )
        )
        #  PETs   PEs  Count    Mean (s)    Min (s)     Min PET Max (s)     Max PET
        timeline.append(
            re.compile(
                r"\s*{}\s+\d+\s+\d+\s+\d+\s+(\d*\.\d+)\s+(\d*\.\d+)\s+\d+\s+(\d*\.\d+)\s+\d+".format(
                    re.escape(heading)
                )
            )
        )
        phase = None
        for line in self.finlines:
            phase = self._get_nuopc_phase(line, instance, phase)
            if phase != "run" and not "[ensemble]" in heading:
                continue
            if heading in line:
                m = timeline[self.version].match(line)
                if m:
                    minval = float(m.group(2))
                    maxval = float(m.group(3))
                    return (minval, maxval, True)
                else:
                    expect(False, "Parsing error in ESMF_Profile.summary file")

        return (0, 0, False)

    @staticmethod
    def _get_nuopc_phase(line, instance, phase):
        if "[ensemble] Init 1" in line:
            phase = "init"
        elif "[ESM" + instance + "] RunPhase1" in line:
            phase = "run"
        elif "[ESM" + instance + "] Finalize" in line:
            phase = "finalize"
        elif "[ESM" in line and "RunPhase1" in line:
            phase = "other"
        return phase

    def getMEDtime(self, instance):
        if instance == "":
            instance = "0001"

        med_phase_line = []
        med_connector_line = []
        med_fraction_line = []
        med_phase_line.append(
            re.compile(r"\s*(\[MED\] med_phases\S+)\s+\d+\s+\d+\s+(\d*\.\d+)\s+")
        )
        med_connector_line.append(
            re.compile(r"\s*(\[MED\] med_connectors\S+)\s+\d+\s+\d+\s+(\d*\.\d+)\s+")
        )
        med_fraction_line.append(
            re.compile(r"\s*(\[MED\] med_fraction\S+)\s+\d+\s+\d+\s+(\d*\.\d+)\s+")
        )
        med_phase_line.append(
            re.compile(r"\s*(\[MED\] med_phases\S+)\s+\d+\s+\d+\s+\d+\s+(\d*\.\d+)\s+")
        )
        med_connector_line.append(
            re.compile(
                r"\s*(\[MED\] med_connectors\S+)\s+\d+\s+\d+\s+\d+\s+(\d*\.\d+)\s+"
            )
        )
        med_fraction_line.append(
            re.compile(
                r"\s*(\[MED\] med_fraction\S+)\s+\d+\s+\d+\s+\d+\s+(\d*\.\d+)\s+"
            )
        )

        m = None
        minval = 0
        maxval = 0
        phase = None
        for line in self.finlines:
            phase = self._get_nuopc_phase(line, instance, phase)
            if phase != "run":
                continue
            m = med_phase_line[self.version].match(line)
            if not m:
                m = med_connector_line[self.version].match(line)
            if not m:
                m = med_fraction_line[self.version].match(line)
            if m:
                minval += float(m.group(2))
                maxval += float(m.group(2))

        return (minval, maxval)

    def getCOMMtime(self, instance):
        if instance == "":
            instance = "0001"
        comm_line = []
        comm_line.append(
            re.compile(r"\s*(\[\S+-TO-\S+\] RunPhase1)\s+\d+\s+\d+\s+(\d*\.\d+)\s+")
        )
        comm_line.append(
            re.compile(
                r"\s*(\[\S+-TO-\S+\] RunPhase1)\s+\d+\s+\d+\s+\d+\s+(\d*\.\d+)\s+"
            )
        )
        m = None
        maxval = 0
        phase = None
        for line in self.finlines:
            phase = self._get_nuopc_phase(line, instance, phase)
            if phase != "run":
                continue
            m = comm_line[self.version].match(line)
            if m:
                heading = m.group(1)
                maxv = float(m.group(2))
                maxval += maxv
                logger.debug("{} time={} sum={}".format(heading, maxv, maxval))
        return maxval

    def getTiming(self):
        ninst = 1
        multi_driver = self.case.get_value("MULTI_DRIVER")
        if multi_driver:
            ninst = self.case.get_value("NINST_MAX")

        if ninst > 1:
            for inst in range(ninst):
                self._getTiming(inst + 1)
        else:
            self._getTiming()

    def _getTiming(self, inst=0):
        components = self.case.get_values("COMP_CLASSES")
        for s in components:
            self.models[s] = _GetTimingInfo(s)
        atm = None
        lnd = None
        rof = None
        ice = None
        ocn = None
        glc = None
        cpl = None
        if "ATM" in self.models:
            atm = self.models["ATM"]
        if "LND" in self.models:
            lnd = self.models["LND"]
        if "ROF" in self.models:
            rof = self.models["ROF"]
        if "ICE" in self.models:
            ice = self.models["ICE"]
        if "OCN" in self.models:
            ocn = self.models["OCN"]
        if "GLC" in self.models:
            glc = self.models["GLC"]
        if "CPL" in self.models:
            cpl = self.models["CPL"]

        cime_model = self.case.get_value("MODEL")
        caseid = self.case.get_value("CASE")
        mach = self.case.get_value("MACH")
        user = self.case.get_value("USER")
        continue_run = self.case.get_value("CONTINUE_RUN")
        rundir = self.case.get_value("RUNDIR")
        run_type = self.case.get_value("RUN_TYPE")
        ncpl_base_period = self.case.get_value("NCPL_BASE_PERIOD")
        ncpl = 0
        ocn_ncpl = None
        for compclass in self.case.get_values("COMP_CLASSES"):
            comp_ncpl = self.case.get_value("{}_NCPL".format(compclass))
            if compclass == "OCN":
                ocn_ncpl = comp_ncpl
            if comp_ncpl is not None:
                ncpl = max(ncpl, comp_ncpl)

        compset = self.case.get_value("COMPSET")
        if compset is None:
            compset = ""
        grid = self.case.get_value("GRID")
        run_type = self.case.get_value("RUN_TYPE")
        stop_option = self.case.get_value("STOP_OPTION")
        stop_n = self.case.get_value("STOP_N")

        cost_pes = self.case.get_value("COST_PES")
        costpes_per_node = self.case.get_value("COSTPES_PER_NODE")

        totalpes = self.case.get_value("TOTALPES")
        max_mpitasks_per_node = self.case.get_value("MAX_MPITASKS_PER_NODE")
        smt_factor = max(
            1, int(self.case.get_value("MAX_TASKS_PER_NODE") / max_mpitasks_per_node)
        )

        if cost_pes > 0:
            pecost = cost_pes
        elif costpes_per_node:
            pecost = self.case.num_nodes * costpes_per_node
        else:
            pecost = totalpes

        for m in self.models.values():
            for key in ["NTASKS", "ROOTPE", "PSTRID", "NTHRDS", "NINST"]:
                if key == "NINST" and m.name == "CPL":
                    m.ninst = 1
                else:
                    setattr(
                        m,
                        key.lower(),
                        int(self.case.get_value("{}_{}".format(key, m.name))),
                    )

            m.comp = self.case.get_value("COMP_{}".format(m.name))
            m.pemax = m.rootpe + m.ntasks * m.pstrid - 1

        now = datetime.datetime.ctime(datetime.datetime.now())
        inittype = "FALSE"
        if (run_type == "startup" or run_type == "hybrid") and not continue_run:
            inittype = "TRUE"

        if inst > 0:
            inst_label = "_{:04d}".format(inst)
        else:
            inst_label = ""
        if self._driver == "mct" or self._driver == "moab":
            binfilename = os.path.join(
                rundir, "timing", "model_timing{}_stats".format(inst_label)
            )
            finfilename = os.path.join(
                self.caseroot,
                "timing",
                "{}_timing{}_stats.{}".format(cime_model, inst_label, self.lid),
            )
        elif self._driver == "nuopc":
            binfilename = os.path.join(rundir, "ESMF_Profile.summary")
            finfilename = os.path.join(
                self.caseroot,
                "timing",
                "{}.ESMF_Profile.summary.{}".format(cime_model, self.lid),
            )

        foutfilename = os.path.join(
            self.caseroot,
            "timing",
            "{}_timing{}.{}.{}".format(cime_model, inst_label, caseid, self.lid),
        )

        timingDir = os.path.join(self.caseroot, "timing")
        if not os.path.isfile(binfilename):
            logger.warning("No timing file found in run directory")
            return

        if not os.path.isdir(timingDir):
            os.makedirs(timingDir)

        safe_copy(binfilename, finfilename)

        os.chdir(self.caseroot)
        try:
            fin = open(finfilename, "r")
            self.finlines = fin.readlines()
            fin.close()
        except Exception as e:
            logger.critical("Unable to open file {}".format(finfilename))
            raise e

        tlen = 1.0
        if ncpl_base_period == "decade":
            tlen = 3650.0
        elif ncpl_base_period == "year":
            tlen = 365.0
        elif ncpl_base_period == "day":
            tlen = 1.0
        elif ncpl_base_period == "hour":
            tlen = 1.0 / 24.0
        else:
            logger.warning("Unknown NCPL_BASE_PERIOD={}".format(ncpl_base_period))

        # at this point the routine becomes driver specific
        if self._driver == "mct" or self._driver == "moab":
            nprocs, ncount = self.gettime2("CPL:CLOCK_ADVANCE ")
            nsteps = ncount / nprocs
        elif self._driver == "nuopc":
            nprocs, nsteps = self.gettime2("")
        adays = nsteps * tlen / ncpl
        odays = nsteps * tlen / ncpl
        if ocn_ncpl and inittype == "TRUE":
            odays = odays - (tlen / ocn_ncpl)

        peminmax = max([m.rootpe for m in self.models.values()]) + 1
        if ncpl_base_period in ["decade", "year", "day"] and int(adays) > 0:
            adays = int(adays)
            if tlen % ocn_ncpl == 0:
                odays = int(odays)
        self.adays = adays
        maxoffset = 40
        extraoff = 20
        for m in self.models.values():
            m.offset = int((maxoffset * m.rootpe) / peminmax) + extraoff
        if cpl:
            cpl.offset = 0
        try:
            self.fout = open(foutfilename, "w")
        except Exception as e:
            logger.critical("Could not open file for writing: {}".format(foutfilename))
            raise e

        self.write("---------------- TIMING PROFILE ---------------------\n")

        self.write("  Case        : {}\n".format(caseid))
        self.write("  LID         : {}\n".format(self.lid))
        self.write("  Machine     : {}\n".format(mach))
        self.write("  Caseroot    : {}\n".format(self.caseroot))
        self.write("  Timeroot    : {}/Tools\n".format(self.caseroot))
        self.write("  User        : {}\n".format(user))
        self.write("  Curr Date   : {}\n".format(now))
        if self._driver == "nuopc":
            self.write("  Driver      : CMEPS\n")
        elif self._driver == "mct" or self._driver == "moab":
            self.write("  Driver      : CPL7\n")

        self.write("  grid        : {}\n".format(grid))
        self.write("  compset     : {}\n".format(compset))
        self.write(
            "  run type    : {}, continue_run = {} (inittype = {})\n".format(
                run_type, str(continue_run).upper(), inittype
            )
        )
        self.write("  stop option : {}, stop_n = {}\n".format(stop_option, stop_n))
        self.write("  run length  : {} days ({} for ocean)\n\n".format(adays, odays))

        self.write(
            "  component       comp_pes    root_pe   tasks  "
            "x threads"
            " instances (stride) \n"
        )
        self.write(
            "  ---------        ------     -------   ------   "
            "------  ---------  ------  \n"
        )
        maxthrds = 0
        xmax = 0
        for k in self.case.get_values("COMP_CLASSES"):
            m = self.models[k]
            if m.comp == "cpl":
                comp_label = m.comp + inst_label
            else:
                comp_label = m.comp
            self.write(
                "  {} = {:<8s}   {:<6d}      {:<6d}   {:<6d} x {:<6d}  {:<6d} ({:<6d}) \n".format(
                    m.name.lower(),
                    comp_label,
                    (m.ntasks * m.nthrds),
                    m.rootpe,
                    m.ntasks,
                    m.nthrds,
                    m.ninst,
                    m.pstrid,
                )
            )
            if m.nthrds > maxthrds:
                maxthrds = m.nthrds
        if self._driver == "nuopc":
            for k in components:
                m = self.models[k]
                if k != "CPL":
                    m.tmin, m.tmax, _ = self._gettime_nuopc(
                        " [{}] RunPhase1 ".format(m.name), inst_label[1:]
                    )
                else:
                    m.tmin, m.tmax = self.getMEDtime(inst_label[1:])
            nmax = self.gettime("[ensemble] Init 1")[1]
            tmax = self.gettime("[ensemble] RunPhase1")[1]
            fmax = self.gettime("[ensemble] FinalizePhase1")[1]
            xmax = self.getCOMMtime(inst_label[1:])

        if self._driver == "mct" or self._driver == "moab":
            for k in components:
                if k != "CPL":
                    m = self.models[k]
                    m.tmin, m.tmax, _ = self.gettime(" CPL:{}_RUN ".format(m.name))
            nmax = self.gettime(" CPL:INIT ")[1]
            tmax = self.gettime(" CPL:RUN_LOOP ")[1]
            wtmin = self.gettime(" CPL:TPROF_WRITE ")[0]
            fmax = self.gettime(" CPL:FINAL ")[1]
            otmin, otmax, _ = self.gettime(" CPL:OCNT_RUN ")

            # pick OCNT_RUN for tight coupling
            if otmax > ocn.tmax:
                ocn.tmin = otmin
                ocn.tmax = otmax

            cpl.tmin, cpl.tmax, _ = self.gettime(" CPL:RUN ")
            xmax = self.gettime(" CPL:COMM ")[1]
            ocnwaittime = self.gettime(" CPL:C2O_INITWAIT")[0]

            if odays != 0:
                ocnrunitime = ocn.tmax * (adays / odays - 1.0)
            else:
                ocnrunitime = 0.0

            correction = max(0, ocnrunitime - ocnwaittime)

            tmax = tmax + wtmin + correction
            ocn.tmax += ocnrunitime

        for m in self.models.values():
            m.tmaxr = 0
            if m.tmax > 0:
                m.tmaxr = adays * 86400.0 / (m.tmax * 365.0)
        xmaxr = 0
        if xmax > 0:
            xmaxr = adays * 86400.0 / (xmax * 365.0)
        tmaxr = 0
        if tmax > 0:
            tmaxr = adays * 86400.0 / (tmax * 365.0)

        self.write("\n")
        self.write("  total pes active           : {} \n".format(totalpes * smt_factor))
        self.write("  mpi tasks per node         : {} \n".format(max_mpitasks_per_node))
        self.write("  pe count for cost estimate : {} \n".format(pecost))
        self.write("\n")

        self.write("  Overall Metrics: \n")
        if adays > 0:
            self.write(
                "    Model Cost:         {:10.2f}   pe-hrs/simulated_year \n".format(
                    (tmax * 365.0 * pecost) / (3600.0 * adays)
                )
            )
        if tmax > 0:
            self.write(
                "    Model Throughput:   {:10.2f}   simulated_years/day \n".format(
                    (86400.0 * adays) / (tmax * 365.0)
                )
            )

        self.write("\n")

        self.write("    Init Time   :  {:10.3f} seconds \n".format(nmax))
        if adays > 0:
            self.write(
                "    Run Time    :  {:10.3f} seconds   {:10.3f} seconds/day \n".format(
                    tmax, tmax / adays
                )
            )
        self.write("    Final Time  :  {:10.3f} seconds \n".format(fmax))

        self.write("\n")
        if self._driver == "mct" or self._driver == "moab":
            self.write(
                "    Actual Ocn Init Wait Time     :  {:10.3f} seconds \n".format(
                    ocnwaittime
                )
            )
            self.write(
                "    Estimated Ocn Init Run Time   :  {:10.3f} seconds \n".format(
                    ocnrunitime
                )
            )
            self.write(
                "    Estimated Run Time Correction :  {:10.3f} seconds \n".format(
                    correction
                )
            )
            self.write(
                "      (This correction has been applied to the ocean and"
                " total run times) \n"
            )

        self.write("\n")
        self.write(
            "Runs Time in total seconds, seconds/model-day, and"
            " model-years/wall-day \n"
        )
        self.write(
            "CPL Run Time represents time in CPL pes alone, "
            "not including time associated with data exchange "
            "with other components \n"
        )
        self.write("\n")

        if adays > 0:
            self.write(
                "    TOT Run Time:  {:10.3f} seconds   {:10.3f} seconds/mday   {:10.2f} myears/wday \n".format(
                    tmax, tmax / adays, tmaxr
                )
            )
            for k in self.case.get_values("COMP_CLASSES"):
                m = self.models[k]
                self.write(
                    "    {} Run Time:  {:10.3f} seconds   {:10.3f} seconds/mday   {:10.2f} myears/wday \n".format(
                        k, m.tmax, m.tmax / adays, m.tmaxr
                    )
                )
            self.write(
                "    CPL COMM Time: {:10.3f} seconds   {:10.3f} seconds/mday   {:10.2f} myears/wday \n".format(
                    xmax, xmax / adays, xmaxr
                )
            )

            pstrlen = 25
            hoffset = 1
            self.write("   NOTE: min:max driver timers (seconds/day):   \n")

            for k in self.case.get_values("COMP_CLASSES"):
                m = self.models[k]
                xspace = (pstrlen + hoffset + m.offset) * " "
                self.write(
                    " {} {} (pes {:d} to {:d}) \n".format(xspace, k, m.rootpe, m.pemax)
                )
            self.write("\n")

            self.prttime(" CPL:CLOCK_ADVANCE ")
            self.prttime(" CPL:OCNPRE1_BARRIER ")
            self.prttime(" CPL:OCNPRE1 ")
            self.prttime(" CPL:ATMOCN1_BARRIER ")
            self.prttime(" CPL:ATMOCN1 ")
            self.prttime(" CPL:OCNPREP_BARRIER ")
            self.prttime(" CPL:OCNPREP ")
            self.prttime(
                " CPL:C2O_BARRIER ", offset=ocn.offset, div=odays, coff=cpl.offset
            )
            self.prttime(" CPL:C2O ", offset=ocn.offset, div=odays, coff=cpl.offset)
            self.prttime(" CPL:LNDPREP_BARRIER ")
            self.prttime(" CPL:LNDPREP ")
            self.prttime(" CPL:C2L_BARRIER ", offset=lnd.offset, coff=cpl.offset)
            self.prttime(" CPL:C2L ", offset=lnd.offset, coff=cpl.offset)
            self.prttime(" CPL:ICEPREP_BARRIER ")
            self.prttime(" CPL:ICEPREP ")
            self.prttime(" CPL:C2I_BARRIER ", offset=ice.offset, coff=cpl.offset)
            self.prttime(" CPL:C2I ", offset=ice.offset, coff=cpl.offset)
            self.prttime(" CPL:WAVPREP_BARRIER ")
            self.prttime(" CPL:WAVPREP ")
            self.prttime(" CPL:C2W_BARRIER ", offset=ice.offset, coff=cpl.offset)
            self.prttime(" CPL:C2W ", offset=ice.offset, coff=cpl.offset)
            self.prttime(" CPL:ROFPREP_BARRIER ")
            self.prttime(" CPL:ROFPREP ")
            self.prttime(" CPL:C2R_BARRIER ", offset=rof.offset, coff=cpl.offset)
            self.prttime(" CPL:C2R ", offset=rof.offset, coff=cpl.offset)
            self.prttime(" CPL:ICE_RUN_BARRIER ", offset=ice.offset)
            self.prttime(" CPL:ICE_RUN ", offset=ice.offset)
            self.prttime(" CPL:LND_RUN_BARRIER ", offset=lnd.offset)
            self.prttime(" CPL:LND_RUN ", offset=lnd.offset)
            self.prttime(" CPL:ROF_RUN_BARRIER ", offset=rof.offset)
            self.prttime(" CPL:ROF_RUN ", offset=rof.offset)
            self.prttime(" CPL:WAV_RUN_BARRIER ", offset=rof.offset)
            self.prttime(" CPL:WAV_RUN ", offset=rof.offset)
            self.prttime(" CPL:OCNT_RUN_BARRIER ", offset=ocn.offset, div=odays)
            self.prttime(" CPL:OCNT_RUN ", offset=ocn.offset, div=odays)
            self.prttime(
                " CPL:O2CT_BARRIER ", offset=ocn.offset, div=odays, coff=cpl.offset
            )
            self.prttime(" CPL:O2CT ", offset=ocn.offset, div=odays, coff=cpl.offset)
            self.prttime(" CPL:OCNPOSTT_BARRIER ")
            self.prttime(" CPL:OCNPOSTT ")
            self.prttime(" CPL:ATMOCNP_BARRIER ")
            self.prttime(" CPL:ATMOCNP ")
            self.prttime(" CPL:L2C_BARRIER ", offset=lnd.offset, coff=cpl.offset)
            self.prttime(" CPL:L2C ", offset=lnd.offset, div=cpl.offset)
            self.prttime(" CPL:LNDPOST_BARRIER ")
            self.prttime(" CPL:LNDPOST ")
            self.prttime(" CPL:GLCPREP_BARRIER ")
            self.prttime(" CPL:GLCPREP ")
            self.prttime(" CPL:C2G_BARRIER ", offset=glc.offset, coff=cpl.offset)
            self.prttime(" CPL:C2G ", offset=glc.offset, coff=cpl.offset)
            self.prttime(" CPL:R2C_BARRIER ", offset=rof.offset, coff=cpl.offset)
            self.prttime(" CPL:R2C ", offset=rof.offset, coff=cpl.offset)
            self.prttime(" CPL:ROFPOST_BARRIER ")
            self.prttime(" CPL:ROFPOST ")
            self.prttime(" CPL:BUDGET1_BARRIER ")
            self.prttime(" CPL:BUDGET1 ")
            self.prttime(" CPL:I2C_BARRIER ", offset=ice.offset, coff=cpl.offset)
            self.prttime(" CPL:I2C ", offset=ice.offset, coff=cpl.offset)
            self.prttime(" CPL:ICEPOST_BARRIER ")
            self.prttime(" CPL:ICEPOST ")
            self.prttime(" CPL:FRACSET_BARRIER ")
            self.prttime(" CPL:FRACSET ")
            self.prttime(" CPL:ATMOCN2_BARRIER ")
            self.prttime(" CPL:ATMOCN2 ")
            self.prttime(" CPL:OCNPRE2_BARRIER ")
            self.prttime(" CPL:OCNPRE2 ")
            self.prttime(
                " CPL:C2O2_BARRIER ", offset=ocn.offset, div=odays, coff=cpl.offset
            )
            self.prttime(" CPL:C2O2 ", offset=ocn.offset, div=odays, coff=cpl.offset)
            self.prttime(" CPL:ATMOCNQ_BARRIER")
            self.prttime(" CPL:ATMOCNQ ")
            self.prttime(" CPL:ATMPREP_BARRIER ")
            self.prttime(" CPL:ATMPREP ")
            self.prttime(" CPL:C2A_BARRIER ", offset=atm.offset, coff=cpl.offset)
            self.prttime(" CPL:C2A ", offset=atm.offset, coff=cpl.offset)
            self.prttime(" CPL:OCN_RUN_BARRIER ", offset=ocn.offset, div=odays)
            self.prttime(" CPL:OCN_RUN ", offset=ocn.offset, div=odays)
            self.prttime(" CPL:ATM_RUN_BARRIER ", offset=atm.offset)
            self.prttime(" CPL:ATM_RUN ", offset=atm.offset)
            self.prttime(" CPL:GLC_RUN_BARRIER ", offset=glc.offset)
            self.prttime(" CPL:GLC_RUN ", offset=glc.offset)
            self.prttime(" CPL:W2C_BARRIER ", offset=glc.offset, coff=cpl.offset)
            self.prttime(" CPL:W2C ", offset=glc.offset, coff=cpl.offset)
            self.prttime(" CPL:WAVPOST_BARRIER ")
            self.prttime(" CPL:WAVPOST ", cpl.offset)
            self.prttime(" CPL:G2C_BARRIER ", offset=glc.offset, coff=cpl.offset)
            self.prttime(" CPL:G2C ", offset=glc.offset, coff=cpl.offset)
            self.prttime(" CPL:GLCPOST_BARRIER ")
            self.prttime(" CPL:GLCPOST ")
            self.prttime(" CPL:A2C_BARRIER ", offset=atm.offset, coff=cpl.offset)
            self.prttime(" CPL:A2C ", offset=atm.offset, coff=cpl.offset)
            self.prttime(" CPL:ATMPOST_BARRIER ")
            self.prttime(" CPL:ATMPOST ")
            self.prttime(" CPL:BUDGET2_BARRIER ")
            self.prttime(" CPL:BUDGET2 ")
            self.prttime(" CPL:BUDGET3_BARRIER ")
            self.prttime(" CPL:BUDGET3 ")
            self.prttime(" CPL:BUDGETF_BARRIER ")
            self.prttime(" CPL:BUDGETF ")
            self.prttime(
                " CPL:O2C_BARRIER ", offset=ocn.offset, div=odays, coff=cpl.offset
            )
            self.prttime(" CPL:O2C ", offset=ocn.offset, div=odays, coff=cpl.offset)
            self.prttime(" CPL:OCNPOST_BARRIER ")
            self.prttime(" CPL:OCNPOST ")
            self.prttime(" CPL:RESTART_BARRIER ")
            self.prttime(" CPL:RESTART")
            self.prttime(" CPL:HISTORY_BARRIER ")
            self.prttime(" CPL:HISTORY ")
            self.prttime(" CPL:TSTAMP_WRITE ")
            self.prttime(" CPL:TPROF_WRITE ")
            self.prttime(" CPL:RUN_LOOP_BSTOP ")

            self.write("\n\n")
            self.write("More info on coupler timing:\n")

            self.write("\n")
            self.prttime(" CPL:OCNPRE1 ")
            self.prttime(" CPL:ocnpre1_atm2ocn ")

            self.write("\n")
            self.prttime(" CPL:OCNPREP ")
            self.prttime(" CPL:OCNPRE2 ")
            self.prttime(" CPL:ocnprep_avg ")
            self.prttime(" CPL:ocnprep_diagav ")

            self.write("\n")
            self.prttime(" CPL:LNDPREP ")
            self.prttime(" CPL:lndprep_atm2lnd ")
            self.prttime(" CPL:lndprep_mrgx2l ")
            self.prttime(" CPL:lndprep_diagav ")

            self.write("\n")
            self.prttime(" CPL:ICEPREP ")
            self.prttime(" CPL:iceprep_ocn2ice ")
            self.prttime(" CPL:iceprep_atm2ice ")
            self.prttime(" CPL:iceprep_mrgx2i ")
            self.prttime(" CPL:iceprep_diagav ")

            self.write("\n")
            self.prttime(" CPL:WAVPREP ")
            self.prttime(" CPL:wavprep_atm2wav ")
            self.prttime(" CPL:wavprep_ocn2wav ")
            self.prttime(" CPL:wavprep_ice2wav ")
            self.prttime(" CPL:wavprep_mrgx2w ")
            self.prttime(" CPL:wavprep_diagav ")

            self.write("\n")
            self.prttime(" CPL:ROFPREP ")
            self.prttime(" CPL:rofprep_l2xavg ")
            self.prttime(" CPL:rofprep_lnd2rof ")
            self.prttime(" CPL:rofprep_mrgx2r ")
            self.prttime(" CPL:rofprep_diagav ")

            self.write("\n")
            self.prttime(" CPL:GLCPREP ")
            self.prttime(" CPL:glcprep_avg ")
            self.prttime(" CPL:glcprep_lnd2glc ")
            self.prttime(" CPL:glcprep_mrgx2g ")
            self.prttime(" CPL:glcprep_diagav ")

            self.write("\n")
            self.prttime(" CPL:ATMPREP ")
            self.prttime(" CPL:atmprep_xao2atm ")
            self.prttime(" CPL:atmprep_ocn2atm ")
            self.prttime(" CPL:atmprep_alb2atm ")
            self.prttime(" CPL:atmprep_ice2atm ")
            self.prttime(" CPL:atmprep_lnd2atm ")
            self.prttime(" CPL:atmprep_mrgx2a ")
            self.prttime(" CPL:atmprep_diagav ")

            self.write("\n")
            self.prttime(" CPL:ATMOCNP ")
            self.prttime(" CPL:ATMOCN1 ")
            self.prttime(" CPL:ATMOCN2 ")
            self.prttime(" CPL:atmocnp_ice2ocn ")
            self.prttime(" CPL:atmocnp_wav2ocn ")
            self.prttime(" CPL:atmocnp_fluxo ")
            self.prttime(" CPL:atmocnp_fluxe ")
            self.prttime(" CPL:atmocnp_mrgx2o ")
            self.prttime(" CPL:atmocnp_accum ")
            self.prttime(" CPL:atmocnp_ocnalb ")

            self.write("\n")
            self.prttime(" CPL:ATMOCNQ ")
            self.prttime(" CPL:atmocnq_ocn2atm ")
            self.prttime(" CPL:atmocnq_fluxa ")
            self.prttime(" CPL:atmocnq_atm2ocnf ")

            self.write("\n")
            self.prttime(" CPL:OCNPOSTT ")
            self.prttime(" CPL:OCNPOST ")
            self.prttime(" CPL:ocnpost_diagav ")

            self.write("\n")
            self.prttime(" CPL:LNDPOST ")
            self.prttime(" CPL:lndpost_diagav ")
            self.prttime(" CPL:lndpost_acc2lr ")
            self.prttime(" CPL:lndpost_acc2lg ")

            self.write("\n")
            self.prttime(" CPL:ROFOST ")
            self.prttime(" CPL:rofpost_diagav ")
            self.prttime(" CPL:rofpost_histaux ")
            self.prttime(" CPL:rofpost_rof2lnd ")
            self.prttime(" CPL:rofpost_rof2ice ")
            self.prttime(" CPL:rofpost_rof2ocn ")

            self.write("\n")
            self.prttime(" CPL:ICEPOST ")
            self.prttime(" CPL:icepost_diagav ")

            self.write("\n")
            self.prttime(" CPL:WAVPOST ")
            self.prttime(" CPL:wavpost_diagav ")

            self.write("\n")
            self.prttime(" CPL:GLCPOST ")
            self.prttime(" CPL:glcpost_diagav ")
            self.prttime(" CPL:glcpost_glc2lnd ")
            self.prttime(" CPL:glcpost_glc2ice ")
            self.prttime(" CPL:glcpost_glc2ocn ")

            self.write("\n")
            self.prttime(" CPL:ATMPOST ")
            self.prttime(" CPL:atmpost_diagav ")

            self.write("\n")
            self.prttime(" CPL:BUDGET ")
            self.prttime(" CPL:BUDGET1 ")
            self.prttime(" CPL:BUDGET2 ")
            self.prttime(" CPL:BUDGET3 ")
            self.prttime(" CPL:BUDGETF ")
            self.write("\n\n")

        self.fout.close()


[docs] def get_timing(case, lid): parser = _TimingParser(case, lid) parser.getTiming()