xref: /aosp_15_r20/external/pytorch/scripts/release_notes/common.py (revision da0073e96a02ea20f0ac840b70461e3646d07c45)
1*da0073e9SAndroid Build Coastguard Workerimport json
2*da0073e9SAndroid Build Coastguard Workerimport locale
3*da0073e9SAndroid Build Coastguard Workerimport os
4*da0073e9SAndroid Build Coastguard Workerimport re
5*da0073e9SAndroid Build Coastguard Workerimport subprocess
6*da0073e9SAndroid Build Coastguard Workerfrom collections import namedtuple
7*da0073e9SAndroid Build Coastguard Workerfrom dataclasses import dataclass
8*da0073e9SAndroid Build Coastguard Workerfrom pathlib import Path
9*da0073e9SAndroid Build Coastguard Worker
10*da0073e9SAndroid Build Coastguard Workerimport requests
11*da0073e9SAndroid Build Coastguard Worker
12*da0073e9SAndroid Build Coastguard Worker
13*da0073e9SAndroid Build Coastguard Worker@dataclass
14*da0073e9SAndroid Build Coastguard Workerclass CategoryGroup:
15*da0073e9SAndroid Build Coastguard Worker    name: str
16*da0073e9SAndroid Build Coastguard Worker    categories: list
17*da0073e9SAndroid Build Coastguard Worker
18*da0073e9SAndroid Build Coastguard Worker
19*da0073e9SAndroid Build Coastguard Workerfrontend_categories = [
20*da0073e9SAndroid Build Coastguard Worker    "meta",
21*da0073e9SAndroid Build Coastguard Worker    "nn",
22*da0073e9SAndroid Build Coastguard Worker    "linalg",
23*da0073e9SAndroid Build Coastguard Worker    "cpp",
24*da0073e9SAndroid Build Coastguard Worker    "python",
25*da0073e9SAndroid Build Coastguard Worker    "complex",
26*da0073e9SAndroid Build Coastguard Worker    "vmap",
27*da0073e9SAndroid Build Coastguard Worker    "autograd",
28*da0073e9SAndroid Build Coastguard Worker    "build",
29*da0073e9SAndroid Build Coastguard Worker    "memory_format",
30*da0073e9SAndroid Build Coastguard Worker    "foreach",
31*da0073e9SAndroid Build Coastguard Worker    "dataloader",
32*da0073e9SAndroid Build Coastguard Worker    "sparse",
33*da0073e9SAndroid Build Coastguard Worker    "nested tensor",
34*da0073e9SAndroid Build Coastguard Worker    "optimizer",
35*da0073e9SAndroid Build Coastguard Worker]
36*da0073e9SAndroid Build Coastguard Worker
37*da0073e9SAndroid Build Coastguard Workerpytorch_2_categories = [
38*da0073e9SAndroid Build Coastguard Worker    "dynamo",
39*da0073e9SAndroid Build Coastguard Worker    "inductor",
40*da0073e9SAndroid Build Coastguard Worker]
41*da0073e9SAndroid Build Coastguard Worker
42*da0073e9SAndroid Build Coastguard Worker# These will all get mapped to quantization
43*da0073e9SAndroid Build Coastguard Workerquantization = CategoryGroup(
44*da0073e9SAndroid Build Coastguard Worker    name="quantization",
45*da0073e9SAndroid Build Coastguard Worker    categories=[
46*da0073e9SAndroid Build Coastguard Worker        "quantization",
47*da0073e9SAndroid Build Coastguard Worker        "AO frontend",
48*da0073e9SAndroid Build Coastguard Worker        "AO Pruning",
49*da0073e9SAndroid Build Coastguard Worker    ],
50*da0073e9SAndroid Build Coastguard Worker)
51*da0073e9SAndroid Build Coastguard Worker
52*da0073e9SAndroid Build Coastguard Worker# Distributed has a number of release note labels we want to map to one
53*da0073e9SAndroid Build Coastguard Workerdistributed = CategoryGroup(
54*da0073e9SAndroid Build Coastguard Worker    name="distributed",
55*da0073e9SAndroid Build Coastguard Worker    categories=[
56*da0073e9SAndroid Build Coastguard Worker        "distributed",
57*da0073e9SAndroid Build Coastguard Worker        "distributed (c10d)",
58*da0073e9SAndroid Build Coastguard Worker        "distributed (composable)",
59*da0073e9SAndroid Build Coastguard Worker        "distributed (ddp)",
60*da0073e9SAndroid Build Coastguard Worker        "distributed (fsdp)",
61*da0073e9SAndroid Build Coastguard Worker        "distributed (rpc)",
62*da0073e9SAndroid Build Coastguard Worker        "distributed (sharded)",
63*da0073e9SAndroid Build Coastguard Worker    ],
64*da0073e9SAndroid Build Coastguard Worker)
65*da0073e9SAndroid Build Coastguard Worker
66*da0073e9SAndroid Build Coastguard Workercategories = (
67*da0073e9SAndroid Build Coastguard Worker    [
68*da0073e9SAndroid Build Coastguard Worker        "Uncategorized",
69*da0073e9SAndroid Build Coastguard Worker        "lazy",
70*da0073e9SAndroid Build Coastguard Worker        "hub",
71*da0073e9SAndroid Build Coastguard Worker        "mobile",
72*da0073e9SAndroid Build Coastguard Worker        "jit",
73*da0073e9SAndroid Build Coastguard Worker        "visualization",
74*da0073e9SAndroid Build Coastguard Worker        "onnx",
75*da0073e9SAndroid Build Coastguard Worker        "caffe2",
76*da0073e9SAndroid Build Coastguard Worker        "amd",
77*da0073e9SAndroid Build Coastguard Worker        "rocm",
78*da0073e9SAndroid Build Coastguard Worker        "cuda",
79*da0073e9SAndroid Build Coastguard Worker        "cpu",
80*da0073e9SAndroid Build Coastguard Worker        "cudnn",
81*da0073e9SAndroid Build Coastguard Worker        "xla",
82*da0073e9SAndroid Build Coastguard Worker        "benchmark",
83*da0073e9SAndroid Build Coastguard Worker        "profiler",
84*da0073e9SAndroid Build Coastguard Worker        "performance_as_product",
85*da0073e9SAndroid Build Coastguard Worker        "package",
86*da0073e9SAndroid Build Coastguard Worker        "dispatcher",
87*da0073e9SAndroid Build Coastguard Worker        "releng",
88*da0073e9SAndroid Build Coastguard Worker        "fx",
89*da0073e9SAndroid Build Coastguard Worker        "code_coverage",
90*da0073e9SAndroid Build Coastguard Worker        "vulkan",
91*da0073e9SAndroid Build Coastguard Worker        "skip",
92*da0073e9SAndroid Build Coastguard Worker        "composability",
93*da0073e9SAndroid Build Coastguard Worker        # 2.0 release
94*da0073e9SAndroid Build Coastguard Worker        "mps",
95*da0073e9SAndroid Build Coastguard Worker        "intel",
96*da0073e9SAndroid Build Coastguard Worker        "functorch",
97*da0073e9SAndroid Build Coastguard Worker        "gnn",
98*da0073e9SAndroid Build Coastguard Worker        "distributions",
99*da0073e9SAndroid Build Coastguard Worker        "serialization",
100*da0073e9SAndroid Build Coastguard Worker    ]
101*da0073e9SAndroid Build Coastguard Worker    + [f"{category}_frontend" for category in frontend_categories]
102*da0073e9SAndroid Build Coastguard Worker    + pytorch_2_categories
103*da0073e9SAndroid Build Coastguard Worker    + [quantization.name]
104*da0073e9SAndroid Build Coastguard Worker    + [distributed.name]
105*da0073e9SAndroid Build Coastguard Worker)
106*da0073e9SAndroid Build Coastguard Worker
107*da0073e9SAndroid Build Coastguard Worker
108*da0073e9SAndroid Build Coastguard Workertopics = [
109*da0073e9SAndroid Build Coastguard Worker    "bc breaking",
110*da0073e9SAndroid Build Coastguard Worker    "deprecation",
111*da0073e9SAndroid Build Coastguard Worker    "new features",
112*da0073e9SAndroid Build Coastguard Worker    "improvements",
113*da0073e9SAndroid Build Coastguard Worker    "bug fixes",
114*da0073e9SAndroid Build Coastguard Worker    "performance",
115*da0073e9SAndroid Build Coastguard Worker    "docs",
116*da0073e9SAndroid Build Coastguard Worker    "devs",
117*da0073e9SAndroid Build Coastguard Worker    "Untopiced",
118*da0073e9SAndroid Build Coastguard Worker    "not user facing",
119*da0073e9SAndroid Build Coastguard Worker    "security",
120*da0073e9SAndroid Build Coastguard Worker]
121*da0073e9SAndroid Build Coastguard Worker
122*da0073e9SAndroid Build Coastguard Worker
123*da0073e9SAndroid Build Coastguard WorkerFeatures = namedtuple(
124*da0073e9SAndroid Build Coastguard Worker    "Features",
125*da0073e9SAndroid Build Coastguard Worker    ["title", "body", "pr_number", "files_changed", "labels", "author", "accepters"],
126*da0073e9SAndroid Build Coastguard Worker)
127*da0073e9SAndroid Build Coastguard Worker
128*da0073e9SAndroid Build Coastguard Worker
129*da0073e9SAndroid Build Coastguard Workerdef dict_to_features(dct):
130*da0073e9SAndroid Build Coastguard Worker    return Features(
131*da0073e9SAndroid Build Coastguard Worker        title=dct["title"],
132*da0073e9SAndroid Build Coastguard Worker        body=dct["body"],
133*da0073e9SAndroid Build Coastguard Worker        pr_number=dct["pr_number"],
134*da0073e9SAndroid Build Coastguard Worker        files_changed=dct["files_changed"],
135*da0073e9SAndroid Build Coastguard Worker        labels=dct["labels"],
136*da0073e9SAndroid Build Coastguard Worker        author=dct["author"],
137*da0073e9SAndroid Build Coastguard Worker        accepters=tuple(dct["accepters"]),
138*da0073e9SAndroid Build Coastguard Worker    )
139*da0073e9SAndroid Build Coastguard Worker
140*da0073e9SAndroid Build Coastguard Worker
141*da0073e9SAndroid Build Coastguard Workerdef features_to_dict(features):
142*da0073e9SAndroid Build Coastguard Worker    return dict(features._asdict())
143*da0073e9SAndroid Build Coastguard Worker
144*da0073e9SAndroid Build Coastguard Worker
145*da0073e9SAndroid Build Coastguard Workerdef run(command):
146*da0073e9SAndroid Build Coastguard Worker    """Returns (return-code, stdout, stderr)"""
147*da0073e9SAndroid Build Coastguard Worker    p = subprocess.Popen(
148*da0073e9SAndroid Build Coastguard Worker        command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True
149*da0073e9SAndroid Build Coastguard Worker    )
150*da0073e9SAndroid Build Coastguard Worker    output, err = p.communicate()
151*da0073e9SAndroid Build Coastguard Worker    rc = p.returncode
152*da0073e9SAndroid Build Coastguard Worker    enc = locale.getpreferredencoding()
153*da0073e9SAndroid Build Coastguard Worker    output = output.decode(enc)
154*da0073e9SAndroid Build Coastguard Worker    err = err.decode(enc)
155*da0073e9SAndroid Build Coastguard Worker    return rc, output.strip(), err.strip()
156*da0073e9SAndroid Build Coastguard Worker
157*da0073e9SAndroid Build Coastguard Worker
158*da0073e9SAndroid Build Coastguard Workerdef commit_body(commit_hash):
159*da0073e9SAndroid Build Coastguard Worker    cmd = f"git log -n 1 --pretty=format:%b {commit_hash}"
160*da0073e9SAndroid Build Coastguard Worker    ret, out, err = run(cmd)
161*da0073e9SAndroid Build Coastguard Worker    return out if ret == 0 else None
162*da0073e9SAndroid Build Coastguard Worker
163*da0073e9SAndroid Build Coastguard Worker
164*da0073e9SAndroid Build Coastguard Workerdef commit_title(commit_hash):
165*da0073e9SAndroid Build Coastguard Worker    cmd = f"git log -n 1 --pretty=format:%s {commit_hash}"
166*da0073e9SAndroid Build Coastguard Worker    ret, out, err = run(cmd)
167*da0073e9SAndroid Build Coastguard Worker    return out if ret == 0 else None
168*da0073e9SAndroid Build Coastguard Worker
169*da0073e9SAndroid Build Coastguard Worker
170*da0073e9SAndroid Build Coastguard Workerdef commit_files_changed(commit_hash):
171*da0073e9SAndroid Build Coastguard Worker    cmd = f"git diff-tree --no-commit-id --name-only -r {commit_hash}"
172*da0073e9SAndroid Build Coastguard Worker    ret, out, err = run(cmd)
173*da0073e9SAndroid Build Coastguard Worker    return out.split("\n") if ret == 0 else None
174*da0073e9SAndroid Build Coastguard Worker
175*da0073e9SAndroid Build Coastguard Worker
176*da0073e9SAndroid Build Coastguard Workerdef parse_pr_number(body, commit_hash, title):
177*da0073e9SAndroid Build Coastguard Worker    regex = r"Pull Request resolved: https://github.com/pytorch/pytorch/pull/([0-9]+)"
178*da0073e9SAndroid Build Coastguard Worker    matches = re.findall(regex, body)
179*da0073e9SAndroid Build Coastguard Worker    if len(matches) == 0:
180*da0073e9SAndroid Build Coastguard Worker        if "revert" not in title.lower() and "updating submodules" not in title.lower():
181*da0073e9SAndroid Build Coastguard Worker            print(f"[{commit_hash}: {title}] Could not parse PR number, ignoring PR")
182*da0073e9SAndroid Build Coastguard Worker        return None
183*da0073e9SAndroid Build Coastguard Worker    if len(matches) > 1:
184*da0073e9SAndroid Build Coastguard Worker        print(f"[{commit_hash}: {title}] Got two PR numbers, using the first one")
185*da0073e9SAndroid Build Coastguard Worker        return matches[0]
186*da0073e9SAndroid Build Coastguard Worker    return matches[0]
187*da0073e9SAndroid Build Coastguard Worker
188*da0073e9SAndroid Build Coastguard Worker
189*da0073e9SAndroid Build Coastguard Workerdef get_ghstack_token():
190*da0073e9SAndroid Build Coastguard Worker    pattern = "github_oauth = (.*)"
191*da0073e9SAndroid Build Coastguard Worker    with open(Path("~/.ghstackrc").expanduser(), "r+") as f:
192*da0073e9SAndroid Build Coastguard Worker        config = f.read()
193*da0073e9SAndroid Build Coastguard Worker    matches = re.findall(pattern, config)
194*da0073e9SAndroid Build Coastguard Worker    if len(matches) == 0:
195*da0073e9SAndroid Build Coastguard Worker        raise RuntimeError("Can't find a github oauth token")
196*da0073e9SAndroid Build Coastguard Worker    return matches[0]
197*da0073e9SAndroid Build Coastguard Worker
198*da0073e9SAndroid Build Coastguard Worker
199*da0073e9SAndroid Build Coastguard Workerdef get_token():
200*da0073e9SAndroid Build Coastguard Worker    env_token = os.environ.get("GITHUB_TOKEN")
201*da0073e9SAndroid Build Coastguard Worker    if env_token is not None:
202*da0073e9SAndroid Build Coastguard Worker        print("using GITHUB_TOKEN from environment variable")
203*da0073e9SAndroid Build Coastguard Worker        return env_token
204*da0073e9SAndroid Build Coastguard Worker    else:
205*da0073e9SAndroid Build Coastguard Worker        return get_ghstack_token()
206*da0073e9SAndroid Build Coastguard Worker
207*da0073e9SAndroid Build Coastguard Worker
208*da0073e9SAndroid Build Coastguard Workertoken = get_token()
209*da0073e9SAndroid Build Coastguard Worker
210*da0073e9SAndroid Build Coastguard Workerheaders = {"Authorization": f"token {token}"}
211*da0073e9SAndroid Build Coastguard Worker
212*da0073e9SAndroid Build Coastguard Worker
213*da0073e9SAndroid Build Coastguard Workerdef run_query(query):
214*da0073e9SAndroid Build Coastguard Worker    request = requests.post(
215*da0073e9SAndroid Build Coastguard Worker        "https://api.github.com/graphql", json={"query": query}, headers=headers
216*da0073e9SAndroid Build Coastguard Worker    )
217*da0073e9SAndroid Build Coastguard Worker    if request.status_code == 200:
218*da0073e9SAndroid Build Coastguard Worker        return request.json()
219*da0073e9SAndroid Build Coastguard Worker    else:
220*da0073e9SAndroid Build Coastguard Worker        raise Exception(  # noqa: TRY002
221*da0073e9SAndroid Build Coastguard Worker            f"Query failed to run by returning code of {request.status_code}. {request.json()}"
222*da0073e9SAndroid Build Coastguard Worker        )
223*da0073e9SAndroid Build Coastguard Worker
224*da0073e9SAndroid Build Coastguard Worker
225*da0073e9SAndroid Build Coastguard Worker_ERRORS = []
226*da0073e9SAndroid Build Coastguard Worker_MAX_ERROR_LEN = 20
227*da0073e9SAndroid Build Coastguard Worker
228*da0073e9SAndroid Build Coastguard Worker
229*da0073e9SAndroid Build Coastguard Workerdef github_data(pr_number):
230*da0073e9SAndroid Build Coastguard Worker    query = (
231*da0073e9SAndroid Build Coastguard Worker        """
232*da0073e9SAndroid Build Coastguard Worker    {
233*da0073e9SAndroid Build Coastguard Worker      repository(owner: "pytorch", name: "pytorch") {
234*da0073e9SAndroid Build Coastguard Worker        pullRequest(number: %s ) {
235*da0073e9SAndroid Build Coastguard Worker          author {
236*da0073e9SAndroid Build Coastguard Worker            login
237*da0073e9SAndroid Build Coastguard Worker          }
238*da0073e9SAndroid Build Coastguard Worker          reviews(last: 5, states: APPROVED) {
239*da0073e9SAndroid Build Coastguard Worker            nodes {
240*da0073e9SAndroid Build Coastguard Worker              author {
241*da0073e9SAndroid Build Coastguard Worker                login
242*da0073e9SAndroid Build Coastguard Worker              }
243*da0073e9SAndroid Build Coastguard Worker            }
244*da0073e9SAndroid Build Coastguard Worker          }
245*da0073e9SAndroid Build Coastguard Worker          labels(first: 10) {
246*da0073e9SAndroid Build Coastguard Worker            edges {
247*da0073e9SAndroid Build Coastguard Worker              node {
248*da0073e9SAndroid Build Coastguard Worker                name
249*da0073e9SAndroid Build Coastguard Worker              }
250*da0073e9SAndroid Build Coastguard Worker            }
251*da0073e9SAndroid Build Coastguard Worker          }
252*da0073e9SAndroid Build Coastguard Worker        }
253*da0073e9SAndroid Build Coastguard Worker      }
254*da0073e9SAndroid Build Coastguard Worker    }
255*da0073e9SAndroid Build Coastguard Worker    """  # noqa: UP031
256*da0073e9SAndroid Build Coastguard Worker        % pr_number
257*da0073e9SAndroid Build Coastguard Worker    )
258*da0073e9SAndroid Build Coastguard Worker    query = run_query(query)
259*da0073e9SAndroid Build Coastguard Worker    if query.get("errors"):
260*da0073e9SAndroid Build Coastguard Worker        global _ERRORS
261*da0073e9SAndroid Build Coastguard Worker        _ERRORS.append(query.get("errors"))
262*da0073e9SAndroid Build Coastguard Worker        if len(_ERRORS) < _MAX_ERROR_LEN:
263*da0073e9SAndroid Build Coastguard Worker            return [], "None", ()
264*da0073e9SAndroid Build Coastguard Worker        else:
265*da0073e9SAndroid Build Coastguard Worker            raise Exception(  # noqa: TRY002
266*da0073e9SAndroid Build Coastguard Worker                f"Got {_MAX_ERROR_LEN} errors: {_ERRORS}, please check if"
267*da0073e9SAndroid Build Coastguard Worker                " there is something wrong"
268*da0073e9SAndroid Build Coastguard Worker            )
269*da0073e9SAndroid Build Coastguard Worker    edges = query["data"]["repository"]["pullRequest"]["labels"]["edges"]
270*da0073e9SAndroid Build Coastguard Worker    labels = [edge["node"]["name"] for edge in edges]
271*da0073e9SAndroid Build Coastguard Worker    author = query["data"]["repository"]["pullRequest"]["author"]["login"]
272*da0073e9SAndroid Build Coastguard Worker    nodes = query["data"]["repository"]["pullRequest"]["reviews"]["nodes"]
273*da0073e9SAndroid Build Coastguard Worker
274*da0073e9SAndroid Build Coastguard Worker    # using set to dedup multiple accepts from same accepter
275*da0073e9SAndroid Build Coastguard Worker    accepters = {node["author"]["login"] for node in nodes}
276*da0073e9SAndroid Build Coastguard Worker    accepters = tuple(sorted(accepters))
277*da0073e9SAndroid Build Coastguard Worker
278*da0073e9SAndroid Build Coastguard Worker    return labels, author, accepters
279*da0073e9SAndroid Build Coastguard Worker
280*da0073e9SAndroid Build Coastguard Worker
281*da0073e9SAndroid Build Coastguard Workerdef get_features(commit_hash):
282*da0073e9SAndroid Build Coastguard Worker    title, body, files_changed = (
283*da0073e9SAndroid Build Coastguard Worker        commit_title(commit_hash),
284*da0073e9SAndroid Build Coastguard Worker        commit_body(commit_hash),
285*da0073e9SAndroid Build Coastguard Worker        commit_files_changed(commit_hash),
286*da0073e9SAndroid Build Coastguard Worker    )
287*da0073e9SAndroid Build Coastguard Worker    pr_number = parse_pr_number(body, commit_hash, title)
288*da0073e9SAndroid Build Coastguard Worker    labels = []
289*da0073e9SAndroid Build Coastguard Worker    author = ""
290*da0073e9SAndroid Build Coastguard Worker    accepters = ()
291*da0073e9SAndroid Build Coastguard Worker    if pr_number is not None:
292*da0073e9SAndroid Build Coastguard Worker        labels, author, accepters = github_data(pr_number)
293*da0073e9SAndroid Build Coastguard Worker    result = Features(title, body, pr_number, files_changed, labels, author, accepters)
294*da0073e9SAndroid Build Coastguard Worker    return result
295*da0073e9SAndroid Build Coastguard Worker
296*da0073e9SAndroid Build Coastguard Worker
297*da0073e9SAndroid Build Coastguard Worker_commit_data_cache = None
298*da0073e9SAndroid Build Coastguard Worker
299*da0073e9SAndroid Build Coastguard Worker
300*da0073e9SAndroid Build Coastguard Workerdef get_commit_data_cache(path="results/data.json"):
301*da0073e9SAndroid Build Coastguard Worker    global _commit_data_cache
302*da0073e9SAndroid Build Coastguard Worker    if _commit_data_cache is None:
303*da0073e9SAndroid Build Coastguard Worker        _commit_data_cache = _CommitDataCache(path)
304*da0073e9SAndroid Build Coastguard Worker    return _commit_data_cache
305*da0073e9SAndroid Build Coastguard Worker
306*da0073e9SAndroid Build Coastguard Worker
307*da0073e9SAndroid Build Coastguard Workerclass _CommitDataCache:
308*da0073e9SAndroid Build Coastguard Worker    def __init__(self, path):
309*da0073e9SAndroid Build Coastguard Worker        self.path = path
310*da0073e9SAndroid Build Coastguard Worker        self.data = {}
311*da0073e9SAndroid Build Coastguard Worker        if os.path.exists(path):
312*da0073e9SAndroid Build Coastguard Worker            self.data = self.read_from_disk()
313*da0073e9SAndroid Build Coastguard Worker        else:
314*da0073e9SAndroid Build Coastguard Worker            os.makedirs(Path(path).parent, exist_ok=True)
315*da0073e9SAndroid Build Coastguard Worker
316*da0073e9SAndroid Build Coastguard Worker    def get(self, commit):
317*da0073e9SAndroid Build Coastguard Worker        if commit not in self.data.keys():
318*da0073e9SAndroid Build Coastguard Worker            # Fetch and cache the data
319*da0073e9SAndroid Build Coastguard Worker            self.data[commit] = get_features(commit)
320*da0073e9SAndroid Build Coastguard Worker            self.write_to_disk()
321*da0073e9SAndroid Build Coastguard Worker        return self.data[commit]
322*da0073e9SAndroid Build Coastguard Worker
323*da0073e9SAndroid Build Coastguard Worker    def read_from_disk(self):
324*da0073e9SAndroid Build Coastguard Worker        with open(self.path) as f:
325*da0073e9SAndroid Build Coastguard Worker            data = json.load(f)
326*da0073e9SAndroid Build Coastguard Worker            data = {commit: dict_to_features(dct) for commit, dct in data.items()}
327*da0073e9SAndroid Build Coastguard Worker        return data
328*da0073e9SAndroid Build Coastguard Worker
329*da0073e9SAndroid Build Coastguard Worker    def write_to_disk(self):
330*da0073e9SAndroid Build Coastguard Worker        data = {commit: features._asdict() for commit, features in self.data.items()}
331*da0073e9SAndroid Build Coastguard Worker        with open(self.path, "w") as f:
332*da0073e9SAndroid Build Coastguard Worker            json.dump(data, f)
333