Skip to content

Commit 11714a6

Browse files
committed
[WIP] Clean up input format versions
The code representing reports (and related entities like machines, runs, etc) was greatly complicated by the presence of multiple input format versions. In particular, the "v2" input format was introduced but several parts of the codebase (e.g. `lnt importreport`) were never switched to it and were still producing reports in the "old" format. This patch simplifies the code by un-versionning the Python objects that represent Report-related entities and moving tests and other tools to produce the latest input format. However, old input formats are still supported in the sense that `lnt import` and the various importation methods still support old JSON formats, which are upgraded to the latest format before actually being submitted. TODO: - Fix tests that use the old input format (e.g. checked-in JSON files) - Look through anything that is still producing the old format (hint, search for elements of the old format like old classes e.g. TestSamples) - Fix remaining mentions of report_version - Look for other mentions of the report version format - Look through https://reviews.llvm.org/D65751 and https://reviews.llvm.org/D34584 - Add tests for the JSON upgrade from V1 to V2 instead of having all kinds of tests for each Python class - The new input format probably needs to make llvm_project_revision optional. It just doesn't make sense in most cases.
1 parent b8ab486 commit 11714a6

4 files changed

Lines changed: 291 additions & 753 deletions

File tree

lnt/lnttool/import_report.py

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,24 +28,29 @@ def action_importreport(input, output, suite, order, machine):
2828
import lnt.testing
2929
import os
3030

31-
machine_info = {}
32-
run_info = {'tag': suite}
33-
run_info['run_order'] = order
34-
machine = lnt.testing.Machine(machine,
35-
machine_info)
31+
machine = lnt.testing.Machine(machine)
32+
3633
ctime = os.path.getctime(input.name)
3734
mtime = os.path.getmtime(input.name)
35+
run = lnt.testing.Run(start_time=ctime, end_time=mtime,
36+
info={'llvm_project_revision': order})
3837

39-
run = lnt.testing.Run(ctime, mtime, run_info)
40-
report = lnt.testing.Report(machine=machine, run=run, tests=[])
41-
38+
tests = {} # name => lnt.testing.Test
4239
for line in input.readlines():
4340
key, val = line.split()
44-
metric = key.split(".")[1]
41+
(testname, metric) = key.split(".")
4542
metric_type = float if metric not in ("hash", "profile") else str
46-
test = lnt.testing.TestSamples(suite + "." + key, [val],
47-
conv_f=metric_type)
4843

49-
report.tests.extend([test])
44+
if testname not in tests:
45+
tests[testname] = lnt.testing.Test(testname, [])
46+
test = tests[testname]
47+
48+
samples = next((s for s in test.samples if s.metric == metric), None)
49+
if samples is None:
50+
test.samples.append(lnt.testing.MetricSamples(metric, []))
51+
samples = test.samples[-1]
52+
53+
samples.add_samples([val], conv_f=metric_type)
5054

55+
report = lnt.testing.Report(machine=machine, run=run, tests=list(tests.values()))
5156
output.write(report.render())

lnt/testing/__init__.py

Lines changed: 44 additions & 154 deletions
Original file line numberDiff line numberDiff line change
@@ -32,66 +32,41 @@ class Report:
3232
In the LNT test model, every test run should define exactly one
3333
machine and run, and any number of test samples.
3434
"""
35-
def __init__(self, machine, run, tests, report_version=1):
36-
"""Construct a LNT report file format in the given format version."""
35+
def __init__(self, machine, run, tests):
36+
"""Construct a LNT report file."""
3737
self.machine = machine
3838
self.run = run
3939
self.tests = list(tests)
40-
self.report_version = report_version
41-
self.check()
40+
self._check()
4241

43-
def check(self):
42+
def _check(self):
4443
"""Check that object members are adequate to generate an LNT
45-
json report file of the version specified at construction when
46-
rendering that instance.
44+
json report file.
4745
"""
48-
# Check requested report version is supported by this library
49-
assert self.report_version <= 2, "Only v2 or older LNT report format supported."
50-
5146
assert isinstance(self.machine, Machine), "Unexpected type for machine."
52-
assert (
53-
self.machine.report_version == self.report_version
54-
), "Mismatch between machine and report version."
55-
5647
assert isinstance(self.run, Run), "Unexpected type for run."
57-
assert (
58-
self.run.report_version == self.report_version
59-
), "Mismatch between run and report version."
60-
6148
for t in self.tests:
62-
if self.report_version == 2:
63-
assert isinstance(t, Test), "Unexpected type for test"
64-
assert (
65-
t.report_version == self.report_version
66-
), "Mismatch between test and report version."
67-
else:
68-
assert isinstance(t, TestSamples), "Unexpected type for test samples."
49+
assert isinstance(t, Test), "Unexpected type for test"
6950

7051
def update_report(self, new_tests_samples, end_time=None):
7152
"""Add extra samples to this report, and update the end time of
7253
the run.
7354
"""
74-
self.check()
55+
self._check()
7556
self.tests.extend(new_tests_samples)
7657
self.run.update_endtime(end_time)
77-
self.check()
58+
self._check()
7859

7960
def render(self, indent=4):
80-
"""Return a LNT json report file format of the version specified
81-
at construction as a string, where each object is indented by
82-
indent spaces compared to its parent.
61+
"""Return a LNT json report file as a string, where each object is
62+
indented by spaces compared to its parent.
8363
"""
84-
if self.report_version == 2:
85-
return json.dumps({'format_version': str(self.report_version),
86-
'machine': self.machine.render(),
87-
'run': self.run.render(),
88-
'tests': [t.render() for t in self.tests]},
89-
sort_keys=True, indent=indent)
90-
else:
91-
return json.dumps({'Machine': self.machine.render(),
92-
'Run': self.run.render(),
93-
'Tests': [t.render() for t in self.tests]},
94-
sort_keys=True, indent=indent)
64+
return json.dumps({
65+
'format_version': '2',
66+
'machine': self.machine.render(),
67+
'run': self.run.render(),
68+
'tests': [t.render() for t in self.tests]},
69+
sort_keys=True, indent=indent)
9570

