(reland) Optimize check_gn_headers.py for speed

- Process the output of 'ninja' live through the pipe rather than
  waiting for it to finish.
- Use multiprocessing.

Benchmark results using pypy:
Before:
17.10user 14.38system 0:20.05elapsed 157%CPU (0avgtext+0avgdata 1708036maxresident)k
18.06user 15.63system 0:21.92elapsed 153%CPU (0avgtext+0avgdata 1710912maxresident)k
17.94user 13.72system 0:19.91elapsed 158%CPU (0avgtext+0avgdata 1172128maxresident)k

After:
15.28user 7.11system 0:06.25elapsed 357%CPU (0avgtext+0avgdata 319236maxresident)k
16.03user 7.74system 0:06.84elapsed 347%CPU (0avgtext+0avgdata 319396maxresident)k
15.24user 8.16system 0:06.36elapsed 367%CPU (0avgtext+0avgdata 318272maxresident)k

BUG=661774

Review-Url: https://codereview.chromium.org/2842513003
Cr-Original-Commit-Position: refs/heads/master@{#466900}
Review-Url: https://codereview.chromium.org/2846473002
Cr-Commit-Position: refs/heads/master@{#467604}
diff --git a/build/check_gn_headers.py b/build/check_gn_headers.py
index ae1ef49..be1e797a 100755
--- a/build/check_gn_headers.py
+++ b/build/check_gn_headers.py
@@ -15,12 +15,27 @@
 import re
 import subprocess
 import sys
+from multiprocessing import Process, Queue
 
 
-def GetHeadersFromNinja(out_dir):
+def GetHeadersFromNinja(out_dir, q):
   """Return all the header files from ninja_deps"""
-  ninja_out = subprocess.check_output(['ninja', '-C', out_dir, '-t', 'deps'])
-  return ParseNinjaDepsOutput(ninja_out)
+
+  def NinjaSource():
+    cmd = ['ninja', '-C', out_dir, '-t', 'deps']
+    # A negative bufsize means to use the system default, which usually
+    # means fully buffered.
+    popen = subprocess.Popen(cmd, stdout=subprocess.PIPE, bufsize=-1)
+    for line in iter(popen.stdout.readline, ''):
+      yield line.rstrip()
+
+    popen.stdout.close()
+    return_code = popen.wait()
+    if return_code:
+      raise subprocess.CalledProcessError(return_code, cmd)
+
+  ninja_out = NinjaSource()
+  q.put(ParseNinjaDepsOutput(ninja_out))
 
 
 def ParseNinjaDepsOutput(ninja_out):
@@ -30,7 +45,7 @@
   prefix = '..' + os.sep + '..' + os.sep
 
   is_valid = False
-  for line in ninja_out.split('\n'):
+  for line in ninja_out:
     if line.startswith('    '):
       if not is_valid:
         continue
@@ -49,11 +64,11 @@
   return all_headers
 
 
-def GetHeadersFromGN(out_dir):
+def GetHeadersFromGN(out_dir, q):
   """Return all the header files from GN"""
   subprocess.check_call(['gn', 'gen', out_dir, '--ide=json', '-q'])
   gn_json = json.load(open(os.path.join(out_dir, 'project.json')))
-  return ParseGNProjectJSON(gn_json)
+  q.put(ParseGNProjectJSON(gn_json))
 
 
 def ParseGNProjectJSON(gn):
@@ -70,7 +85,7 @@
   return all_headers
 
 
-def GetDepsPrefixes():
+def GetDepsPrefixes(q):
   """Return all the folders controlled by DEPS file"""
   gclient_out = subprocess.check_output(
       ['gclient', 'recurse', '--no-progress', '-j1',
@@ -80,7 +95,7 @@
     if i.startswith('src/'):
       i = i[4:]
       prefixes.add(i)
-  return prefixes
+  q.put(prefixes)
 
 
 def ParseWhiteList(whitelist):
@@ -101,13 +116,29 @@
 
   args, _extras = parser.parse_known_args()
 
-  d = GetHeadersFromNinja(args.out_dir)
-  gn = GetHeadersFromGN(args.out_dir)
+  d_q = Queue()
+  d_p = Process(target=GetHeadersFromNinja, args=(args.out_dir, d_q,))
+  d_p.start()
+
+  gn_q = Queue()
+  gn_p = Process(target=GetHeadersFromGN, args=(args.out_dir, gn_q,))
+  gn_p.start()
+
+  deps_q = Queue()
+  deps_p = Process(target=GetDepsPrefixes, args=(deps_q,))
+  deps_p.start()
+
+  d = d_q.get()
+  gn = gn_q.get()
   missing = d - gn
 
-  deps = GetDepsPrefixes()
+  deps = deps_q.get()
   missing = {m for m in missing if not any(m.startswith(d) for d in deps)}
 
+  d_p.join()
+  gn_p.join()
+  deps_p.join()
+
   if args.whitelist:
     whitelist = ParseWhiteList(open(args.whitelist).read())
     missing -= whitelist