emit by-file timing information
diff --git a/src/black/__init__.py b/src/black/__init__.py
index a985926..3360a14 100644
--- a/src/black/__init__.py
+++ b/src/black/__init__.py
@@ -4,12 +4,14 @@
 from datetime import datetime
 from enum import Enum
 import io
+import json
 from multiprocessing import Manager, freeze_support
 import os
 from pathlib import Path
 import regex as re
 import signal
 import sys
+import time
 import tokenize
 import traceback
 from typing import (
@@ -449,6 +451,8 @@
         out(error_msg if report.return_code else "All done! ✨ 🍰 ✨")
         if code is None:
             click.echo(str(report), err=True)
+    with open("timing.json", "w") as f:
+        json.dump({str(p): v for p, v in report.times.items()}, f)
     ctx.exit(report.return_code)
 
 
@@ -547,13 +551,14 @@
     :func:`format_file_in_place` or :func:`format_stdin_to_stdout`.
     """
     path = Path("<string>")
+    start_time = time.time()
     try:
         changed = Changed.NO
         if format_stdin_to_stdout(
             content=content, fast=fast, write_back=write_back, mode=mode
         ):
             changed = Changed.YES
-        report.done(path, changed)
+        report.done(path, changed, time.time() - start_time)
     except Exception as exc:
         if report.verbose:
             traceback.print_exc()
@@ -569,6 +574,7 @@
     :func:`format_file_in_place` or :func:`format_stdin_to_stdout`.
     """
     try:
+        start_time = time.time()
         changed = Changed.NO
 
         if str(src) == "-":
@@ -594,15 +600,17 @@
                 res_src_s = str(res_src)
                 if res_src_s in cache and cache[res_src_s] == get_cache_info(res_src):
                     changed = Changed.CACHED
-            if changed is not Changed.CACHED and format_file_in_place(
-                src, fast=fast, write_back=write_back, mode=mode
-            ):
-                changed = Changed.YES
+            if changed is not Changed.CACHED:
+                result, _ = format_file_in_place(
+                    src, fast=fast, write_back=write_back, mode=mode
+                )
+                if result:
+                    changed = Changed.YES
             if (write_back is WriteBack.YES and changed is not Changed.CACHED) or (
                 write_back is WriteBack.CHECK and changed is Changed.NO
             ):
                 write_cache(cache, [src], mode)
-        report.done(src, changed)
+        report.done(src, changed, time.time() - start_time)
     except Exception as exc:
         if report.verbose:
             traceback.print_exc()
@@ -667,7 +675,7 @@
         cache = read_cache(mode)
         sources, cached = filter_cached(cache, sources)
         for src in sorted(cached):
-            report.done(src, Changed.CACHED)
+            report.done(src, Changed.CACHED, 0.0)
     if not sources:
         return
 
@@ -703,14 +711,15 @@
             elif task.exception():
                 report.failed(src, str(task.exception()))
             else:
-                changed = Changed.YES if task.result() else Changed.NO
+                result, time_taken = task.result()
+                changed = Changed.YES if result else Changed.NO
                 # If the file was written back or was successfully checked as
                 # well-formatted, store this information in the cache.
                 if write_back is WriteBack.YES or (
                     write_back is WriteBack.CHECK and changed is Changed.NO
                 ):
                     sources_to_cache.append(src)
-                report.done(src, changed)
+                report.done(src, changed, time_taken)
     if cancelled:
         await asyncio.gather(*cancelled, loop=loop, return_exceptions=True)
     if sources_to_cache:
@@ -723,13 +732,14 @@
     mode: Mode,
     write_back: WriteBack = WriteBack.NO,
     lock: Any = None,  # multiprocessing.Manager().Lock() is some crazy proxy
-) -> bool:
+) -> Tuple[bool, float]:
     """Format file under `src` path. Return True if changed.
 
     If `write_back` is DIFF, write a diff to stdout. If it is YES, write reformatted
     code to the file.
     `mode` and `fast` options are passed to :func:`format_file_contents`.
     """
+    start_time = time.time()
     if src.suffix == ".pyi":
         mode = replace(mode, is_pyi=True)
 
@@ -739,7 +749,7 @@
     try:
         dst_contents = format_file_contents(src_contents, fast=fast, mode=mode)
     except NothingChanged:
-        return False
+        return False, time.time() - start_time
 
     if write_back == WriteBack.YES:
         with open(src, "w", encoding=encoding, newline=newline) as f:
@@ -764,7 +774,7 @@
             f.write(diff_contents)
             f.detach()
 
-    return True
+    return True, time.time() - start_time
 
 
 def format_stdin_to_stdout(
diff --git a/src/black/report.py b/src/black/report.py
index 8fc5da2..05d1a0c 100644
--- a/src/black/report.py
+++ b/src/black/report.py
@@ -1,9 +1,11 @@
 """
 Summarize Black runs to users.
 """
-from dataclasses import dataclass
+from dataclasses import dataclass, field
 from enum import Enum
 from pathlib import Path
+from typing import Dict
+import time
 
 from click import style
 
@@ -27,20 +29,23 @@
     change_count: int = 0
     same_count: int = 0
     failure_count: int = 0
+    times: Dict[Path, float] = field(default_factory=dict)
 
-    def done(self, src: Path, changed: Changed) -> None:
+    def done(self, src: Path, changed: Changed, time_taken: float) -> None:
         """Increment the counter for successful reformatting. Write out a message."""
+        time_str = f"({time_taken:.2f} s)"
+        self.times[src] = time_taken
         if changed is Changed.YES:
             reformatted = "would reformat" if self.check or self.diff else "reformatted"
             if self.verbose or not self.quiet:
-                out(f"{reformatted} {src}")
+                out(f"{reformatted} {src} {time_str}")
             self.change_count += 1
         else:
             if self.verbose:
                 if changed is Changed.NO:
-                    msg = f"{src} already well formatted, good job."
+                    msg = f"{src} already well formatted, good job {time_str}."
                 else:
-                    msg = f"{src} wasn't modified on disk since last run."
+                    msg = f"{src} wasn't modified on disk since last run {time_str}."
                 out(msg, bold=False)
             self.same_count += 1