9671

9772
class Machine:
@@ -104,44 +79,25 @@ class Machine:
10479
Machines entries in the database are uniqued by their name and the
10580
entire contents of the info dictionary.
10681
"""
107-
def __init__(self, name, info={}, report_version=1):
82+
def __init__(self, name, info={}):
10883
self.name = str(name)
10984
self.info = dict((str(key), str(value))
11085
for key, value in info.items())
111-
self.report_version = report_version
112-
self.check()
113-
114-
def check(self):
115-
"""Check object members are adequate to generate an LNT json
116-
report file of the version specified at construction when
117-
rendering that instance.
118-
"""
119-
# Check requested version is supported by this library
120-
assert (
121-
self.report_version <= 2
122-
), "Only v2 or older supported for LNT report format Machine objects."
12386

12487
def render(self):
12588
"""Return info from this instance in a dictionary that respects
126-
the LNT report format in the version specified at construction
127-
when printed as json.
89+
the LNT JSON report format.
12890
"""
129-
if self.report_version == 2:
130-
d = dict(self.info)
131-
d['Name'] = self.name
132-
return d
133-
else:
134-
return {'Name': self.name,
135-
'Info': self.info}
91+
d = dict(self.info)
92+
d['name'] = self.name
93+
return d
13694

13795

13896
class Run:
13997
"""Information on the particular test run.
14098
14199
At least one parameter must be supplied and is used as ordering
142-
among several runs. When generating a report in format 1 or earlier,
143-
both start_time and end_time are used for that effect and the
144-
current date is used if their value is None.
100+
among several runs.
145101
146102
As with Machine, the info dictionary can be used to describe
147103
additional information on the run. This dictionary should be used to
@@ -151,12 +107,7 @@ class Run:
151107
which could be useful in analysis, for example the current machine
152108
load.
153109
"""
154-
def __init__(self, start_time=None, end_time=None, info={}, report_version=1):
155-
if report_version <= 1:
156-
if start_time is None:
157-
start_time = datetime.datetime.utcnow()
158-
if end_time is None:
159-
end_time = datetime.datetime.utcnow()
110+
def __init__(self, start_time=None, end_time=None, info={}):
160111
self.start_time = normalize_time(start_time) if start_time is not None else None
161112
self.end_time = normalize_time(end_time) if end_time is not None else None
162113
self.info = dict()
@@ -165,68 +116,32 @@ def __init__(self, start_time=None, end_time=None, info={}, report_version=1):
165116
key = str(key)
166117
value = str(value)
167118
self.info[key] = value
168-
self.report_version = report_version
169-
if self.report_version <= 1:
170-
if 'tag' not in self.info:
171-
raise ValueError("Missing 'tag' entry in 'info' dictionary")
172-
if 'run_order' not in self.info:
173-
raise ValueError("Missing 'run_order' entry in 'info' dictionary")
174-
else:
175-
if 'llvm_project_revision' not in self.info:
176-
raise ValueError("Missing 'llvm_project_revision' entry in 'info' dictionary")
177-
if '__report_version__' in info:
178-
raise ValueError("'__report_version__' key is reserved")
179-
if report_version == 1:
180-
self.info['__report_version__'] = '1'
181-
self.check()
182119

183-
def check(self):
184-
"""Check object members are adequate to generate an LNT json
185-
report file of the version specified at construction when
186-
rendering that instance.
187-
"""
188-
# Check requested version is supported by this library
189-
assert (
190-
self.report_version <= 2
191-
), "Only v2 or older supported for LNT report format Run objects."
192-
if self.start_time is None and self.end_time is None and not bool(self.info):
193-
raise ValueError("No data defined in this Run")
120+
if 'llvm_project_revision' not in self.info:
121+
raise ValueError("Missing 'llvm_project_revision' entry in 'info' dictionary")
194122

195123
def update_endtime(self, end_time=None):
196124
"""Update the end time of this run."""
197-
if self.report_version <= 1 and end_time is None:
198-
end_time = datetime.datetime.utcnow()
199125
self.end_time = normalize_time(end_time) if end_time else None
200-
self.check()
201126

202127
def render(self):
203128
"""Return info from this instance in a dictionary that respects
204-
the LNT report format in the version specified at construction
205-
when printed as json.
129+
the LNT JSON report format.
206130
"""
207-
if self.report_version == 2:
208-
d = dict(self.info)
209-
if self.start_time is not None:
210-
d['start_time'] = self.start_time
211-
if self.end_time is not None:
212-
d['end_time'] = self.end_time
213-
return d
214-
else:
215-
info = dict(self.info)
216-
if self.report_version == 1:
217-
info['__report_version__'] = '1'
218-
return {'Start Time': self.start_time,
219-
'End Time': self.end_time,
220-
'Info': info}
131+
d = dict(self.info)
132+
if self.start_time is not None:
133+
d['start_time'] = self.start_time
134+
if self.end_time is not None:
135+
d['end_time'] = self.end_time
136+
return d
221137

222138

223139
class Test:
224140
"""Information on a particular test in the run and its associated
225141
samples.
226142
227143
The server automatically creates test database objects whenever a
228-
new test name is seen. Test should be used to generate report in
229-
version 2 or later of LNT JSON report file format.
144+
new test name is seen.
230145
231146
Test names are intended to be a persistent, recognizable identifier
232147
for what is being executed. Currently, most formats use some form of
@@ -242,10 +157,10 @@ class Test:
242157
for example, the compile flags the test was built with, or the
243158
runtime parameters that were used. As a general rule, if two test
244159
samples are meaningfully and directly comparable, then they should
245-
have the same test name but different info paramaters.
160+
have the same test name but different info parameters.
246161
"""
247162

248-
def __init__(self, name, samples, info={}, report_version=2):
163+
def __init__(self, name, samples, info={}):
249164
self.name = name
250165
self.samples = samples
251166
self.info = dict()
@@ -254,33 +169,22 @@ def __init__(self, name, samples, info={}, report_version=2):
254169
key = str(key)
255170
value = str(value)
256171
self.info[key] = value
257-
self.report_version = report_version
258172
self.check()
259173

260174
def check(self):
261175
"""Check object members are adequate to generate an LNT json
262-
report file of the version specified at construction when
263-
rendering that instance.
176+
report file.
264177
"""
265-
# Check requested version is supported by this library and is
266-
# valid for this object.
267-
assert (
268-
self.report_version == 2
269-
), "Only v2 supported for LNT report format Test objects."
270178
for s in self.samples:
271179
assert isinstance(s, MetricSamples), "Unexpected type for metric sample."
272-
assert (
273-
s.report_version == self.report_version
274-
), "Mismatch between test and metric samples."
275180

276181
def render(self):
277182
"""Return info from this instance in a dictionary that respects
278-
the LNT report format in the version specified at construction
279-
when printed as json.
183+
the LNT JSON report format.
280184
"""
281185
d = dict(self.info)
282186
d.update([s.render().popitem() for s in self.samples])
283-
d['Name'] = self.name
187+
d['name'] = self.name
284188
return d
285189

286190

@@ -309,7 +213,7 @@ class TestSamples:
309213
for example, the compile flags the test was built with, or the
310214
runtime parameters that were used. As a general rule, if two test
311215
samples are meaningfully and directly comparable, then they should
312-
have the same test name but different info paramaters.
216+
have the same test name but different info parameters.
313217
314218
The report may include an arbitrary number of samples for each test
315219
for situations where the same test is run multiple times to gather
@@ -347,27 +251,11 @@ class MetricSamples:
347251
An arbitrary number of samples for a given metric is allowed for
348252
situations where the same metric is obtained several time for a
349253
given test to gather statistical data.
350-
351-
MetricSamples should be used to generate report in version 2 or
352-
later of LNT JSON report file format.
353254
"""
354255

