Skip to content

Xil Effort

tools.xil_effort

xil_effort.py — estimate effort from git history + xil pipeline logs. Lives in tools/; run from anywhere: python3 tools/xil_effort.py [--log PATH ...] Git repo: auto-detected from this script's location. Data root: $XIL_PROJECTROOT — logs read from $XIL_PROJECTROOT/logs/*.log, outputs written to $XIL_PROJECTROOT/reports/. Falls back to the repo root for both if $XIL_PROJECTROOT is unset.

REPO module-attribute

REPO = find_repo()

DATA module-attribute

DATA = Path(os.environ.get('XIL_PROJECTROOT') or REPO)

REPORTS module-attribute

REPORTS = DATA / 'reports'

ap module-attribute

ap = argparse.ArgumentParser()

args module-attribute

args = ap.parse_args()

commits module-attribute

commits = git('log', '--date=short', '--pretty=%ad')

merges module-attribute

merges = git('log', '--merges', '--date=short', '--pretty=%ad')

day_commits module-attribute

day_commits = defaultdict(int)

day_merges module-attribute

day_merges = defaultdict(int)

day_ins module-attribute

day_ins = defaultdict(int)

day_del module-attribute

day_del = defaultdict(int)

day_pipe module-attribute

day_pipe = defaultdict(int)

cmd_counts module-attribute

cmd_counts = defaultdict(int)

FNAME_DATE module-attribute

FNAME_DATE = re.compile('xil_(?:v\\d+_)?(\\d{4}-\\d{2}-\\d{2})(?:_[^/]*)?\\.log$')

HEADER module-attribute

HEADER = re.compile('^([A-Z][A-Z0-9_/-]+(?: [A-Z0-9_/-]+)+)')

LEVEL module-attribute

LEVEL = re.compile('^[A-Z]+$')

matched_files module-attribute

matched_files = 0

fname_day module-attribute

fname_day = None

m module-attribute

m = FNAME_DATE.search(path)

parts module-attribute

parts = [(p.strip()) for p in (line.split('|'))]

day module-attribute

day = dt.date.fromisoformat(parts[0][:10]).isoformat()

stage module-attribute

stage = parts[3]

h module-attribute

h = HEADER.match(line)

name module-attribute

name = h.group(1)

msg module-attribute

msg = 'no log files matched' if matched_files == 0 else f'{matched_files} log file(s) matched but no activity was parsed'

cur module-attribute

cur = None

line module-attribute

line = line.strip()

ins module-attribute

ins = re.search('(\\d+) insertion', line)

dele module-attribute

dele = re.search('(\\d+) deletion', line)

weeks module-attribute

weeks = defaultdict(lambda: {'commits': 0, 'merges': 0, 'days': set(), 'ins': 0, 'del': 0, 'pipe': 0})

wk module-attribute

wk = weeks[iso_week(d)]

csv_out module-attribute

csv_out = open(REPORTS / 'effort_weekly.csv', 'w', newline='')

cw module-attribute

cw = csv.writer(csv_out)

w module-attribute

w = weeks[wk]

t module-attribute

t = tier(n, w['ins'] + w['del'])

tot_c module-attribute

tot_c = sum((w['commits']) for w in (weeks.values()))

tot_m module-attribute

tot_m = sum((w['merges']) for w in (weeks.values()))

tot_p module-attribute

tot_p = sum((w['pipe']) for w in (weeks.values()))

tot_d module-attribute

tot_d = sum((len(w['days'])) for w in (weeks.values()))

tot_i module-attribute

tot_i = sum((w['ins']) for w in (weeks.values()))

tot_x module-attribute

tot_x = sum((w['del']) for w in (weeks.values()))

all_days module-attribute

all_days = set(day_commits) | set(day_pipe)

first module-attribute

first = min((dt.date.fromisoformat(d)) for d in all_days)

last module-attribute

last = max((dt.date.fromisoformat(d)) for d in all_days)

start module-attribute

start = first - dt.timedelta(days=first.weekday())

peak module-attribute

peak = max(day_commits.values()) if day_commits else 1

cells module-attribute

cells = defaultdict(dict)

n module-attribute

n = day_commits.get(d.isoformat(), 0)

rows module-attribute

rows = []

tds module-attribute

tds = ''.join((f'<td title="{c[0]}{c[1]} commits, {day_pipe.get(c[0].isoformat(), 0)} pipe runs, +{day_ins.get(c[0].isoformat(), 0)}/−{day_del.get(c[0].isoformat(), 0)} lines" style="{cell_style(c[0], c[1])}"></td>' if (c := (cells[wd].get(i))) else '<td></td>') for i in (range(wi + 1)))

html module-attribute

html = f'<html><body style='font-family:sans-serif'><h3>xil-pipeline activity ({first}{last})</h3><table style='border-spacing:2px'>{''.join(rows)}</table><p>{tot_c} commits, {tot_m} merges, {tot_p} pipeline runs, {tot_d} active days, +{tot_i}/−{tot_x} lines. Green = commits (peak {peak}/day); orange outline = pipeline activity.</p></body></html>'

find_repo

find_repo()
Source code in tools/xil_effort.py
def find_repo():
    here = Path(__file__).resolve().parent
    try:
        top = subprocess.run(["git", "-C", str(here), "rev-parse", "--show-toplevel"],
            capture_output=True, text=True, check=True).stdout.strip()
        return Path(top)
    except subprocess.CalledProcessError:
        raise SystemExit(f"No git repo found at or above {here}")

git

git(*a)
Source code in tools/xil_effort.py
def git(*a):
    return subprocess.run(["git", "-C", str(REPO)] + list(a),
        capture_output=True, text=True, check=True).stdout.splitlines()

iso_week

iso_week(datestr)
Source code in tools/xil_effort.py
def iso_week(datestr):
    y, w, _ = dt.date.fromisoformat(datestr).isocalendar()
    return f"{y}-W{w:02d}"

tier

tier(active_days, churn)
Source code in tools/xil_effort.py
def tier(active_days, churn):
    base = 2 if active_days >= 4 else 1 if active_days >= 2 else 0
    if churn >= 1000: base = min(base + 1, 2)   # big churn bumps a tier
    return ["Low", "Medium", "High"][base]

color

color(n)
Source code in tools/xil_effort.py
def color(n):
    if n == 0: return "#ebedf0"
    steps = ["#9be9a8", "#40c463", "#30a14e", "#216e39"]
    return steps[min(int(n / peak * 4), 3)]

cell_style

cell_style(day, n)
Source code in tools/xil_effort.py
def cell_style(day, n):
    s = f"width:12px;height:12px;background:{color(n)};border-radius:2px"
    if day_pipe.get(day.isoformat(), 0):
        s += ";outline:2px solid #f66a0a;outline-offset:-2px"  # pipeline marker
    return s