argo-perf-wrapper 5 KB
Newer Older
Kamil Iskra's avatar
Kamil Iskra committed
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
###############################################################################

Kamil Iskra's avatar
Kamil Iskra committed
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
Kamil Iskra's avatar
Kamil Iskra committed
22

23
PUB_MSG = messaging.MSGTYPES['down_event']
Kamil Iskra's avatar
Kamil Iskra committed
24 25
logger = logging.getLogger('perf-wrapper')

26

Kamil Iskra's avatar
Kamil Iskra committed
27 28 29 30 31 32 33 34 35
class PerfWrapper(object):

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

    def __init__(self):
        pass

    def shutdown(self):
36 37 38
        update = {'api': 'down_event',
                  'type': 'application_exit',
                  'application_uuid': self.app_uuid,
Kamil Iskra's avatar
Kamil Iskra committed
39
                  }
40 41
        msg = PUB_MSG['application_exit'](**update)
        self.downstream_event.sendmsg(msg)
Kamil Iskra's avatar
Kamil Iskra committed
42

43
    def performance_report(self, performance):
44
        update = {'api': 'down_event',
45 46 47 48
                  'type': 'performance',
                  'payload': performance,
                  'container_uuid' : self.container_uuid,
                  'application_uuid' : self.app_uuid,
Kamil Iskra's avatar
Kamil Iskra committed
49
                  }
50
        msg = PUB_MSG['performance'](**update)
51
        self.downstream_event.sendmsg(msg)
Kamil Iskra's avatar
Kamil Iskra committed
52 53

    def setup(self):
54 55 56 57
        downstream_url = "ipc:///tmp/nrm-downstream-event"
        self.downstream_event = messaging.DownstreamEventClient(downstream_url)
        self.downstream_event.wait_connected()
        logger.info("downstream pub socket connected to: %s", downstream_url)
Kamil Iskra's avatar
Kamil Iskra committed
58 59 60 61 62 63 64 65 66

        # 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
67 68 69 70
        update = {'api': 'down_event',
                  'type': 'application_start',
                  'container_uuid': self.container_uuid,
                  'application_uuid': self.app_uuid,
Kamil Iskra's avatar
Kamil Iskra committed
71
                  }
72 73
        msg = PUB_MSG['application_start'](**update)
        self.downstream_event.sendmsg(msg)
Kamil Iskra's avatar
Kamil Iskra committed
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99

    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)
100
        os.mkfifo(fifoname, 0o600)
Kamil Iskra's avatar
Kamil Iskra committed
101

102 103
        perf_tool_path = os.environ.get('PERF', 'perf')
        argv = [perf_tool_path, 'stat', '-e', 'instructions', '-x', ',',
Kamil Iskra's avatar
Kamil Iskra committed
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
                '-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:
119
            line = fifo.readline()
Kamil Iskra's avatar
Kamil Iskra committed
120 121 122
            if not line:
                break

123
            line = line.strip()
Kamil Iskra's avatar
Kamil Iskra committed
124 125 126 127 128 129 130 131 132 133 134 135 136 137
            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)
138
            self.performance_report(ips)
Kamil Iskra's avatar
Kamil Iskra committed
139 140 141 142 143 144 145 146 147 148 149

            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)

150

Kamil Iskra's avatar
Kamil Iskra committed
151 152 153 154
if __name__ == "__main__":
    logging.basicConfig(level=logging.WARNING)
    wrapper = PerfWrapper()
    wrapper.main()