Python Script to Run Command N Times a Second
New here? Learn about Bountify and follow @bountify to get notified of new bounties! x

Python script (python3 preferred, standard library only preferred).

The script will accept the following inputs:

  1. Number of times per second to execute command (may be 100s or more)
  2. Number of seconds to run the script for
  3. (Bash) Shell command to execute

The script will execute the provided shell command N times a second. The shell command may take 1 or more seconds to run, so the script can’t block waiting to execute the shell command again.

The script will collect the return code of the execution of each shell command run and summarize with a count of each unique return code at the end.

Crowdsource coding tasks.

2 Solutions


Super simple, works flawlessly.
I wrote this on Windows, but I'm sure it works the same with bash as it does for batch.

import os, time
def do_loop(hz, dofor, cmd):
    outputs = []
    times_to_run = hz * dofor
    for i in range(times_to_run):
        time.sleep(1/hz)
        code = os.system(cmd)
        outputs += [code]
    print(f"done! return codes: {outputs}")

# do_loop(5, 1, "echo hi")
Winning solution

Here's my solution. It doesn't block because it uses Popen instead of system and it can use a busy loop in case the granularity is too high (that should only be a problem with Windows). The cmd can be passed as a string or a function that takes the iteration number and returns a string.

#!/usr/bin/env python3

import subprocess
import time


TIMEOUT_TIME = 1.0
MAX_TIMEOUTS = 10
USB_BUSY_LOOP = False


def hifreq_cmd(cmd, freq, period, use_busy_loop = USB_BUSY_LOOP):
    if isinstance(cmd, str):
        cmd_fn = lambda i: cmd
    else:
        assert callable(cmd)
        cmd_fn = cmd

    end_time = time.time() + period
    deadline = time.time()

    children = []

    i = 0
    while time.time() < end_time:
        deadline += 1.0 / freq
        if use_busy_loop:
            while (time.time() < deadline):
                pass
        else:
            if time.time() < deadline:
                time.sleep(deadline - time.time())
        child = subprocess.Popen(cmd_fn(i).split(" "))
        children.append(child)
        i += 1

    return_codes = {}
    timing_out = {}
    while children:
        child = children.pop(0)
        if timing_out.get(child, 0) > MAX_TIMEOUTS:
            return_codes[None] = return_codes.get(None, 0) + 1
            continue
        try:
            rc = child.wait(TIMEOUT_TIME)
            return_codes[rc] = return_codes.get(rc, 0) + 1
        except subprocess.TimeoutExpired:
            children.append(child)
            timing_out[child] = timing_out.get(child, 0) + 1
    return return_codes


if __name__ == "__main__":
    """ Usage examples of hifreq_cmd
    hifreq_cmd("echo 3 times once a second", 1, 3.0)
    hifreq_cmd(lambda i: "echo %d" % i, 1000, 1.0, True)
    """

    import argparse

    parser = argparse.ArgumentParser(
        description="Runs a command repeatedly at a given rate, for a given" + 
                    " amount of time.")
    parser.add_argument("frequency", type=float,
                        help="Rate at which to run the command, in times/sec")
    parser.add_argument("period", type=float,
                        help="Amount of time to run the command, in seconds")
    parser.add_argument("command", type=str, nargs="+",
                        help="The command to run, between quotes or not")

    args = parser.parse_args()
    freq = args.frequency
    period = args.period
    cmd = " ".join(args.command)

    return_codes = hifreq_cmd(cmd, freq, period)
    total = sum(return_codes.values())
    print("Return codes:")
    for rc, n in return_codes.items():
        if rc is not None:
            print("  %d: %d/%d" % (rc, n, total))
    if None in return_codes:
        print("  Timed out: %d/%d" % (return_codes[None], total))

Edit 1: Added script argument handling.

Edit 2: Added summary of return codes.

Edit 3: Changed poll to wait and added timeout handling.

Edit 4: Cleaned up the script, added argument handling with argparse.

This is close. It doesn't capture the return codes of each execution and summarize it after finishing
Riddler 4 months ago
Sorry about, I didn't see the last line. I edited my solution to include the summary of return codes.
CyteBode 4 months ago
Seems to hang polling for the return codes. ^CTraceback (most recent call last): File "test.py", line 55, in returncodes = hifreqcmd(cmd, freq, period) File "test.py", line 37, in hifreq_cmd time.sleep(0.001) KeyboardInterrupt
Riddler 4 months ago
Looks like its a race condition. Sometimes it does exit with the summary. The longer the command takes to run the less likely it is to exit cleanly. "echo 1" almost always works, but something like "sleep 1" does not
Riddler 4 months ago
I updated the solution so it now waits 1.0 seconds after a child, before trying the others. If it waits 1.0s after the same child more than 10 times, it's considered timed out and discarded. You can change the two constants at the top if you want. It at least works with sleep 1 now.
CyteBode 4 months ago
View Timeline