From 4cf1b0c003a8bd0214e548a77bf0336148235931 Mon Sep 17 00:00:00 2001
From: Daniel Mueller <deso@posteo.net>
Date: Wed, 16 Jan 2019 18:58:32 -0800
Subject: Add a script to determine the nitrocli binary size at a git revision

We have been loosely tracking the resulting size of the stripped release
binary as that is arguably the most relevant metric to optimize for. To
have a better idea of the influence of various changes and their effect
on the binary size, this change adds a script that automates the process
of gathering this metric. E.g.,
  $ var/binary-size.py HEAD~3 HEAD --unit kib
  > 994
  > 970
---
 nitrocli/var/binary-size.py | 134 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 134 insertions(+)
 create mode 100755 nitrocli/var/binary-size.py

diff --git a/nitrocli/var/binary-size.py b/nitrocli/var/binary-size.py
new file mode 100755
index 0000000..3653814
--- /dev/null
+++ b/nitrocli/var/binary-size.py
@@ -0,0 +1,134 @@
+#!/usr/bin/python3 -B
+
+#/***************************************************************************
+# *   Copyright (C) 2019 Daniel Mueller (deso@posteo.net)                   *
+# *                                                                         *
+# *   This program is free software: you can redistribute it and/or modify  *
+# *   it under the terms of the GNU General Public License as published by  *
+# *   the Free Software Foundation, either version 3 of the License, or     *
+# *   (at your option) any later version.                                   *
+# *                                                                         *
+# *   This program is distributed in the hope that it will be useful,       *
+# *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
+# *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
+# *   GNU General Public License for more details.                          *
+# *                                                                         *
+# *   You should have received a copy of the GNU General Public License     *
+# *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
+# ***************************************************************************/
+
+from argparse import (
+  ArgumentParser,
+  ArgumentTypeError,
+)
+from concurrent.futures import (
+  ThreadPoolExecutor,
+)
+from json import (
+  loads as jsonLoad,
+)
+from os import (
+  stat,
+)
+from os.path import (
+  join,
+)
+from subprocess import (
+  check_call,
+  check_output,
+)
+from sys import (
+  argv,
+  exit,
+)
+from tempfile import (
+  TemporaryDirectory,
+)
+
+UNITS = {
+  "byte": 1,
+  "kib": 1024,
+  "mib": 1024 * 1024,
+}
+
+def unit(string):
+  """Create a unit."""
+  if string in UNITS:
+    return UNITS[string]
+  else:
+    raise ArgumentTypeError("Invalid unit: \"%s\"." % string)
+
+
+def nitrocliPath(cwd):
+  """Determine the path to the nitrocli release build binary."""
+  out = check_output(["cargo", "metadata", "--format-version=1"], cwd=cwd)
+  data = jsonLoad(out)
+  return join(data["target_directory"], "release", "nitrocli")
+
+
+def fileSize(path):
+  """Determine the size of the file at the given path."""
+  return stat(path).st_size
+
+
+def repoRoot():
+  """Retrieve the root directory of the current git repository."""
+  out = check_output(["git", "rev-parse", "--show-toplevel"])
+  return out.decode().strip()
+
+
+def resolveCommit(commit):
+  """Resolve a commit into a SHA1 hash."""
+  out = check_output(["git", "rev-parse", "--verify", "%s^{commit}" % commit])
+  return out.decode().strip()
+
+
+def determineSizeAt(root, rev):
+  """Determine the size of the nitrocli release build binary at the given git revision."""
+  sha1 = resolveCommit(rev)
+  with TemporaryDirectory() as d:
+    cwd = join(d, "nitrocli")
+    check_call(["git", "clone", root, d])
+    check_call(["git", "checkout", "--quiet", sha1], cwd=cwd)
+    check_call(["cargo", "build", "--quiet", "--release"], cwd=cwd)
+
+    ncli = nitrocliPath(cwd)
+    check_call(["strip", ncli])
+    return fileSize(ncli)
+
+
+def setupArgumentParser():
+  """Create and initialize an argument parser."""
+  parser = ArgumentParser()
+  parser.add_argument(
+    "revs", metavar="REVS", nargs="+",
+    help="The revisions at which to measure the release binary size.",
+  )
+  parser.add_argument(
+    "-u", "--unit", default="byte", dest="unit", metavar="UNIT", type=unit,
+    help="The unit in which to output the result (%s)." % "|".join(UNITS.keys()),
+  )
+  return parser
+
+
+def main(args):
+  """Determine the size of the nitrocli binary at given git revisions."""
+  parser = setupArgumentParser()
+  ns = parser.parse_args(args)
+  root = repoRoot()
+  futures = []
+  executor = ThreadPoolExecutor()
+
+  for rev in ns.revs:
+    futures += [executor.submit(lambda r=rev: determineSizeAt(root, r))]
+
+  executor.shutdown(wait=True)
+
+  for future in futures:
+    print(int(round(future.result() / ns.unit, 0)))
+
+  return 0
+
+
+if __name__ == "__main__":
+  exit(main(argv[1:]))
-- 
cgit v1.2.3