argo-perf-wrapper 4.43 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14
#!/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')

15

16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 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 89 90 91 92
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',
                  'event': 'progress',
                  'payload': progress,
                  'uuid': self.app_uuid,
                  }
        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')
        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)
        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)
93
        os.mkfifo(fifoname, 0o600)
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110

        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:
111
            line = fifo.readline()
112 113 114
            if not line:
                break

115
            line = line.strip()
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
            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)

142

143 144 145 146
if __name__ == "__main__":
    logging.basicConfig(level=logging.WARNING)
    wrapper = PerfWrapper()
    wrapper.main()