Exemple #1
0
def test_utc_now():
    """
    Test construction of a UTC date and time.
    """

    from time           import sleep
    from nose.tools     import assert_true
    from cargo.temporal import utc_now

    then = utc_now()

    sleep(2.0)

    assert_true(utc_now() > then)
Exemple #2
0
def test_utc_now():
    """
    Test construction of a UTC date and time.
    """

    from time import sleep
    from nose.tools import assert_true
    from cargo.temporal import utc_now

    then = utc_now()

    sleep(2.0)

    assert_true(utc_now() > then)
Exemple #3
0
def add_disk_handler(prefix, level=logging.NOTSET):
    """
    Enable typical logging to disk.
    """

    # generate an unused log file path
    from os.path import lexists
    from itertools import count

    for i in count():
        path = "%s.%i" % (prefix, i)

        if not lexists(path):
            break

    # build a handler
    from cargo.temporal import utc_now

    handler = FileHandler(path, encoding="utf-8")

    handler.setFormatter(VerboseFileFormatter())
    handler.setLevel(level)

    # add it
    logging.root.addHandler(handler)

    log.debug("added log handler for file %s at %s", path, utc_now())

    return handler
Exemple #4
0
def add_console_handler(level=logging.NOTSET, verbose=True):
    """
    Enable typical logging to the console.
    """

    # get the appropriate formatter
    if verbose:
        formatter = TTY_VerboseFormatter
    else:
        formatter = TTY_ConciseFormatter

    # build a handler
    from sys import stdout
    from cargo.temporal import utc_now

    handler = StreamHandler(stdout)

    handler.setFormatter(formatter(stdout))
    handler.setLevel(level)

    # add it
    logging.root.addHandler(handler)

    log.debug("added log handler for console at %s", utc_now())

    return handler
Exemple #5
0
def add_disk_handler(prefix, level=logging.NOTSET):
    """
    Enable typical logging to disk.
    """

    # generate an unused log file path
    from os.path import lexists
    from itertools import count

    for i in count():
        path = "%s.%i" % (prefix, i)

        if not lexists(path):
            break

    # build a handler
    from cargo.temporal import utc_now

    handler = FileHandler(path, encoding="utf-8")

    handler.setFormatter(VerboseFileFormatter())
    handler.setLevel(level)

    # add it
    logging.root.addHandler(handler)

    log.debug("added log handler for file %s at %s", path, utc_now())

    return handler
Exemple #6
0
def add_console_handler(level=logging.NOTSET, verbose=True):
    """
    Enable typical logging to the console.
    """

    # get the appropriate formatter
    if verbose:
        formatter = TTY_VerboseFormatter
    else:
        formatter = TTY_ConciseFormatter

    # build a handler
    from sys import stdout
    from cargo.temporal import utc_now

    handler = StreamHandler(stdout)

    handler.setFormatter(formatter(stdout))
    handler.setLevel(level)

    # add it
    logging.root.addHandler(handler)

    log.debug("added log handler for console at %s", utc_now())

    return handler
