argo-perf-wrapper 4.52 KB
Newer Older
1 2 3 4 5 6 7 8 9 10
#!/usr/bin/env python2

from __future__ import print_function

import argparse
import logging
import os
import tempfile
import subprocess
import uuid
11
from nrm import messaging
12

13
PUB_MSG = messaging.MSGTYPES['down_event']
14 15
logger = logging.getLogger('perf-wrapper')

16

17 18 19 20 21 22 23 24 25
class PerfWrapper(object):

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

    def __init__(self):
        pass

    def shutdown(self):
26 27 28
        update = {'api': 'down_event',
                  'type': 'application_exit',
                  'application_uuid': self.app_uuid,
29
                  }
30 31
        msg = PUB_MSG['application_exit'](**update)
        self.downstream_event.sendmsg(msg)
32 33

    def progress_report(self, progress):
34 35
        update = {'api': 'down_event',
                  'type': 'progress',
36 37
                  'payload': progress,
                  }
38 39
        msg = PUB_MSG['progress'](**update)
        self.downstream_event.sendmsg(msg)
40 41

    def setup(self):
42 43 44 45
        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)
46 47 48 49 50 51 52 53 54

        # 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
55 56 57 58
        update = {'api': 'down_event',
                  'type': 'application_start',
                  'container_uuid': self.container_uuid,
                  'application_uuid': self.app_uuid,
59
                  }
60 61
        msg = PUB_MSG['application_start'](**update)
        self.downstream_event.sendmsg(msg)
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

    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)
88
        os.mkfifo(fifoname, 0o600)
89

90 91
        perf_tool_path = os.environ.get('PERF', 'perf')
        argv = [perf_tool_path, 'stat', '-e', 'instructions', '-x', ',',
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
                '-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:
107
            line = fifo.readline()
108 109 110
            if not line:
                break

111
            line = line.strip()
112 113 114 115 116 117 118 119 120 121 122 123 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)
            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)

138

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