Skip to content

Commit a9ff1b3

Browse files
committed
A script to summarize gas differences from isoltest for PRs.
1 parent 7d8a4e6 commit a9ff1b3

File tree

1 file changed

+139
-0
lines changed

1 file changed

+139
-0
lines changed

scripts/gas_diff_stats.py

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
"""A script to collect gas statistics and print it.
2+
3+
Useful to summarize gas differences to semantic tests for a PR / branch.
4+
5+
Dependencies: Parsec (https://pypi.org/project/parsec/) and Tabulate
6+
(https://pypi.org/project/tabulate/)
7+
8+
pip install parsec tabulate
9+
10+
Run from root project dir.
11+
12+
python3 scripts/gas_diff_stats.py
13+
14+
Note that the changes to semantic tests have to be committed.
15+
16+
Assumes that there is a remote named ``origin`` pointing to the Solidity github
17+
repository. The changes are compared against ``origin/develop``.
18+
19+
"""
20+
import subprocess
21+
from pathlib import Path
22+
from enum import Enum
23+
from parsec import *
24+
from tabulate import tabulate
25+
26+
class Kind(Enum):
27+
IrOptimized = 1
28+
Legacy = 2
29+
LegacyOptimized = 3
30+
31+
class Diff(Enum):
32+
Minus = 1
33+
Plus = 2
34+
35+
minus = string("-").result(Diff.Minus)
36+
plus = string("+").result(Diff.Plus)
37+
38+
space = string(" ")
39+
comment = string("//")
40+
colon = string(":")
41+
42+
gas_ir_optimized = string("gas irOptimized").result(Kind.IrOptimized)
43+
gas_legacy_optimized = string("gas legacyOptimized").result(Kind.LegacyOptimized)
44+
gas_legacy = string("gas legacy").result(Kind.Legacy)
45+
46+
def number() -> int:
47+
"""Parse number."""
48+
return regex(r"([0-9]*)").parsecmap(int)
49+
50+
@generate
51+
def diff_string() -> (Kind, Diff, int):
52+
"""Usage: diff_string.parse(string)
53+
54+
Example string:
55+
56+
-// gas irOptimized: 138070
57+
58+
"""
59+
diff_kind = yield (minus | plus)
60+
yield comment
61+
yield space
62+
codegen_kind = yield (gas_ir_optimized ^ gas_legacy_optimized ^ gas_legacy)
63+
yield colon
64+
yield space
65+
val = yield number()
66+
return (diff_kind, codegen_kind, val)
67+
68+
def collect_statistics(lines) -> (int, int, int, int, int, int):
69+
"""Returns
70+
71+
(old_ir_optimized, old_legacy_optimized, old_legacy, new_ir_optimized,
72+
new_legacy_optimized, new_legacy)
73+
74+
All the values in the same file (in the diff) are summed up.
75+
76+
"""
77+
if not lines:
78+
raise Exception("Empty list")
79+
80+
def try_parse(line):
81+
try:
82+
return diff_string.parse(line)
83+
except ParseError:
84+
pass
85+
86+
out = [parsed for line in lines if (parsed := try_parse(line)) is not None]
87+
diff_kinds = [Diff.Minus, Diff.Plus]
88+
codegen_kinds = [Kind.IrOptimized, Kind.LegacyOptimized, Kind.Legacy]
89+
return tuple([
90+
sum([
91+
val
92+
for (diff_kind, codegen_kind, val) in out
93+
if diff_kind == _diff_kind and codegen_kind == _codegen_kind
94+
])
95+
for _diff_kind in diff_kinds
96+
for _codegen_kind in codegen_kinds
97+
])
98+
99+
def semantictest_statistics():
100+
"""Prints the tabulated statistics that can be pasted in github."""
101+
def try_parse_git_diff(fname):
102+
try:
103+
diff_output = subprocess.check_output(
104+
"git diff --unified=0 origin/develop HEAD " + fname,
105+
shell=True,
106+
universal_newlines=True
107+
).splitlines()
108+
if diff_output:
109+
return collect_statistics(diff_output)
110+
except subprocess.CalledProcessError as e:
111+
print("Error in the git diff:")
112+
print(e.output)
113+
return None
114+
def stat(old, new):
115+
return ((new - old) / old) * 100 if old else 0
116+
117+
table = []
118+
119+
for path in Path("test/libsolidity/semanticTests").rglob("*.sol"):
120+
fname = path.as_posix()
121+
parsed = try_parse_git_diff(fname)
122+
if parsed is None:
123+
continue
124+
ir_optimized = stat(parsed[0], parsed[3])
125+
legacy_optimized = stat(parsed[1], parsed[4])
126+
legacy = stat(parsed[2], parsed[5])
127+
fname = fname.split('/', 3)[-1]
128+
table += [map(str, [fname, ir_optimized, legacy_optimized, legacy])]
129+
130+
if table:
131+
print("<details><summary>Click for a table of gas differences</summary>\n")
132+
table_header = ["File name", "IR-optimized (%)", "Legacy-Optimized (%)", "Legacy (%)"]
133+
print(tabulate(table, headers=table_header, tablefmt="github"))
134+
print("</details>")
135+
else:
136+
print("No differences found.")
137+
138+
if __name__ == "__main__":
139+
semantictest_statistics()

0 commit comments

Comments
 (0)