Exemple #7
0
def run_cpu_limited(
    arguments,
    limit,
    pty         = True,
    environment = {},
    resolution  = 0.5,
    ):
    """
    Spawn a subprocess whose process tree is granted limited CPU (user) time.

    @param environment Override specific existing environment variables.

    The subprocess must not expect input. This method is best suited to
    processes which may run for a reasonable amount of time (eg, at least
    several seconds); it will be fairly inefficient (and ineffective) at
    fine-grained limiting of CPU allocation to short-duration processes.

    We run the process and read its output. Every time we receive a chunk of
    data, or every C{resolution} seconds, we estimate the total CPU time used
    by the session---and store that information with the chunk of output, if
    any. After at least C{limit} of CPU time has been used by the spawned
    session, or after the session leader terminates, whichever is first, the
    session is (sig)killed, the session leader waited on, and any data
    remaining in the pipe is read.

    Note that the use of SIGKILL means that child processes *cannot* perform
    their own cleanup.

    If C{pty} is specified, process stdout is piped through a pty, which makes
    process output less likely to be buffered. This behavior is the default.

    Kernel-reported resource usage includes the sum of all directly and
    indirectly waited-on children. It will be accurate in the common case where
    processes terminate after correctly waiting on their children, and
    inaccurate in cases where zombies are reparented to init. Elapsed CPU time
    taken from the /proc accounting mechanism is used to do CPU time limiting,
    and will always be at least the specified limit.
    """

    log.detail("running %s for %s", arguments, limit)

    # sanity
    if not arguments:
        raise ArgumentError()

    # start the run
    from cargo.temporal import utc_now

    popened   = None
    fd_chunks = {}
    exit_pid  = None
    started   = utc_now()

    try:
        # start running the child process
        if pty:
            popened = spawn_pty_session(arguments, environment)
        else:
            popened = spawn_pipe_session(arguments, environment)

        fd_chunks = {
            popened.stdout.fileno(): [],
            popened.stderr.fileno(): [],
            }

        log.debug("spawned child with pid %i", popened.pid)

        # read the child's output while accounting (note that the session id
        # is, under Linux, the pid of the session leader)
        accountant = SessionTimeAccountant(popened.pid)
        reader     = PollingReader(fd_chunks.keys())

        while reader.fds:
            # nuke if we're past cutoff
            if accountant.total >= limit:
                popened.kill()

                break

            # read from and audit the child process
            (chunk_fd, chunk) = reader.read(resolution)

            accountant.audit()

            if chunk is not None:
                log.debug(
                    "got %i bytes at %s (user time) on fd %i; chunk follows:\n%s",
                    len(chunk),
                    accountant.total,
                    chunk_fd,
                    chunk,
                    )

                if chunk != "":
                    fd_chunks[chunk_fd].append((accountant.total, chunk))
                else:
                    reader.unregister([chunk_fd])

        # wait for our child to die
        (exit_pid, termination, usage) = os.wait4(popened.pid, 0)

        # nuke the session from orbit (it's the only way to be sure)
        kill_session(popened.pid, signal.SIGKILL)
    except:
        # something has gone awry, so we need to kill our children
        log.warning("something went awry! (our pid is %i)", os.getpid())

        raised = Raised()

        if exit_pid is None and popened is not None:
            try:
                # nuke the entire session
                kill_session(popened.pid, signal.SIGKILL)

                # and don't leave the child as a zombie
                os.waitpid(popened.pid, 0)
            except:
                Raised().print_ignored()

        raised.re_raise()
    else:
        # grab any output left in the kernel buffers
        while reader.fds:
            (chunk_fd, chunk) = reader.read(128.0)

            if chunk:
                fd_chunks[chunk_fd].append((accountant.total, chunk))
            elif chunk_fd:
                reader.unregister([chunk_fd])
            else:
                raise RuntimeError("final read from child timed out; undead child?")

        # done
        from datetime import timedelta

        return \
            CPU_LimitedRun(
                started,
                limit,
                fd_chunks[popened.stdout.fileno()],
                fd_chunks[popened.stderr.fileno()],
                timedelta(seconds = usage.ru_utime),
                accountant.total,
                os.WEXITSTATUS(termination) if os.WIFEXITED(termination) else None,
                os.WTERMSIG(termination) if os.WIFSIGNALED(termination) else None,
                )
    finally:
        # let's not leak file descriptors
        if popened is not None:
            popened.stdout.close()
            popened.stderr.close()
