#!/usr/bin/env python3
# SPDX-License-Identifier: BSD-3-Clause-Clear
# Copyright (c) 2019, The Numerical Algorithms Group, Ltd. All rights reserved.
"""\
Dimemas Automation
------------------
Helper routines to run Dimemas from Python, including the special case of
ideal trace generation.
"""
import os
import warnings
from os.path import basename, splitext
from tempfile import mkdtemp
import subprocess as sp
from pkg_resources import resource_filename
from .prv import _parse_paraver_headerline, zipopen
from .extrae import remove_trace
IDEAL_CONF_PATH = "cfgs/dimemas_ideal.cfg"
IDEAL_COLL_PATH = "cfgs/ideal.collectives"
[docs]def dimemas_idealise(tracefile, outpath=None):
"""Idealise a tracefile using Dimemas
Parameters
----------
tracefile: str
Path to Extrae tracefile (`*.prv`)
outpath: str
Optional path to output idealised trace. (If not specified will be
created in a temporary folder.)
Returns
-------
idealised: str
Path to idealised tracefile in prv format.
"""
# First we need the application layout info
with zipopen(tracefile, "rt") as fh:
metadata = _parse_paraver_headerline(fh.readline().strip())
# Calculate ranks per node for Dimemas
ranks_per_node = metadata.application_layout.commsize // metadata.nodes
# Check there is not some odd layout we can't deal with
if (
len(set(metadata.procs_per_node)) != 1
or ranks_per_node * metadata.nodes != metadata.application_layout.commsize
):
raise ValueError("Can only analyze homogenous sized ranks")
# Populate run specfic config data
subs = {
"@NUM_NODES@": metadata.nodes,
"@PROCS_PER_NODE@": metadata.procs_per_node[0],
"@RANKS_PER_NODE@": ranks_per_node,
"@COLLECTIVES_PATH@": resource_filename(__name__, IDEAL_COLL_PATH),
}
# Pass trace, run config and path to idealisation skeleton config and let
# dimemas_analyze work its subtle magic(k)s
return dimemas_analyse(
tracefile, resource_filename(__name__, IDEAL_CONF_PATH), outpath, subs
)
[docs]def dimemas_analyse(tracefile, configfile, outpath=None, substrings=None):
"""Run a Dimemas simulation given a configuration and tracefile
The configuration file may be modifed with key->value substitutions prior
to running dimemas using the substrings parameter. This allows the use of a
predefined skeleton configuration file rather than requiring programmatic
production of the complete config.
Parameters
----------
tracefile: str
Path to Extrae tracefile (`*.prv`)
configfile: str
Path to Dimemas configfile
outpath: str or None
Optional path to output idealised trace. (If not specified, will be
created in a temporary folder.)
substrings: dict
Dict of keys in the config file to be replaced with corresponding
values.
Returns
-------
simulated: str
Path to simulated tracefile in prv format.
"""
# Perform all work in a tempdir with predictable names,
# this works around a series of weird dimemas bugs
workdir = mkdtemp()
# Create temporary config from supplied config and substitution dict
dimconfig = os.path.join(workdir, ".tmpconfig".join(splitext(basename(configfile))))
with open(configfile, "rt") as ifh, open(dimconfig, "wt") as ofh:
for line in ifh:
if substrings:
for key, val in substrings.items():
line = line.replace(key, str(val))
ofh.write(line)
# Now copy temporary prv file:
tmp_prv = os.path.join(workdir, "input.prv")
with zipopen(tracefile, "rb") as ifh, open(tmp_prv, "wb") as ofh:
while True:
buff = ifh.read(8589934592)
if not buff:
break
ofh.write(buff)
# And also copy row and pcf if available
for ext in [".row", ".pcf"]:
tracestem = splitext(tracefile)[0]
tracestem = tracestem[:-3] if tracefile.endswith(".gz") else tracestem
infile = splitext(tracestem)[0] + ext
outfile = splitext(tmp_prv)[0] + ext
try:
with open(infile, "rb") as ifh, open(outfile, "wb") as ofh:
while True:
buff = ifh.read(8589934592)
if not buff:
break
ofh.write(buff)
except FileNotFoundError:
warnings.warn(
"Could not find {}, dimemas may fail or produce invalid data"
"".format(infile)
)
# Now create the dim file for dimemas
tmp_dim = os.path.join(workdir, splitext(basename(tmp_prv))[0] + ".dim")
# Use basenames as running in workdir
prv2dim_params = [
"prv2dim",
"--prv-trace",
basename(tmp_prv),
"--dim-trace",
basename(tmp_dim),
]
# Run prv2dim and check for success
result = sp.run(prv2dim_params, stdout=sp.PIPE, stderr=sp.PIPE, cwd=workdir)
if not os.path.exists(tmp_dim) or result.returncode != 0:
raise RuntimeError(
"prv2dim execution failed:\n{}" "".format(result.stderr.decode())
)
# Run in workdir to workaround Dimemas path bug, output to relpath
sim_prv = ".sim".join(splitext(tmp_prv))
dimemas_params = [
"Dimemas",
"-S",
"32k",
"--dim",
basename(tmp_dim),
"-p",
basename(sim_prv),
basename(dimconfig),
]
result = sp.run(dimemas_params, stdout=sp.PIPE, stderr=sp.STDOUT, cwd=workdir)
if not os.path.exists(sim_prv) or result.returncode != 0:
raise RuntimeError(
"Dimemas execution failed:\n{}" "".format(result.stdout.decode())
)
# remove all the temporary files we created
os.remove(dimconfig)
os.remove(tmp_dim)
remove_trace(tmp_prv)
# If no outpath specified then we are done
if outpath is None:
return sim_prv
# Otherwise copy back to requested location
with open(sim_prv, "rb") as ifh, open(outpath, "wb") as ofh:
while True:
buff = ifh.read(8589934592)
if not buff:
break
ofh.write(buff)
# And also copy row and pcf
for ext in [".row", ".pcf"]:
infile = splitext(sim_prv)[0] + ext
outfile = splitext(outpath)[0] + ext
with open(infile, "rb") as ifh, open(outfile, "wb") as ofh:
while True:
buff = ifh.read(8589934592)
if not buff:
break
ofh.write(buff)
# and then delete temps
remove_trace(sim_prv)
# finally return outpath as promised
return outpath