diff --git a/tools/mtstats.py b/tools/mtstats.py new file mode 100755 index 0000000000..5c22ea394d --- /dev/null +++ b/tools/mtstats.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2019-present Team LibreELEC (https://libreelec.tv) + +import sys, os, codecs, datetime, time + +EPOCH = datetime.datetime(1970, 1, 1) + +class HistoryEvent: + def __init__(self, event): + # squash spaces, then split by space + items = ' '.join(event.replace("\n", "").split()).split(" ") + + self.datetime = "%s %s" % (items[0], items[1][:-4]) + (self.slot, self.seq) = items[3][1:-1].split("/") + self.status = items[4] + self.task = items[5] if len(items) > 5 else "" + self.secs = None + + def __repr__(self): + return "%s; %s; %s; %s; %s" % (self.datetime, self.slot, self.seq, self.status, self.task) + + def get_time_secs(self): + if self.secs == None: + self.secs = (datetime.datetime.strptime(self.datetime, "%Y-%m-%d %H:%M:%S.%f") - EPOCH).total_seconds() + return self.secs + +def secs_to_hms(secs): + return "%02d:%02d:%06.3f" % (int(secs / 3600), int(secs / 60 % 60), secs % 60) + +def get_slot_val(slot): + return slots[slot]["total"] + +def get_concurrent_val(concurrent): + return concurrency[concurrent]["total"] + +#--------------------------- + +events = [] +for event in sys.stdin: events.append(HistoryEvent(event)) + +if len(events) == 0: + sys.exit(1) + +started = events[0].get_time_secs() +ended = events[-1].get_time_secs() +now = time.time() + +slots = {} +concurrency = {} +active = peak = 0 +for event in events: + slot = slots.get(event.slot, {"active": False, + "start": 0.0, + "total": 0.0}) + + if event.status in ["ACTIVE", "MUTEX"]: + if slot["active"] == False: + active += 1 + concurrent = concurrency.get(active, {"start": 0.0, "total": 0.0}) + concurrent["start"] = event.get_time_secs() + concurrency[active] = concurrent + + slot["active"] = True + slot["start"] = event.get_time_secs() + slots[event.slot] = slot + + if active > peak: + peak = active + + elif slot["active"] == True: + concurrency[active]["total"] += (event.get_time_secs() - concurrency[active]["start"]) + active -= 1 + slot["active"] = False + slot["total"] += (event.get_time_secs() - slot["start"]) + slots[event.slot] = slot + +# If any slots remain active, either the build failed or the build is ongoing, in +# which case "close" the active slots with the current time. +for slotn in slots: + slot = slots[slotn] + if slot["active"] == True: + ended = now + slot["total"] += (now - slot["start"]) + +elapsed = (ended - started) + +print("Total Build Time: %s\n" % secs_to_hms(elapsed)) + +print("Peak concurrency: %d out of %d slots\n" % (peak, len(slots))) + +print("Slot usage (time in ACTIVE state): | Concurrency breakdown:\n") +print("#Rank Slot Usage ( Pct ) # of Slots Usage ( Pct )") + +lines = [] + +for rank, slotn in enumerate(sorted(slots, key=get_slot_val, reverse=True)): + slot = slots[slotn] + pct = (100 * slot["total"] / elapsed) if elapsed > 0.0 else 0.0 + state = " active" if slot["active"] else " " + stime = secs_to_hms(slot["total"]).replace("00:", " :") + lines.append("%s %s (%04.1f%%)%s" % (slotn, stime, pct, state)) + +for rank, concurrentn in enumerate(sorted(concurrency, key=get_concurrent_val, reverse=True)): + concurrent = concurrency[concurrentn] + pct = (100 * concurrent["total"] / elapsed) if elapsed > 0.0 else 0.0 + stime = secs_to_hms(concurrent["total"]).replace("00:", " :") + lines[rank] += " %02d %s (%04.1f%%)" % (concurrentn, stime, pct) + +for rank, line in enumerate(lines): + print(" #%02d %s" % (rank + 1, line))