-
Notifications
You must be signed in to change notification settings - Fork 249
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ci: A manual command for comparing contract sizes (#1048)
- Loading branch information
Showing
8 changed files
with
263 additions
and
169 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
name: Compare contract sizes with master | ||
on: | ||
issue_comment: | ||
types: | ||
- created | ||
jobs: | ||
generate_report: | ||
runs-on: ubuntu-latest | ||
if: ${{ (github.event.issue.pull_request) && (github.event.comment.body == '/compare') }} | ||
permissions: | ||
pull-requests: write | ||
steps: | ||
- name: Checkout | ||
uses: actions/checkout@v2 | ||
- name: Checkout Pull Request | ||
run: hub pr checkout ${{ github.event.issue.number }} | ||
env: | ||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
- name: Cache intermediate artifacts | ||
uses: actions/cache@v3 | ||
env: | ||
cache-name: compare-sizes | ||
with: | ||
path: | | ||
docker-target | ||
/home/runner/work/.cargo/git | ||
/home/runner/work/.cargo/registry | ||
# TODO: add an additional key here, e.g. the Rust toolchain version used | ||
key: ${{ runner.os }}-build-${{ env.cache-name }} | ||
# restore-keys: | | ||
# ${{ runner.os }}-build-${{ env.cache-name }} | ||
- name: Generate report | ||
run: | | ||
yes | pip install GitPython docker appdirs | ||
EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64) | ||
echo "REPORT<<$EOF" >> "$GITHUB_ENV" | ||
ci/compare_sizes/compare_sizes.py --cargo-cache-dir /home/runner/work/.cargo >> "$GITHUB_ENV" | ||
echo "$EOF" >> "$GITHUB_ENV" | ||
- name: Submit report | ||
if: ${{ success() }} | ||
uses: actions/github-script@v4 | ||
with: | ||
script: | | ||
github.issues.createComment({ | ||
issue_number: context.issue.number, | ||
owner: context.repo.owner, | ||
repo: context.repo.repo, | ||
body: process.env.REPORT, | ||
}); | ||
- name: Notify about failure | ||
if: ${{ failure() }} | ||
uses: actions/github-script@v4 | ||
with: | ||
script: | | ||
github.issues.createComment({ | ||
issue_number: context.issue.number, | ||
owner: context.repo.owner, | ||
repo: context.repo.repo, | ||
body: 'Failed to generate size comparison report! See the failed CI job for more details.', | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,3 +17,6 @@ neardev | |
.idea | ||
.vscode | ||
**/.DS_Store | ||
|
||
# Python | ||
__pycache__ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
# Compare example contract sizes | ||
|
||
This is a script to compare example contract sizes between the current non-master branch and `master`, and then produce a markdown report. | ||
|
||
# Usage | ||
|
||
The script is mostly triggered in PRs by posting `/compare` in a comment. For details, check out [the workflow](../../.github/workflows/compare_sizes.yml). | ||
|
||
It's also possible to test it locally like so: | ||
|
||
``` sh | ||
# from the root dir | ||
ci/compare_sizes/compare_sizes.py | ||
``` | ||
|
||
When run like this, the script maintains a build cache in the user app directories appropriate for the host platform. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import os | ||
import sys | ||
|
||
|
||
class Cache: | ||
def __init__(self, dir): | ||
self._dir = dir | ||
print(f"Cache directory: {dir}", file=sys.stderr) | ||
os.makedirs(self.registry, exist_ok=True) | ||
os.makedirs(self.git, exist_ok=True) | ||
os.makedirs(self.target, exist_ok=True) | ||
|
||
@property | ||
def root(self): | ||
return self._dir | ||
|
||
@property | ||
def registry(self): | ||
return os.path.join(self.root, "registry") | ||
|
||
@property | ||
def git(self): | ||
return os.path.join(self.root, "git") | ||
|
||
@property | ||
def target(self): | ||
return os.path.join(self.root, "target") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
#!/usr/bin/env python3 | ||
|
||
# Requires: | ||
# `pip install GitPython docker appdirs` | ||
import argparse | ||
import os | ||
import sys | ||
from cache import Cache | ||
from appdirs import AppDirs | ||
|
||
from project_instance import ProjectInstance | ||
|
||
|
||
def common_entries(*dcts): | ||
if not dcts: | ||
return | ||
for i in set(dcts[0]).intersection(*dcts[1:]): | ||
yield (i,) + tuple(d[i] for d in dcts) | ||
|
||
|
||
def list_dirs(path): | ||
entries = map(lambda p: os.path.join(path, p), os.listdir(path)) | ||
return filter(os.path.isdir, entries) | ||
|
||
|
||
def report(master, this_branch): | ||
def diff(old, new): | ||
diff = (new - old) / old | ||
|
||
return "{0:+.0%}".format(diff) | ||
|
||
header = """# Contract size report | ||
Sizes are given in bytes. | ||
| contract | master | this branch | difference | | ||
| - | - | - | - |""" | ||
|
||
combined = [ | ||
(name, master, branch, diff(master, branch)) | ||
for name, master, branch in common_entries(master, this_branch) | ||
] | ||
combined.sort(key=lambda el: el[0]) | ||
rows = [f"| {name} | {old} | {new} | {diff} |" for name, old, new, diff in combined] | ||
|
||
return "\n".join([header, *rows]) | ||
|
||
|
||
def main(): | ||
parser = argparse.ArgumentParser( | ||
prog="compare_sizes", | ||
description="compare example contract sizes between current branch and master", | ||
) | ||
parser.add_argument("-c", "--cargo-cache-dir") | ||
args = parser.parse_args() | ||
|
||
default_cache_dir = os.path.join( | ||
AppDirs("near_sdk_dev_cache", "near").user_data_dir, | ||
"contract_build", | ||
) | ||
cache_dir = args.cargo_cache_dir if args.cargo_cache_dir else default_cache_dir | ||
cache = Cache(cache_dir) | ||
|
||
this_file = os.path.abspath(os.path.realpath(__file__)) | ||
project_root = os.path.dirname(os.path.dirname(os.path.dirname(this_file))) | ||
|
||
cur_branch = ProjectInstance(project_root) | ||
|
||
with cur_branch.branch("master") as master: | ||
cur_sizes = cur_branch.sizes(cache) | ||
master_sizes = master.sizes(cache) | ||
|
||
print(report(master_sizes, cur_sizes)) | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
import docker | ||
import os | ||
import glob | ||
import subprocess | ||
import tempfile | ||
import shutil | ||
import sys | ||
import platform | ||
from contextlib import contextmanager | ||
from git import Repo | ||
|
||
|
||
class ProjectInstance: | ||
def __init__(self, root_dir): | ||
self._root_dir = root_dir | ||
|
||
@contextmanager | ||
def branch(self, branch): | ||
repo = Repo(self._root_dir) | ||
|
||
try: | ||
with tempfile.TemporaryDirectory() as tempdir: | ||
repo.git.worktree("add", tempdir, branch) | ||
branch_project = ProjectInstance(tempdir) | ||
|
||
yield branch_project | ||
finally: | ||
repo.git.worktree("prune") | ||
|
||
@property | ||
def _examples_dir(self): | ||
return os.path.join(self._root_dir, "examples") | ||
|
||
def _build_artifact(self, artifact, cache): | ||
client = docker.from_env() | ||
tag = "latest-arm64" if platform.machine() == "ARM64" else "latest-amd64" | ||
image = f"nearprotocol/contract-builder:{tag}" | ||
|
||
client.containers.run( | ||
image, | ||
"./build.sh", | ||
mounts=[ | ||
docker.types.Mount("/host", self._root_dir, type="bind"), | ||
docker.types.Mount( | ||
"/usr/local/cargo/registry", cache.registry, type="bind" | ||
), | ||
docker.types.Mount("/usr/local/cargo/git", cache.git, type="bind"), | ||
docker.types.Mount("/target", cache.target, type="bind"), | ||
], | ||
working_dir=f"/host/examples/{artifact.name}", | ||
cap_add=["SYS_PTRACE"], | ||
security_opt=["seccomp=unconfined"], | ||
remove=True, | ||
user=os.getuid(), | ||
environment={ | ||
"RUSTFLAGS": "-C link-arg=-s", | ||
"CARGO_TARGET_DIR": "/target", | ||
}, | ||
) | ||
|
||
@property | ||
def _examples(self): | ||
examples = filter(os.DirEntry.is_dir, os.scandir(self._examples_dir)) | ||
|
||
# build "status-message" first, as it's a dependency of some other examples | ||
return sorted(examples, key=lambda x: x.name != "status-message") | ||
|
||
def build_artifacts(self, cache): | ||
for example in self._examples: | ||
print(f"Building {example.name}...", file=sys.stderr) | ||
self._build_artifact(example, cache) | ||
|
||
def sizes(self, cache): | ||
self.build_artifacts(cache) | ||
|
||
artifact_paths = glob.glob(self._examples_dir + "/*/res/*.wasm") | ||
return { | ||
os.path.basename(path): os.stat(path).st_size for path in artifact_paths | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,5 +2,4 @@ | |
* | ||
|
||
# Except this to keep the directory | ||
!.gitignore | ||
!adder_abi.json | ||
!.gitignore |
Oops, something went wrong.