More logging.
diff --git a/lib/src/dart_formatter.dart b/lib/src/dart_formatter.dart
index 15a0415..92bd793 100644
--- a/lib/src/dart_formatter.dart
+++ b/lib/src/dart_formatter.dart
@@ -18,6 +18,7 @@
 import 'constants.dart';
 import 'exceptions.dart';
 import 'front_end/ast_node_visitor.dart';
+import 'profile.dart';
 import 'short/source_visitor.dart';
 import 'short/style_fix.dart';
 import 'source_code.dart';
@@ -97,104 +98,116 @@
   /// Returns a new [SourceCode] containing the formatted code and the resulting
   /// selection, if any.
   SourceCode formatSource(SourceCode source) {
-    var inputOffset = 0;
-    var text = source.text;
-    var unitSourceCode = source;
+    Profile.begin2('DartFormatter.formatSource()', source.uri);
+    try {
+      var inputOffset = 0;
+      var text = source.text;
+      var unitSourceCode = source;
 
-    // If we're parsing a single statement, wrap the source in a fake function.
-    if (!source.isCompilationUnit) {
-      var prefix = 'void foo() { ';
-      inputOffset = prefix.length;
-      text = '$prefix$text }';
-      unitSourceCode = SourceCode(
-        text,
-        uri: source.uri,
-        isCompilationUnit: false,
-        selectionStart: source.selectionStart != null
-            ? source.selectionStart! + inputOffset
-            : null,
-        selectionLength: source.selectionLength,
-      );
-    }
-
-    // Parse it.
-    var parseResult = _parse(text, source.uri, patterns: true);
-
-    // If we couldn't parse it with patterns enabled, it may be because of
-    // one of the breaking syntax changes to switch cases. Try parsing it
-    // again without patterns.
-    if (parseResult.errors.isNotEmpty) {
-      var withoutPatternsResult = _parse(text, source.uri, patterns: false);
-
-      // If we succeeded this time, use this parse instead.
-      if (withoutPatternsResult.errors.isEmpty) {
-        parseResult = withoutPatternsResult;
+      // If we're parsing a single statement, wrap the source in a fake function.
+      if (!source.isCompilationUnit) {
+        var prefix = 'void foo() { ';
+        inputOffset = prefix.length;
+        text = '$prefix$text }';
+        unitSourceCode = SourceCode(
+          text,
+          uri: source.uri,
+          isCompilationUnit: false,
+          selectionStart: source.selectionStart != null
+              ? source.selectionStart! + inputOffset
+              : null,
+          selectionLength: source.selectionLength,
+        );
       }
-    }
 
-    // Infer the line ending if not given one. Do it here since now we know
-    // where the lines start.
-    if (lineEnding == null) {
-      // If the first newline is "\r\n", use that. Otherwise, use "\n".
-      var lineStarts = parseResult.lineInfo.lineStarts;
-      if (lineStarts.length > 1 &&
-          lineStarts[1] >= 2 &&
-          text[lineStarts[1] - 2] == '\r') {
-        lineEnding = '\r\n';
+      // Parse it.
+      Profile.begin2('parse', source.uri);
+      ParseStringResult parseResult;
+      AstNode node;
+      try {
+        parseResult = _parse(text, source.uri, patterns: true);
+
+        // If we couldn't parse it with patterns enabled, it may be because of
+        // one of the breaking syntax changes to switch cases. Try parsing it
+        // again without patterns.
+        if (parseResult.errors.isNotEmpty) {
+          var withoutPatternsResult = _parse(text, source.uri, patterns: false);
+
+          // If we succeeded this time, use this parse instead.
+          if (withoutPatternsResult.errors.isEmpty) {
+            parseResult = withoutPatternsResult;
+          }
+        }
+
+        // Infer the line ending if not given one. Do it here since now we know
+        // where the lines start.
+        if (lineEnding == null) {
+          // If the first newline is "\r\n", use that. Otherwise, use "\n".
+          var lineStarts = parseResult.lineInfo.lineStarts;
+          if (lineStarts.length > 1 &&
+              lineStarts[1] >= 2 &&
+              text[lineStarts[1] - 2] == '\r') {
+            lineEnding = '\r\n';
+          } else {
+            lineEnding = '\n';
+          }
+        }
+
+        // Throw if there are syntactic errors.
+        var syntacticErrors = parseResult.errors.where((error) {
+          return error.errorCode.type == ErrorType.SYNTACTIC_ERROR;
+        }).toList();
+        if (syntacticErrors.isNotEmpty) {
+          throw FormatterException(syntacticErrors);
+        }
+
+        if (source.isCompilationUnit) {
+          node = parseResult.unit;
+        } else {
+          var function =
+              parseResult.unit.declarations[0] as FunctionDeclaration;
+          var body = function.functionExpression.body as BlockFunctionBody;
+          node = body.block.statements[0];
+
+          // Make sure we consumed all of the source.
+          var token = node.endToken.next!;
+          if (token.type != TokenType.CLOSE_CURLY_BRACKET) {
+            var stringSource = StringSource(text, source.uri);
+            var error = AnalysisError.tmp(
+                source: stringSource,
+                offset: token.offset - inputOffset,
+                length: math.max(token.length, 1),
+                errorCode: ParserErrorCode.UNEXPECTED_TOKEN,
+                arguments: [token.lexeme]);
+            throw FormatterException([error]);
+          }
+        }
+      } finally {
+        Profile.end2('parse', source.uri);
+      }
+
+      // Format it.
+      var lineInfo = parseResult.lineInfo;
+
+      SourceCode output;
+      if (experimentFlags.contains(tallStyleExperimentFlag)) {
+        var visitor = AstNodeVisitor(this, lineInfo, unitSourceCode);
+        output = visitor.run(node);
       } else {
-        lineEnding = '\n';
+        var visitor = SourceVisitor(this, lineInfo, unitSourceCode);
+        output = visitor.run(node);
       }
-    }
 
-    // Throw if there are syntactic errors.
-    var syntacticErrors = parseResult.errors.where((error) {
-      return error.errorCode.type == ErrorType.SYNTACTIC_ERROR;
-    }).toList();
-    if (syntacticErrors.isNotEmpty) {
-      throw FormatterException(syntacticErrors);
-    }
-
-    AstNode node;
-    if (source.isCompilationUnit) {
-      node = parseResult.unit;
-    } else {
-      var function = parseResult.unit.declarations[0] as FunctionDeclaration;
-      var body = function.functionExpression.body as BlockFunctionBody;
-      node = body.block.statements[0];
-
-      // Make sure we consumed all of the source.
-      var token = node.endToken.next!;
-      if (token.type != TokenType.CLOSE_CURLY_BRACKET) {
-        var stringSource = StringSource(text, source.uri);
-        var error = AnalysisError.tmp(
-            source: stringSource,
-            offset: token.offset - inputOffset,
-            length: math.max(token.length, 1),
-            errorCode: ParserErrorCode.UNEXPECTED_TOKEN,
-            arguments: [token.lexeme]);
-        throw FormatterException([error]);
+      // Sanity check that only whitespace was changed if that's all we expect.
+      if (fixes.isEmpty &&
+          !string_compare.equalIgnoringWhitespace(source.text, output.text)) {
+        throw UnexpectedOutputException(source.text, output.text);
       }
+
+      return output;
+    } finally {
+      Profile.end2('DartFormatter.formatSource()', source.uri);
     }
-
-    // Format it.
-    var lineInfo = parseResult.lineInfo;
-
-    SourceCode output;
-    if (experimentFlags.contains(tallStyleExperimentFlag)) {
-      var visitor = AstNodeVisitor(this, lineInfo, unitSourceCode);
-      output = visitor.run(node);
-    } else {
-      var visitor = SourceVisitor(this, lineInfo, unitSourceCode);
-      output = visitor.run(node);
-    }
-
-    // Sanity check that only whitespace was changed if that's all we expect.
-    if (fixes.isEmpty &&
-        !string_compare.equalIgnoringWhitespace(source.text, output.text)) {
-      throw UnexpectedOutputException(source.text, output.text);
-    }
-
-    return output;
   }
 
   /// Parse [source] from [uri].
diff --git a/lib/src/short/source_visitor.dart b/lib/src/short/source_visitor.dart
index d5a84f8..9beb20b 100644
--- a/lib/src/short/source_visitor.dart
+++ b/lib/src/short/source_visitor.dart
@@ -122,17 +122,23 @@
   SourceCode run(AstNode node) {
     Profile.begin('SourceVisitor create Chunks');
 
+    Profile.begin2('SourceVisitor visit AST', _source.uri);
     visit(node);
 
     // Output trailing comments.
     writePrecedingCommentsAndNewlines(node.endToken.next!);
 
+    Profile.end2('SourceVisitor visit AST', _source.uri);
+
     assert(_constNesting == 0, 'Should have exited all const contexts.');
 
     Profile.end('SourceVisitor create Chunks');
 
     // Finish writing and return the complete result.
-    return builder.end();
+    Profile.begin2('SourceVisitor format', _source.uri);
+    var result = builder.end();
+    Profile.end2('SourceVisitor format', _source.uri);
+    return result;
   }
 
   @override
diff --git a/lib/src/worker.dart b/lib/src/worker.dart
index aef5a59..343ad11 100644
--- a/lib/src/worker.dart
+++ b/lib/src/worker.dart
@@ -87,20 +87,22 @@
 
   static WorkerFormatResponse _processFormatRequest(
       _WorkerFormatRequest request) {
-    var source = SourceCode(request.source, uri: request.filePath);
-
-    var formatter = DartFormatter(
-        indent: request.indent,
-        pageWidth: request.pageWidth,
-        fixes: request.fixes,
-        experimentFlags: request.experimentFlags);
+    Profile.begin2('Worker._processFormatRequest()', request.filePath);
     try {
-      var output = formatter.formatSource(source);
+      var source = SourceCode(request.source, uri: request.filePath);
 
-      // TODO: Temporary code to replace the actual formatting logic with some
-      // other CPU-intensive task. Comment out the above line and uncomment this
-      // block to try it.
-      /*
+      var formatter = DartFormatter(
+          indent: request.indent,
+          pageWidth: request.pageWidth,
+          fixes: request.fixes,
+          experimentFlags: request.experimentFlags);
+      try {
+        var output = formatter.formatSource(source);
+
+        // TODO: Temporary code to replace the actual formatting logic with some
+        // other CPU-intensive task. Comment out the above line and uncomment this
+        // block to try it.
+        /*
       // Do some dumb slow computation.
       int fib(int n) {
         if (n <= 1) return n;
@@ -112,53 +114,47 @@
       var output = source;
       */
 
-      return (
-        path: request.filePath,
-        text: output.text,
-        isChanged: source.text != output.text,
-        selectionStart: output.selectionStart,
-        selectionLength: output.selectionLength
-      );
-    } on FormatterException catch (err) {
-      // TODO: Probably want all error reporting to happen on main isolate.
-      var color = Platform.operatingSystem != 'windows' &&
-          stdioType(stderr) == StdioType.terminal;
+        return (
+          path: request.filePath,
+          text: output.text,
+          isChanged: source.text != output.text,
+          selectionStart: output.selectionStart,
+          selectionLength: output.selectionLength
+        );
+      } on FormatterException catch (err) {
+        // TODO: Probably want all error reporting to happen on main isolate.
+        var color = Platform.operatingSystem != 'windows' &&
+            stdioType(stderr) == StdioType.terminal;
 
-      stderr.writeln(err.message(color: color));
-    } on UnexpectedOutputException catch (err) {
-      // TODO: Probably want all error reporting to happen on main isolate.
-      // TODO: Should show display path.
-      stderr.writeln(
-          '''Hit a bug in the formatter when formatting ${request.filePath}.
+        stderr.writeln(err.message(color: color));
+      } on UnexpectedOutputException catch (err) {
+        // TODO: Probably want all error reporting to happen on main isolate.
+        // TODO: Should show display path.
+        stderr.writeln(
+            '''Hit a bug in the formatter when formatting ${request.filePath}.
 $err
 Please report at github.com/dart-lang/dart_style/issues.''');
-    } catch (err, stack) {
-      // TODO: Probably want all error reporting to happen on main isolate.
-      // TODO: Should show display path.
-      stderr.writeln(
-          '''Hit a bug in the formatter when formatting ${request.filePath}.
+      } catch (err, stack) {
+        // TODO: Probably want all error reporting to happen on main isolate.
+        // TODO: Should show display path.
+        stderr.writeln(
+            '''Hit a bug in the formatter when formatting ${request.filePath}.
 Please report at github.com/dart-lang/dart_style/issues.
 $err
 $stack''');
+      }
+
+      // TODO: Temp.
+      return (
+        path: request.filePath,
+        text: 'ERROR',
+        isChanged: false,
+        selectionStart: null,
+        selectionLength: null
+      );
+    } finally {
+      Profile.end2('Worker._processFormatRequest()', request.filePath);
     }
-
-    // TODO: Temp.
-    return (
-      path: request.filePath,
-      text: 'ERROR',
-      isChanged: false,
-      selectionStart: null,
-      selectionLength: null
-    );
-
-    // } on FormatterException catch (err) {
-    //   // TODO: Color?
-    //   return (_ErrorType.formatter, err.message());
-    // } on UnexpectedOutputException catch (err) {
-    //   return (_ErrorType.unexpectedOutput, err.toString());
-    // } catch (err, stack) {
-    //   return (_ErrorType.other, '$err\n$stack');
-    // }
   }
 
   final int _id;
diff --git a/tool/time.dart b/tool/time.dart
index d6487c0..600917c 100644
--- a/tool/time.dart
+++ b/tool/time.dart
@@ -23,7 +23,11 @@
       var time = int.parse(match[1]!);
       var label = match[2]!;
 
-      var start = starts.remove(label)!;
+      var start = starts.remove(label);
+      if (start == null) {
+        print('No start for "$label"');
+        throw '!';
+      }
       var elapsed = time - start;
 
       var kind = label;
@@ -36,8 +40,21 @@
     }
   }
 
+  var tracked = [
+    // Nested:
+    'format everything',
+    'request and wait for format',
+    'Worker._processFormatRequest()',
+    // 'DartFormatter.formatSource()',
+    // Sequential:
+    // 'parse',
+    // 'SourceVisitor visit AST',
+    // 'SourceVisitor format',
+  ];
+  var trackedTimes = <String, double>{};
+
   var kinds = cumulative.keys.toList();
-  kinds.sort((a, b) => cumulative[a]!.compareTo(cumulative[b]!));
+  kinds.sort();
   for (var kind in kinds) {
     var time = cumulative[kind]!;
     var total = (time / 1000).toStringAsFixed(3).padLeft(12);
@@ -49,5 +66,16 @@
       line += ', $count count ave $each';
     }
     print(line);
+
+    if (tracked.contains(kind)) {
+      trackedTimes[kind] = time / 1000;
+    }
   }
+
+  print(tracked.map((t) => '"$t"').join(','));
+  var trackedTimesSorted = <double>[];
+  for (var kind in tracked) {
+    trackedTimesSorted.add(trackedTimes[kind]!);
+  }
+  print(trackedTimesSorted.map((t) => '"$t"').join(','));
 }