argo-perf-wrapper 4.92 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
#!/usr/bin/env python2

from __future__ import print_function

import argparse
import logging
import zmq
import os
import tempfile
import subprocess
import uuid

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

class PerfWrapper(object):

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

    def __init__(self):
        pass

    def shutdown(self):
        update = {'type': 'application',
                  'event': 'exit',
                  'uuid': self.app_uuid,
                  }
        self.downstream_pub_socket.send_json(update)

    def progress_report(self, progress):
        update = {'type': 'application',
32
                  'event': 'hardware-progress',
33 34
                  'payload': progress,
                  'uuid': self.app_uuid,
35
                  'container': self.container_uuid,
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
                  }
        self.downstream_pub_socket.send_json(update)

    def setup(self):
        context = zmq.Context()
        self.downstream_pub_socket = context.socket(zmq.PUB)

        downstream_pub_param = "ipc:///tmp/nrm-downstream-in"

        self.downstream_pub_socket.connect(downstream_pub_param)

        logger.info("downstream pub socket connected to: %s",
                    downstream_pub_param)

        # retrieve our container uuid
        self.container_uuid = os.environ.get('ARGO_CONTAINER_UUID')
52 53
        # logger.error("container uuid:",str(self.container_uuid))
        # logger.error("environ:",dict(os.environ))
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
        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
        update = {'type': 'application',
                  'event': 'start',
                  'container': self.container_uuid,
                  'uuid': self.app_uuid,
                  'progress': True,
                  'threads': {'min': 1, 'cur': 1, 'max': 1},
                  }
        self.downstream_pub_socket.send_json(update)

    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)
78 79 80
        parser.add_argument("-l", "--logfile",
                            help="perf-wrapper log file",
                            type=str, default="/tmp/argo-perf-wrapper.log")
81 82 83 84 85 86 87
        parser.add_argument("cmd", help="command and arguments",
                            nargs=argparse.REMAINDER)
        args = parser.parse_args()

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

88 89 90 91
        if args.logfile:
            logger.setLevel(logging.DEBUG)
            logger.addHandler(logging.FileHandler(args.logfile))

92 93 94 95 96 97 98 99 100 101
        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)
102
        os.mkfifo(fifoname, 0o600)
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119

        argv = ['perf', 'stat', '-e', 'instructions', '-x', ',',
                '-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:
120
            line = fifo.readline()
121 122 123
            if not line:
                break

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

            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)

151

152
if __name__ == "__main__":
153
    # logging.basicConfig(level=logging.INFO)
154 155
    wrapper = PerfWrapper()
    wrapper.main()