17db96d56Sopenharmony_ci"""Print a summary of specialization stats for all files in the
27db96d56Sopenharmony_cidefault stats folders.
37db96d56Sopenharmony_ci"""
47db96d56Sopenharmony_ci
57db96d56Sopenharmony_ciimport collections
67db96d56Sopenharmony_ciimport os.path
77db96d56Sopenharmony_ciimport opcode
87db96d56Sopenharmony_cifrom datetime import date
97db96d56Sopenharmony_ciimport itertools
107db96d56Sopenharmony_ciimport argparse
117db96d56Sopenharmony_ci
127db96d56Sopenharmony_ciif os.name == "nt":
137db96d56Sopenharmony_ci    DEFAULT_DIR = "c:\\temp\\py_stats\\"
147db96d56Sopenharmony_cielse:
157db96d56Sopenharmony_ci    DEFAULT_DIR = "/tmp/py_stats/"
167db96d56Sopenharmony_ci
177db96d56Sopenharmony_ci#Create list of all instruction names
187db96d56Sopenharmony_cispecialized = iter(opcode._specialized_instructions)
197db96d56Sopenharmony_ciopname = ["<0>"]
207db96d56Sopenharmony_cifor name in opcode.opname[1:]:
217db96d56Sopenharmony_ci    if name.startswith("<"):
227db96d56Sopenharmony_ci        try:
237db96d56Sopenharmony_ci            name = next(specialized)
247db96d56Sopenharmony_ci        except StopIteration:
257db96d56Sopenharmony_ci            pass
267db96d56Sopenharmony_ci    opname.append(name)
277db96d56Sopenharmony_ci
287db96d56Sopenharmony_ci# opcode_name --> opcode
297db96d56Sopenharmony_ci# Sort alphabetically.
307db96d56Sopenharmony_ciopmap = {name: i for i, name in enumerate(opname)}
317db96d56Sopenharmony_ciopmap = dict(sorted(opmap.items()))
327db96d56Sopenharmony_ci
337db96d56Sopenharmony_ciTOTAL = "specialization.deferred", "specialization.hit", "specialization.miss", "execution_count"
347db96d56Sopenharmony_ci
357db96d56Sopenharmony_cidef print_specialization_stats(name, family_stats, defines):
367db96d56Sopenharmony_ci    if "specializable" not in family_stats:
377db96d56Sopenharmony_ci        return
387db96d56Sopenharmony_ci    total = sum(family_stats.get(kind, 0) for kind in TOTAL)
397db96d56Sopenharmony_ci    if total == 0:
407db96d56Sopenharmony_ci        return
417db96d56Sopenharmony_ci    with Section(name, 3, f"specialization stats for {name} family"):
427db96d56Sopenharmony_ci        rows = []
437db96d56Sopenharmony_ci        for key in sorted(family_stats):
447db96d56Sopenharmony_ci            if key.startswith("specialization.failure_kinds"):
457db96d56Sopenharmony_ci                continue
467db96d56Sopenharmony_ci            if key in ("specialization.hit", "specialization.miss"):
477db96d56Sopenharmony_ci                label = key[len("specialization."):]
487db96d56Sopenharmony_ci            elif key == "execution_count":
497db96d56Sopenharmony_ci                label = "unquickened"
507db96d56Sopenharmony_ci            elif key in ("specialization.success",  "specialization.failure", "specializable"):
517db96d56Sopenharmony_ci                continue
527db96d56Sopenharmony_ci            elif key.startswith("pair"):
537db96d56Sopenharmony_ci                continue
547db96d56Sopenharmony_ci            else:
557db96d56Sopenharmony_ci                label = key
567db96d56Sopenharmony_ci            rows.append((f"{label:>12}", f"{family_stats[key]:>12}", f"{100*family_stats[key]/total:0.1f}%"))
577db96d56Sopenharmony_ci        emit_table(("Kind", "Count", "Ratio"), rows)
587db96d56Sopenharmony_ci        print_title("Specialization attempts", 4)
597db96d56Sopenharmony_ci        total_attempts = 0
607db96d56Sopenharmony_ci        for key in ("specialization.success",  "specialization.failure"):
617db96d56Sopenharmony_ci            total_attempts += family_stats.get(key, 0)
627db96d56Sopenharmony_ci        rows = []
637db96d56Sopenharmony_ci        for key in ("specialization.success",  "specialization.failure"):
647db96d56Sopenharmony_ci            label = key[len("specialization."):]
657db96d56Sopenharmony_ci            label = label[0].upper() + label[1:]
667db96d56Sopenharmony_ci            val = family_stats.get(key, 0)
677db96d56Sopenharmony_ci            rows.append((label, val, f"{100*val/total_attempts:0.1f}%"))
687db96d56Sopenharmony_ci        emit_table(("", "Count:", "Ratio:"), rows)
697db96d56Sopenharmony_ci        total_failures = family_stats.get("specialization.failure", 0)
707db96d56Sopenharmony_ci        failure_kinds = [ 0 ] * 30
717db96d56Sopenharmony_ci        for key in family_stats:
727db96d56Sopenharmony_ci            if not key.startswith("specialization.failure_kind"):
737db96d56Sopenharmony_ci                continue
747db96d56Sopenharmony_ci            _, index = key[:-1].split("[")
757db96d56Sopenharmony_ci            index =  int(index)
767db96d56Sopenharmony_ci            failure_kinds[index] = family_stats[key]
777db96d56Sopenharmony_ci        failures = [(value, index) for (index, value) in enumerate(failure_kinds)]
787db96d56Sopenharmony_ci        failures.sort(reverse=True)
797db96d56Sopenharmony_ci        rows = []
807db96d56Sopenharmony_ci        for value, index in failures:
817db96d56Sopenharmony_ci            if not value:
827db96d56Sopenharmony_ci                continue
837db96d56Sopenharmony_ci            rows.append((kind_to_text(index, defines, name), value, f"{100*value/total_failures:0.1f}%"))
847db96d56Sopenharmony_ci        emit_table(("Failure kind", "Count:", "Ratio:"), rows)
857db96d56Sopenharmony_ci
867db96d56Sopenharmony_cidef gather_stats():
877db96d56Sopenharmony_ci    stats = collections.Counter()
887db96d56Sopenharmony_ci    for filename in os.listdir(DEFAULT_DIR):
897db96d56Sopenharmony_ci        with open(os.path.join(DEFAULT_DIR, filename)) as fd:
907db96d56Sopenharmony_ci            for line in fd:
917db96d56Sopenharmony_ci                key, value = line.split(":")
927db96d56Sopenharmony_ci                key = key.strip()
937db96d56Sopenharmony_ci                value = int(value)
947db96d56Sopenharmony_ci                stats[key] += value
957db96d56Sopenharmony_ci    return stats
967db96d56Sopenharmony_ci
977db96d56Sopenharmony_cidef extract_opcode_stats(stats):
987db96d56Sopenharmony_ci    opcode_stats = [ {} for _ in range(256) ]
997db96d56Sopenharmony_ci    for key, value in stats.items():
1007db96d56Sopenharmony_ci        if not key.startswith("opcode"):
1017db96d56Sopenharmony_ci            continue
1027db96d56Sopenharmony_ci        n, _, rest = key[7:].partition("]")
1037db96d56Sopenharmony_ci        opcode_stats[int(n)][rest.strip(".")] = value
1047db96d56Sopenharmony_ci    return opcode_stats
1057db96d56Sopenharmony_ci
1067db96d56Sopenharmony_cidef parse_kinds(spec_src):
1077db96d56Sopenharmony_ci    defines = collections.defaultdict(list)
1087db96d56Sopenharmony_ci    for line in spec_src:
1097db96d56Sopenharmony_ci        line = line.strip()
1107db96d56Sopenharmony_ci        if not line.startswith("#define SPEC_FAIL_"):
1117db96d56Sopenharmony_ci            continue
1127db96d56Sopenharmony_ci        line = line[len("#define SPEC_FAIL_"):]
1137db96d56Sopenharmony_ci        name, val = line.split()
1147db96d56Sopenharmony_ci        defines[int(val.strip())].append(name.strip())
1157db96d56Sopenharmony_ci    return defines
1167db96d56Sopenharmony_ci
1177db96d56Sopenharmony_cidef pretty(defname):
1187db96d56Sopenharmony_ci    return defname.replace("_", " ").lower()
1197db96d56Sopenharmony_ci
1207db96d56Sopenharmony_cidef kind_to_text(kind, defines, opname):
1217db96d56Sopenharmony_ci    if kind < 7:
1227db96d56Sopenharmony_ci        return pretty(defines[kind][0])
1237db96d56Sopenharmony_ci    if opname.endswith("ATTR"):
1247db96d56Sopenharmony_ci        opname = "ATTR"
1257db96d56Sopenharmony_ci    if opname.endswith("SUBSCR"):
1267db96d56Sopenharmony_ci        opname = "SUBSCR"
1277db96d56Sopenharmony_ci    if opname.startswith("PRECALL"):
1287db96d56Sopenharmony_ci        opname = "CALL"
1297db96d56Sopenharmony_ci    for name in defines[kind]:
1307db96d56Sopenharmony_ci        if name.startswith(opname):
1317db96d56Sopenharmony_ci            return pretty(name[len(opname)+1:])
1327db96d56Sopenharmony_ci    return "kind " + str(kind)
1337db96d56Sopenharmony_ci
1347db96d56Sopenharmony_cidef categorized_counts(opcode_stats):
1357db96d56Sopenharmony_ci    basic = 0
1367db96d56Sopenharmony_ci    specialized = 0
1377db96d56Sopenharmony_ci    not_specialized = 0
1387db96d56Sopenharmony_ci    specialized_instructions = {
1397db96d56Sopenharmony_ci        op for op in opcode._specialized_instructions
1407db96d56Sopenharmony_ci        if "__" not in op and "ADAPTIVE" not in op}
1417db96d56Sopenharmony_ci    adaptive_instructions = {
1427db96d56Sopenharmony_ci        op for op in opcode._specialized_instructions
1437db96d56Sopenharmony_ci        if "ADAPTIVE" in op}
1447db96d56Sopenharmony_ci    for i, opcode_stat in enumerate(opcode_stats):
1457db96d56Sopenharmony_ci        if "execution_count" not in opcode_stat:
1467db96d56Sopenharmony_ci            continue
1477db96d56Sopenharmony_ci        count = opcode_stat['execution_count']
1487db96d56Sopenharmony_ci        name = opname[i]
1497db96d56Sopenharmony_ci        if "specializable" in opcode_stat:
1507db96d56Sopenharmony_ci            not_specialized += count
1517db96d56Sopenharmony_ci        elif name in adaptive_instructions:
1527db96d56Sopenharmony_ci            not_specialized += count
1537db96d56Sopenharmony_ci        elif name in specialized_instructions:
1547db96d56Sopenharmony_ci            miss = opcode_stat.get("specialization.miss", 0)
1557db96d56Sopenharmony_ci            not_specialized += miss
1567db96d56Sopenharmony_ci            specialized += count - miss
1577db96d56Sopenharmony_ci        else:
1587db96d56Sopenharmony_ci            basic += count
1597db96d56Sopenharmony_ci    return basic, not_specialized, specialized
1607db96d56Sopenharmony_ci
1617db96d56Sopenharmony_cidef print_title(name, level=2):
1627db96d56Sopenharmony_ci    print("#"*level, name)
1637db96d56Sopenharmony_ci    print()
1647db96d56Sopenharmony_ci
1657db96d56Sopenharmony_ciclass Section:
1667db96d56Sopenharmony_ci
1677db96d56Sopenharmony_ci    def __init__(self, title, level=2, summary=None):
1687db96d56Sopenharmony_ci        self.title = title
1697db96d56Sopenharmony_ci        self.level = level
1707db96d56Sopenharmony_ci        if summary is None:
1717db96d56Sopenharmony_ci            self.summary = title.lower()
1727db96d56Sopenharmony_ci        else:
1737db96d56Sopenharmony_ci            self.summary = summary
1747db96d56Sopenharmony_ci
1757db96d56Sopenharmony_ci    def __enter__(self):
1767db96d56Sopenharmony_ci        print_title(self.title, self.level)
1777db96d56Sopenharmony_ci        print("<details>")
1787db96d56Sopenharmony_ci        print("<summary>", self.summary, "</summary>")
1797db96d56Sopenharmony_ci        print()
1807db96d56Sopenharmony_ci        return self
1817db96d56Sopenharmony_ci
1827db96d56Sopenharmony_ci    def __exit__(*args):
1837db96d56Sopenharmony_ci        print()
1847db96d56Sopenharmony_ci        print("</details>")
1857db96d56Sopenharmony_ci        print()
1867db96d56Sopenharmony_ci
1877db96d56Sopenharmony_cidef emit_table(header, rows):
1887db96d56Sopenharmony_ci    width = len(header)
1897db96d56Sopenharmony_ci    header_line = "|"
1907db96d56Sopenharmony_ci    under_line = "|"
1917db96d56Sopenharmony_ci    for item in header:
1927db96d56Sopenharmony_ci        under = "---"
1937db96d56Sopenharmony_ci        if item.endswith(":"):
1947db96d56Sopenharmony_ci            item = item[:-1]
1957db96d56Sopenharmony_ci            under += ":"
1967db96d56Sopenharmony_ci        header_line += item + " | "
1977db96d56Sopenharmony_ci        under_line += under + "|"
1987db96d56Sopenharmony_ci    print(header_line)
1997db96d56Sopenharmony_ci    print(under_line)
2007db96d56Sopenharmony_ci    for row in rows:
2017db96d56Sopenharmony_ci        if width is not None and len(row) != width:
2027db96d56Sopenharmony_ci            raise ValueError("Wrong number of elements in row '" + str(rows) + "'")
2037db96d56Sopenharmony_ci        print("|", " | ".join(str(i) for i in row), "|")
2047db96d56Sopenharmony_ci    print()
2057db96d56Sopenharmony_ci
2067db96d56Sopenharmony_cidef emit_execution_counts(opcode_stats, total):
2077db96d56Sopenharmony_ci    with Section("Execution counts", summary="execution counts for all instructions"):
2087db96d56Sopenharmony_ci        counts = []
2097db96d56Sopenharmony_ci        for i, opcode_stat in enumerate(opcode_stats):
2107db96d56Sopenharmony_ci            if "execution_count" in opcode_stat:
2117db96d56Sopenharmony_ci                count = opcode_stat['execution_count']
2127db96d56Sopenharmony_ci                miss = 0
2137db96d56Sopenharmony_ci                if "specializable" not in opcode_stat:
2147db96d56Sopenharmony_ci                    miss = opcode_stat.get("specialization.miss")
2157db96d56Sopenharmony_ci                counts.append((count, opname[i], miss))
2167db96d56Sopenharmony_ci        counts.sort(reverse=True)
2177db96d56Sopenharmony_ci        cumulative = 0
2187db96d56Sopenharmony_ci        rows = []
2197db96d56Sopenharmony_ci        for (count, name, miss) in counts:
2207db96d56Sopenharmony_ci            cumulative += count
2217db96d56Sopenharmony_ci            if miss:
2227db96d56Sopenharmony_ci                miss =  f"{100*miss/count:0.1f}%"
2237db96d56Sopenharmony_ci            else:
2247db96d56Sopenharmony_ci                miss = ""
2257db96d56Sopenharmony_ci            rows.append((name, count, f"{100*count/total:0.1f}%",
2267db96d56Sopenharmony_ci                        f"{100*cumulative/total:0.1f}%", miss))
2277db96d56Sopenharmony_ci        emit_table(
2287db96d56Sopenharmony_ci            ("Name", "Count:", "Self:", "Cumulative:", "Miss ratio:"),
2297db96d56Sopenharmony_ci            rows
2307db96d56Sopenharmony_ci        )
2317db96d56Sopenharmony_ci
2327db96d56Sopenharmony_ci
2337db96d56Sopenharmony_cidef emit_specialization_stats(opcode_stats):
2347db96d56Sopenharmony_ci    spec_path = os.path.join(os.path.dirname(__file__), "../../Python/specialize.c")
2357db96d56Sopenharmony_ci    with open(spec_path) as spec_src:
2367db96d56Sopenharmony_ci        defines = parse_kinds(spec_src)
2377db96d56Sopenharmony_ci    with Section("Specialization stats", summary="specialization stats by family"):
2387db96d56Sopenharmony_ci        for i, opcode_stat in enumerate(opcode_stats):
2397db96d56Sopenharmony_ci            name = opname[i]
2407db96d56Sopenharmony_ci            print_specialization_stats(name, opcode_stat, defines)
2417db96d56Sopenharmony_ci
2427db96d56Sopenharmony_cidef emit_specialization_overview(opcode_stats, total):
2437db96d56Sopenharmony_ci    basic, not_specialized, specialized = categorized_counts(opcode_stats)
2447db96d56Sopenharmony_ci    with Section("Specialization effectiveness"):
2457db96d56Sopenharmony_ci        emit_table(("Instructions", "Count:", "Ratio:"), (
2467db96d56Sopenharmony_ci            ("Basic", basic, f"{basic*100/total:0.1f}%"),
2477db96d56Sopenharmony_ci            ("Not specialized", not_specialized, f"{not_specialized*100/total:0.1f}%"),
2487db96d56Sopenharmony_ci            ("Specialized", specialized, f"{specialized*100/total:0.1f}%"),
2497db96d56Sopenharmony_ci        ))
2507db96d56Sopenharmony_ci
2517db96d56Sopenharmony_cidef emit_call_stats(stats):
2527db96d56Sopenharmony_ci    with Section("Call stats", summary="Inlined calls and frame stats"):
2537db96d56Sopenharmony_ci        total = 0
2547db96d56Sopenharmony_ci        for key, value in stats.items():
2557db96d56Sopenharmony_ci            if "Calls to" in key:
2567db96d56Sopenharmony_ci                total += value
2577db96d56Sopenharmony_ci        rows = []
2587db96d56Sopenharmony_ci        for key, value in stats.items():
2597db96d56Sopenharmony_ci            if "Calls to" in key:
2607db96d56Sopenharmony_ci                rows.append((key, value, f"{100*value/total:0.1f}%"))
2617db96d56Sopenharmony_ci        for key, value in stats.items():
2627db96d56Sopenharmony_ci            if key.startswith("Frame"):
2637db96d56Sopenharmony_ci                rows.append((key, value, f"{100*value/total:0.1f}%"))
2647db96d56Sopenharmony_ci        emit_table(("", "Count:", "Ratio:"), rows)
2657db96d56Sopenharmony_ci
2667db96d56Sopenharmony_cidef emit_object_stats(stats):
2677db96d56Sopenharmony_ci    with Section("Object stats", summary="allocations, frees and dict materializatons"):
2687db96d56Sopenharmony_ci        total = stats.get("Object new values")
2697db96d56Sopenharmony_ci        rows = []
2707db96d56Sopenharmony_ci        for key, value in stats.items():
2717db96d56Sopenharmony_ci            if key.startswith("Object"):
2727db96d56Sopenharmony_ci                if "materialize" in key:
2737db96d56Sopenharmony_ci                    materialize = f"{100*value/total:0.1f}%"
2747db96d56Sopenharmony_ci                else:
2757db96d56Sopenharmony_ci                    materialize = ""
2767db96d56Sopenharmony_ci                label = key[6:].strip()
2777db96d56Sopenharmony_ci                label = label[0].upper() + label[1:]
2787db96d56Sopenharmony_ci                rows.append((label, value, materialize))
2797db96d56Sopenharmony_ci        emit_table(("",  "Count:", "Ratio:"), rows)
2807db96d56Sopenharmony_ci
2817db96d56Sopenharmony_cidef get_total(opcode_stats):
2827db96d56Sopenharmony_ci    total = 0
2837db96d56Sopenharmony_ci    for opcode_stat in opcode_stats:
2847db96d56Sopenharmony_ci        if "execution_count" in opcode_stat:
2857db96d56Sopenharmony_ci            total += opcode_stat['execution_count']
2867db96d56Sopenharmony_ci    return total
2877db96d56Sopenharmony_ci
2887db96d56Sopenharmony_cidef emit_pair_counts(opcode_stats, total):
2897db96d56Sopenharmony_ci    pair_counts = []
2907db96d56Sopenharmony_ci    for i, opcode_stat in enumerate(opcode_stats):
2917db96d56Sopenharmony_ci        if i == 0:
2927db96d56Sopenharmony_ci            continue
2937db96d56Sopenharmony_ci        for key, value in opcode_stat.items():
2947db96d56Sopenharmony_ci            if key.startswith("pair_count"):
2957db96d56Sopenharmony_ci                x, _, _ = key[11:].partition("]")
2967db96d56Sopenharmony_ci                if value:
2977db96d56Sopenharmony_ci                    pair_counts.append((value, (i, int(x))))
2987db96d56Sopenharmony_ci    with Section("Pair counts", summary="Pair counts for top 100 pairs"):
2997db96d56Sopenharmony_ci        pair_counts.sort(reverse=True)
3007db96d56Sopenharmony_ci        cumulative = 0
3017db96d56Sopenharmony_ci        rows = []
3027db96d56Sopenharmony_ci        for (count, pair) in itertools.islice(pair_counts, 100):
3037db96d56Sopenharmony_ci            i, j = pair
3047db96d56Sopenharmony_ci            cumulative += count
3057db96d56Sopenharmony_ci            rows.append((opname[i] + " " + opname[j], count, f"{100*count/total:0.1f}%",
3067db96d56Sopenharmony_ci                        f"{100*cumulative/total:0.1f}%"))
3077db96d56Sopenharmony_ci        emit_table(("Pair", "Count:", "Self:", "Cumulative:"),
3087db96d56Sopenharmony_ci            rows
3097db96d56Sopenharmony_ci        )
3107db96d56Sopenharmony_ci    with Section("Predecessor/Successor Pairs", summary="Top 3 predecessors and successors of each opcode"):
3117db96d56Sopenharmony_ci        predecessors = collections.defaultdict(collections.Counter)
3127db96d56Sopenharmony_ci        successors = collections.defaultdict(collections.Counter)
3137db96d56Sopenharmony_ci        total_predecessors = collections.Counter()
3147db96d56Sopenharmony_ci        total_successors = collections.Counter()
3157db96d56Sopenharmony_ci        for count, (first, second) in pair_counts:
3167db96d56Sopenharmony_ci            if count:
3177db96d56Sopenharmony_ci                predecessors[second][first] = count
3187db96d56Sopenharmony_ci                successors[first][second] = count
3197db96d56Sopenharmony_ci                total_predecessors[second] += count
3207db96d56Sopenharmony_ci                total_successors[first] += count
3217db96d56Sopenharmony_ci        for name, i in opmap.items():
3227db96d56Sopenharmony_ci            total1 = total_predecessors[i]
3237db96d56Sopenharmony_ci            total2 = total_successors[i]
3247db96d56Sopenharmony_ci            if total1 == 0 and total2 == 0:
3257db96d56Sopenharmony_ci                continue
3267db96d56Sopenharmony_ci            pred_rows = succ_rows = ()
3277db96d56Sopenharmony_ci            if total1:
3287db96d56Sopenharmony_ci                pred_rows = [(opname[pred], count, f"{count/total1:.1%}")
3297db96d56Sopenharmony_ci                             for (pred, count) in predecessors[i].most_common(3)]
3307db96d56Sopenharmony_ci            if total2:
3317db96d56Sopenharmony_ci                succ_rows = [(opname[succ], count, f"{count/total2:.1%}")
3327db96d56Sopenharmony_ci                             for (succ, count) in successors[i].most_common(3)]
3337db96d56Sopenharmony_ci            with Section(name, 3, f"Successors and predecessors for {name}"):
3347db96d56Sopenharmony_ci                emit_table(("Predecessors", "Count:", "Percentage:"),
3357db96d56Sopenharmony_ci                    pred_rows
3367db96d56Sopenharmony_ci                )
3377db96d56Sopenharmony_ci                emit_table(("Successors", "Count:", "Percentage:"),
3387db96d56Sopenharmony_ci                    succ_rows
3397db96d56Sopenharmony_ci                )
3407db96d56Sopenharmony_ci
3417db96d56Sopenharmony_cidef main():
3427db96d56Sopenharmony_ci    stats = gather_stats()
3437db96d56Sopenharmony_ci    opcode_stats = extract_opcode_stats(stats)
3447db96d56Sopenharmony_ci    total = get_total(opcode_stats)
3457db96d56Sopenharmony_ci    emit_execution_counts(opcode_stats, total)
3467db96d56Sopenharmony_ci    emit_pair_counts(opcode_stats, total)
3477db96d56Sopenharmony_ci    emit_specialization_stats(opcode_stats)
3487db96d56Sopenharmony_ci    emit_specialization_overview(opcode_stats, total)
3497db96d56Sopenharmony_ci    emit_call_stats(stats)
3507db96d56Sopenharmony_ci    emit_object_stats(stats)
3517db96d56Sopenharmony_ci    print("---")
3527db96d56Sopenharmony_ci    print("Stats gathered on:", date.today())
3537db96d56Sopenharmony_ci
3547db96d56Sopenharmony_ciif __name__ == "__main__":
3557db96d56Sopenharmony_ci    main()
356