355-
def __init__(self, metric, data, conv_f=float, report_version=2):
256+
def __init__(self, metric, data, conv_f=float):
356257
self.metric = str(metric)
357258
self.data = list(map(conv_f, data))
358-
self.report_version = report_version
359-
self.check()
360-
361-
def check(self):
362-
"""Check object members are adequate to generate an LNT json
363-
report file of the version specified at construction when
364-
rendering that instance.
365-
"""
366-
# Check requested version is supported by this library and is
367-
# valid for this object.
368-
assert (
369-
self.report_version == 2
370-
), "Only v2 supported for LNT report format MetricSamples objects."
371259

372260
def add_samples(self, new_samples, conv_f=float):
373261
"""Add samples for this metric, converted to float by calling
@@ -389,8 +277,10 @@ def render(self):
389277
# We record information on the report "version" to allow the server to support
390278
# some level of auto-upgrading data from submissions of older reports.
391279
#
392-
# We recorder the report version as a reserved key in the run information
393-
# (primarily so that it can be accessed post-import on the server).
280+
# We record the report version as a reserved key in the run information. When
281+
# importing data, we detect the version of the report using the version number
282+
# and we normalize it to the latest format so that the rest of the code only
283+
# has to deal with the latest version at all times.
394284
#
395285
# Version 0 -- : initial (and unversioned).
396286
#

0 commit comments

Comments
 (0)