Exemple #8
0
def run_cpu_limited(
    arguments,
    limit,
    pty=True,
    environment={},
    resolution=0.5,
):
    """
    Spawn a subprocess whose process tree is granted limited CPU (user) time.

    @param environment Override specific existing environment variables.

    The subprocess must not expect input. This method is best suited to
    processes which may run for a reasonable amount of time (eg, at least
    several seconds); it will be fairly inefficient (and ineffective) at
    fine-grained limiting of CPU allocation to short-duration processes.

    We run the process and read its output. Every time we receive a chunk of
    data, or every C{resolution} seconds, we estimate the total CPU time used
    by the session---and store that information with the chunk of output, if
    any. After at least C{limit} of CPU time has been used by the spawned
    session, or after the session leader terminates, whichever is first, the
    session is (sig)killed, the session leader waited on, and any data
    remaining in the pipe is read.

    Note that the use of SIGKILL means that child processes *cannot* perform
    their own cleanup.

    If C{pty} is specified, process stdout is piped through a pty, which makes
    process output less likely to be buffered. This behavior is the default.

    Kernel-reported resource usage includes the sum of all directly and
    indirectly waited-on children. It will be accurate in the common case where
    processes terminate after correctly waiting on their children, and
    inaccurate in cases where zombies are reparented to init. Elapsed CPU time
    taken from the /proc accounting mechanism is used to do CPU time limiting,
    and will always be at least the specified limit.
    """

    log.detail("running %s for %s", arguments, limit)

    # sanity
    if not arguments:
        raise ArgumentError()

    # start the run
    from cargo.temporal import utc_now

    popened = None
    fd_chunks = {}
    exit_pid = None
    started = utc_now()

    try:
        # start running the child process
        if pty:
            popened = spawn_pty_session(arguments, environment)
        else:
            popened = spawn_pipe_session(arguments, environment)

        fd_chunks = {
            popened.stdout.fileno(): [],
            popened.stderr.fileno(): [],
        }

        log.debug("spawned child with pid %i", popened.pid)

        # read the child's output while accounting (note that the session id
        # is, under Linux, the pid of the session leader)
        accountant = SessionTimeAccountant(popened.pid)
        reader = PollingReader(fd_chunks.keys())

        while reader.fds:
            # nuke if we're past cutoff
            if accountant.total >= limit:
                popened.kill()

                break

            # read from and audit the child process
            (chunk_fd, chunk) = reader.read(resolution)

            accountant.audit()

            if chunk is not None:
                log.debug(
                    "got %i bytes at %s (user time) on fd %i; chunk follows:\n%s",
                    len(chunk),
                    accountant.total,
                    chunk_fd,
                    chunk,
                )

                if chunk != "":
                    fd_chunks[chunk_fd].append((accountant.total, chunk))
                else:
                    reader.unregister([chunk_fd])

        # wait for our child to die
        (exit_pid, termination, usage) = os.wait4(popened.pid, 0)

        # nuke the session from orbit (it's the only way to be sure)
        kill_session(popened.pid, signal.SIGKILL)
    except:
        # something has gone awry, so we need to kill our children
        log.warning("something went awry! (our pid is %i)", os.getpid())

        raised = Raised()

        if exit_pid is None and popened is not None:
            try:
                # nuke the entire session
                kill_session(popened.pid, signal.SIGKILL)

                # and don't leave the child as a zombie
                os.waitpid(popened.pid, 0)
            except:
                Raised().print_ignored()

        raised.re_raise()
    else:
        # grab any output left in the kernel buffers
        while reader.fds:
            (chunk_fd, chunk) = reader.read(128.0)

            if chunk:
                fd_chunks[chunk_fd].append((accountant.total, chunk))
            elif chunk_fd:
                reader.unregister([chunk_fd])
            else:
                raise RuntimeError(
                    "final read from child timed out; undead child?")

        # done
        from datetime import timedelta

        return \
            CPU_LimitedRun(
                started,
                limit,
                fd_chunks[popened.stdout.fileno()],
                fd_chunks[popened.stderr.fileno()],
                timedelta(seconds = usage.ru_utime),
                accountant.total,
                os.WEXITSTATUS(termination) if os.WIFEXITED(termination) else None,
                os.WTERMSIG(termination) if os.WIFSIGNALED(termination) else None,
                )
    finally:
        # let's not leak file descriptors
        if popened is not None:
            popened.stdout.close()
            popened.stderr.close()