|
#!/usr/bin/python3 |
|
"""timestamp-output |
|
|
|
This annotates output the same way that annotate-output from |
|
the ubuntu devscripts does. The benefit of this over that is |
|
primarily in that it does not create a subprocess for every |
|
line read (annotate-output users `date` to get the timestamp)""" |
|
|
|
import argparse |
|
import datetime |
|
import os |
|
import re |
|
import subprocess |
|
import sys |
|
import threading |
|
|
|
|
|
START_TIME = datetime.datetime.now() |
|
DELTA_FMT_RE = re.compile('(?P<tok>%(s|-?\d*.?\d+s))') |
|
|
|
FD_STDOUT = 1 |
|
FD_STDERR = 2 |
|
|
|
|
|
def fp_readlines(fp, sizehint=None): |
|
if sizehint is None: |
|
sizehint = 1024 |
|
leftover = bytes() |
|
while True: |
|
buf = os.read(fp, sizehint) |
|
consumed = buf == b'' |
|
|
|
if leftover: |
|
buf = leftover + buf |
|
leftover = bytes() |
|
|
|
while True: |
|
end = buf.find(b'\n') |
|
if end != -1: |
|
yield buf[0:end+1] |
|
buf = buf[end+1:] |
|
else: |
|
leftover = buf |
|
break |
|
|
|
if consumed: |
|
break |
|
|
|
|
|
def ftime(fmt, ts=None): |
|
if ts is None: |
|
ts = datetime.datetime.now() |
|
return datetime.datetime.strftime(ts, fmt) |
|
|
|
|
|
def uftime(fmt, ts=None, ref=None): |
|
if ref is None: |
|
ref = START_TIME |
|
if ts is None: |
|
ts = datetime.datetime.now() |
|
match = DELTA_FMT_RE.search(fmt) |
|
if match: |
|
fmtstr = match.group('tok') |
|
fmtfloat = fmtstr[:-1] + "f" |
|
fmt = fmt.replace( |
|
fmtstr, fmtfloat % (ts - ref).total_seconds(), 1) |
|
return ftime(fmt, ts) |
|
|
|
|
|
def needs_delta(fmt): |
|
return bool(DELTA_FMT_RE.search(fmt)) |
|
|
|
|
|
def addtime(fh_in, fh_out, fmt='%H:%M:%S '): |
|
if needs_delta(fmt): |
|
fmtfunc = uftime |
|
else: |
|
fmtfunc = ftime |
|
|
|
for line in fp_readlines(fh_in): |
|
os.write(fh_out, fmtfunc(fmt).encode() + line) |
|
|
|
return |
|
|
|
|
|
def main(): |
|
hmr = "%H:%M:%S" |
|
ymd = "%Y-%m-%d" |
|
def_out_fmt = hmr + " O: " |
|
def_err_fmt = hmr + " E: " |
|
delta_fmt = "[%8.3s] " |
|
date_delta_fmt = " ".join((ymd, hmr, delta_fmt)) |
|
|
|
parser = argparse.ArgumentParser() |
|
parser.add_argument('-o', '--stdout-prefix', |
|
dest='out_fmt', default=def_out_fmt, |
|
help='the format prefix for stdout') |
|
parser.add_argument('-e', '--stderr-prefix', |
|
dest='err_fmt', default=def_err_fmt, |
|
help='the format prefix for stderr') |
|
group = parser.add_mutually_exclusive_group() |
|
group.add_argument('-O', '--output-prefix', |
|
dest='all_fmt', default=None, |
|
help='the format prefix for both out and err') |
|
mhelp = 'Shortcut for --output-prefix=' + delta_fmt.replace("%", "%%") |
|
group.add_argument('-d', '--delta', action='store_const', |
|
dest='all_fmt', default=None, const=delta_fmt, |
|
help=mhelp) |
|
mhelp = 'Shortcut for --output-prefix=' + date_delta_fmt.replace("%", "%%") |
|
group.add_argument('-D', '--date-delta', action='store_const', |
|
dest='all_fmt', default=None, const=date_delta_fmt, |
|
help=mhelp) |
|
|
|
parser.add_argument('cmd', nargs='+', default=[]) |
|
|
|
args = parser.parse_args() |
|
|
|
if args.all_fmt: |
|
if args.out_fmt != def_out_fmt or args.err_fmt != def_err_fmt: |
|
sys.stderr.write( |
|
"--stdout-prefix/--stderr-prefix are " |
|
"incompatible with --output-prefix and --delta.\n") |
|
sys.exit(1) |
|
args.out_fmt = args.all_fmt.replace("_S_", "O") |
|
args.err_fmt = args.all_fmt.replace("_S_", "E") |
|
|
|
threads = [] |
|
cmd = args.cmd |
|
|
|
out_r, out_w = os.pipe() |
|
err_r, err_w = os.pipe() |
|
|
|
try: |
|
t = threading.Thread( |
|
target=addtime, args=(out_r, FD_STDOUT, args.out_fmt)) |
|
t.start() |
|
threads.append(t) |
|
|
|
t = threading.Thread( |
|
target=addtime, args=(err_r, FD_STDERR, args.err_fmt)) |
|
t.start() |
|
threads.append(t) |
|
|
|
sp = subprocess.Popen(cmd, stdout=out_w, stderr=err_w) |
|
sp.communicate() |
|
rc = sp.returncode |
|
|
|
sys.exit(rc) |
|
|
|
finally: |
|
os.close(out_w) |
|
os.close(err_w) |
|
|
|
for t in threads: |
|
t.join() |
|
|
|
os.close(out_r) |
|
os.close(err_r) |
|
|
|
|
|
main() |