nrm-perfwrapper 4.56 KB
Newer Older
1 2
#!/usr/bin/env python2

3 4 5 6 7 8 9 10 11 12
###############################################################################
# Copyright 2019 UChicago Argonne, LLC.
# (c.f. AUTHORS, LICENSE)
#
# This file is part of the NRM project.
# For more info, see https://xgitlab.cels.anl.gov/argo/nrm
#
# SPDX-License-Identifier: BSD-3-Clause
###############################################################################

13 14 15 16 17 18 19 20
from __future__ import print_function

import argparse
import logging
import os
import tempfile
import subprocess
import uuid
21
from nrm import messaging
22 23 24

logger = logging.getLogger('perf-wrapper')

25

26 27 28 29 30 31 32 33 34
class PerfWrapper(object):

    """Implements middleware between the Linux perf and
    the NRM downstream interface."""

    def __init__(self):
        pass

    def shutdown(self):
35
        self.downstream_event.send(tag="exit", application_uuid=self.app_uuid)
36

37
    def performance_report(self, performance):
38 39 40 41 42
        self.downstream_event.send(
                tag="performance",
                payload=performance,
                container_uuid=self.container_uuid,
                application_uuid=self.app_uuid)
43 44

    def setup(self):
45 46
        downstream_url = "ipc:///tmp/nrm-downstream-event"
        self.downstream_event = messaging.DownstreamEventClient(downstream_url)
47
        logger.info("connecting downstream pub")
48
        self.downstream_event.connect()
49
        logger.info("downstream pub socket connected to: %s", downstream_url)
50 51 52 53 54 55 56 57 58

        # retrieve our container uuid
        self.container_uuid = os.environ.get('ARGO_CONTAINER_UUID')
        if self.container_uuid is None:
            logger.error("missing container uuid")
            exit(1)
        self.app_uuid = str(uuid.uuid4())
        logger.info("client uuid: %r", self.app_uuid)
        # send an hello to the demon
59 60 61 62
        self.downstream_event.send(
                tag="start",
                container_uuid=self.container_uuid,
                application_uuid=self.app_uuid)
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88

    def main(self):
        parser = argparse.ArgumentParser()
        parser.add_argument("-v", "--verbose",
                            help="verbose logging information",
                            action='store_true')
        parser.add_argument("-f", "--frequency",
                            help="sampling frequency in ms",
                            type=int, default=1000)
        parser.add_argument("cmd", help="command and arguments",
                            nargs=argparse.REMAINDER)
        args = parser.parse_args()

        if args.verbose:
            logger.setLevel(logging.DEBUG)

        logger.info("cmd: %r", args.cmd)

        self.setup()

        # create a named pipe between us and the to-be-launched perf
        # There is no mkstemp for FIFOs but we can securely create a temporary
        # directory and then create a FIFO inside of it.
        tmpdir = tempfile.mkdtemp()
        fifoname = os.path.join(tmpdir, 'perf-fifo')
        logger.info("fifoname: %r", fifoname)
89
        os.mkfifo(fifoname, 0o600)
90

91 92
        perf_tool_path = os.environ.get('PERF', 'perf')
        argv = [perf_tool_path, 'stat', '-e', 'instructions', '-x', ',',
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
                '-I', str(args.frequency), '-o', fifoname, '--']
        argv.extend(args.cmd)
        logger.info("argv: %r", argv)

        p = subprocess.Popen(argv, close_fds=True)

        # This blocks until the other end opens as well so we need to invoke
        # it after Popen.
        # FIXME: will deadlock if Popen fails (say, no perf).
        fifo = open(fifoname, 'r')

        last_time = 0.0
        # "for line in fifo" idiom didn't work for me here -- Python was
        # buffering the output internally until perf was finished.
        while True:
108
            line = fifo.readline()
109 110 111
            if not line:
                break

112
            line = line.strip()
113 114 115 116 117 118 119 120 121 122 123 124 125 126
            if len(line) == 0 or line[0] == '#':
                continue
            tokens = line.split(',')

            logger.info("tokens: %r", tokens)

            time = float(tokens[0])
            if tokens[1] == '<not counted>':
                instructions = 0
            else:
                instructions = int(tokens[1])
            ips = int(instructions / (time - last_time))

            logger.info("instructions per second: %r", ips)
127
            self.performance_report(ips)
128 129 130 131 132 133 134 135 136 137 138

            last_time = time

        # The child should be dead by now so this should terminate immediately.
        p.wait()

        self.shutdown()
        fifo.close()
        os.remove(fifoname)
        os.rmdir(tmpdir)

139

140 141 142 143
if __name__ == "__main__":
    logging.basicConfig(level=logging.WARNING)
    wrapper = PerfWrapper()
    wrapper.main()