git_cache: add more diagnostic logging wrt bootstrapping.

This is short of sending this metrics to ts_mon, but is still useful
for diagnose and then claim improvements in git cache performance on
bots.

This change has been tested for realz with led, particularly on windows
https://ci.chromium.org/swarming/task/3a01358af14a7d10

Bug: 749709
Change-Id: I2b3589079d2caa7f70007f90fcbce85a0205d24b
Reviewed-on: https://chromium-review.googlesource.com/787173
Reviewed-by: Takuto Ikuta <tikuta@google.com>
Reviewed-by: Sergiy Byelozyorov <sergiyb@chromium.org>
Reviewed-by: Michael Achenbach <machenbach@chromium.org>
Commit-Queue: Andrii Shyshkalov <tandrii@chromium.org>
diff --git a/git_cache.py b/git_cache.py
index 01b1482..2bcc139 100755
--- a/git_cache.py
+++ b/git_cache.py
@@ -6,6 +6,7 @@
 """A git command for managing a local cache of git repositories."""
 
 from __future__ import print_function
+import contextlib
 import errno
 import logging
 import optparse
@@ -223,6 +224,14 @@
   def print_without_file(self, message, **_kwargs):
     self.print_func(message)
 
+  @contextlib.contextmanager
+  def print_duration_of(self, what):
+    start = time.time()
+    try:
+      yield
+    finally:
+      self.print('%s took %.1f minutes' % (what, (time.time() - start) / 60.0))
+
   @property
   def bootstrap_bucket(self):
     u = urlparse.urlparse(self.url)
@@ -324,11 +333,10 @@
           cwd=cwd)
 
   def bootstrap_repo(self, directory):
-    """Bootstrap the repo from Google Stroage if possible.
+    """Bootstrap the repo from Google Storage if possible.
 
     More apt-ly named bootstrap_repo_from_cloud_if_possible_else_do_nothing().
     """
-
     if not self.bootstrap_bucket:
       return False
     python_fallback = False
@@ -344,10 +352,13 @@
     gs_folder = 'gs://%s/%s' % (self.bootstrap_bucket, self.basedir)
     gsutil = Gsutil(self.gsutil_exe, boto_path=None)
     # Get the most recent version of the zipfile.
-    _, ls_out, _ = gsutil.check_call('ls', gs_folder)
+    _, ls_out, ls_err = gsutil.check_call('ls', gs_folder)
     ls_out_sorted = sorted(ls_out.splitlines())
     if not ls_out_sorted:
       # This repo is not on Google Storage.
+      self.print('No bootstrap file for %s found in %s, stderr:\n  %s' %
+                 (self.mirror_path, self.bootstrap_bucket,
+                  '  '.join((ls_err or '').splitlines(True))))
       return False
     latest_checkout = ls_out_sorted[-1]
 
@@ -355,28 +366,30 @@
     try:
       tempdir = tempfile.mkdtemp(prefix='_cache_tmp', dir=self.GetCachePath())
       self.print('Downloading %s' % latest_checkout)
-      code = gsutil.call('cp', latest_checkout, tempdir)
+      with self.print_duration_of('download'):
+        code = gsutil.call('cp', latest_checkout, tempdir)
       if code:
         return False
       filename = os.path.join(tempdir, latest_checkout.split('/')[-1])
 
       # Unpack the file with 7z on Windows, unzip on linux, or fallback.
-      if not python_fallback:
-        if sys.platform.startswith('win'):
-          cmd = ['7z', 'x', '-o%s' % directory, '-tzip', filename]
+      with self.print_duration_of('unzip'):
+        if not python_fallback:
+          if sys.platform.startswith('win'):
+            cmd = ['7z', 'x', '-o%s' % directory, '-tzip', filename]
+          else:
+            cmd = ['unzip', filename, '-d', directory]
+          retcode = subprocess.call(cmd)
         else:
-          cmd = ['unzip', filename, '-d', directory]
-        retcode = subprocess.call(cmd)
-      else:
-        try:
-          with zipfile.ZipFile(filename, 'r') as f:
-            f.printdir()
-            f.extractall(directory)
-        except Exception as e:
-          self.print('Encountered error: %s' % str(e), file=sys.stderr)
-          retcode = 1
-        else:
-          retcode = 0
+          try:
+            with zipfile.ZipFile(filename, 'r') as f:
+              f.printdir()
+              f.extractall(directory)
+          except Exception as e:
+            self.print('Encountered error: %s' % str(e), file=sys.stderr)
+            retcode = 1
+          else:
+            retcode = 0
     finally:
       # Clean up the downloaded zipfile.
       #
@@ -432,6 +445,8 @@
 
     if os.path.isdir(pack_dir):
       pack_files = [f for f in os.listdir(pack_dir) if f.endswith('.pack')]
+      self.print('%s has %d .pack files, re-bootstrapping if >%d' %
+                 (self.mirror_path, len(pack_files), GC_AUTOPACKLIMIT))
 
     should_bootstrap = (force or
                         not self.exists() or
@@ -455,8 +470,8 @@
       else:
         # Bootstrap failed, previous cache exists; warn and continue.
         logging.warn(
-            'Git cache has a lot of pack files (%d).  Tried to re-bootstrap '
-            'but failed.  Continuing with non-optimized repository.'
+            'Git cache has a lot of pack files (%d). Tried to re-bootstrap '
+            'but failed. Continuing with non-optimized repository.'
             % len(pack_files))
         gclient_utils.rmtree(tempdir)
         tempdir = None
@@ -481,7 +496,8 @@
     for spec in fetch_specs:
       try:
         self.print('Fetching %s' % spec)
-        self.RunGit(fetch_cmd + [spec], cwd=rundir, retry=True)
+        with self.print_duration_of('fetch %s' % spec):
+          self.RunGit(fetch_cmd + [spec], cwd=rundir, retry=True)
       except subprocess.CalledProcessError:
         if spec == '+refs/heads/*:refs/heads/*':
           raise ClobberNeeded()  # Corrupted cache.