-
Notifications
You must be signed in to change notification settings - Fork 0
/
ninjavis.py
136 lines (115 loc) · 4.25 KB
/
ninjavis.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# :copyright: (c) 2019-2020 Guilhem Charles. All rights reserved.
"""Generate visualization of a ninja build from its logs."""
import argparse
import re
import sys
from os.path import getmtime
from typing import List, Optional
TIMELINE = """
<!DOCTYPE HTML>
<html>
<head>
<title>{title}</title>
<style type="text/css">
body, html {{
font-family: sans-serif;
}}
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vis/4.21.0/vis.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/vis/4.21.0/vis.min.css" rel="stylesheet" type="text/css" />
</head>
<body>
<div id="visualization"></div>
<script type="text/javascript">
// DOM element where the Timeline will be attached
var container = document.getElementById('visualization');
// Create a DataSet (allows two way data-binding)
var items = new vis.DataSet({dataset});
// Configuration for the Timeline
var options = {{}};
// Create a Timeline
var timeline = new vis.Timeline(container, items, options);
</script>
</body>
</html>
"""
def generate_build_profile(logfile: str, time_offset: int) -> List[dict]:
"""
Parse a ninja build log file and generates a profile. A profile consist of the list of item
part of the build.
:param logfile: Path to the build log file.
:param time_offset: Start time of the visualization.
:return: Profile of the build.
"""
def parse_build_entry(line: str) -> Optional[dict]:
try:
# ignore comments
if line[:1] != "#":
start_time, end_time, _, command, _ = line.split()
return {
"content": command,
"start": int(start_time) + time_offset,
"end": int(end_time) + time_offset,
}
except ValueError:
print(f"error: could not parse {line}", file=sys.stderr)
return None
profile = []
with open(logfile, "r") as build_log:
# first line might be a header specifying ninja build log version
header = build_log.readline()
log_version = re.search(r"# ninja log v(\d+)", header)
if log_version:
parsed_version = log_version.group(1)
if int(parsed_version) != 5:
raise RuntimeError(f"unsupported log file version: {parsed_version}")
else:
# header is a log entry
parsed_project = parse_build_entry(header)
if parsed_project:
profile = [parsed_project]
# handle remaining lines, filter out entries that could not be parsed
profile.extend(filter(None, (parse_build_entry(line) for line in build_log)))
return profile
def generate_timeline_from(profile: List[dict], output: str, title: str):
"""
Generate a visjs timeline from the ninja build profile.
:param profile: Ninja build information.
:param output: File to output the visualization.
:param title: Title of the visualization.
:return:
"""
try:
with open(output, "w") as visualization:
visualization.write(TIMELINE.format(title=title, dataset=profile))
except RuntimeError as exc:
print(f"error: could not generate timeline: {exc}", file=sys.stderr)
sys.exit(1)
def get_argparser() -> argparse.ArgumentParser:
"""
ninjavis arguments parser.
:return: Arguments parser.
"""
parser = argparse.ArgumentParser(
prog="ninjavis",
description="Parse ninja build log file and " "generates a timeline of the build",
)
parser.add_argument("logfile", help="Ninja build log (.ninja_log)")
parser.add_argument("output", help="Output file for the visualization")
parser.add_argument("--title", help="Visualization title", default="Ninja build")
return parser
def main():
"""
Parse the arguments and try to generate a visualization out of the provided build log.
:return:
"""
args = get_argparser().parse_args(sys.argv[1:])
try:
profile = generate_build_profile(args.logfile, int(getmtime(args.logfile)))
generate_timeline_from(profile, args.output, args.title)
except (RuntimeError, FileNotFoundError) as err:
print(err, file=sys.stderr)
sys.exit(1)
sys.exit(0)