Version 2.1.1-dev.3.2

* Cherry-pick 9d25cc93e850d4717cdc9e1c4bd3623e09c16d47 to dev
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7e0cef2..9db2340 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 2.1.1-dev.3.2
+
+* Cherry-pick 9d25cc93e850d4717cdc9e1c4bd3623e09c16d47 to dev
+
 ## 2.1.1-dev.3.1
 
 * Cherry-pick 46080dd886a622c5520895d49c97506ecedb1df8 to dev
diff --git a/tools/VERSION b/tools/VERSION
index f1ad7f9..5034ac4 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -28,4 +28,4 @@
 MINOR 1
 PATCH 1
 PRERELEASE 3
-PRERELEASE_PATCH 1
+PRERELEASE_PATCH 2
diff --git a/tools/bots/update_flakiness.dart b/tools/bots/update_flakiness.dart
index 514592b..c1572e7 100755
--- a/tools/bots/update_flakiness.dart
+++ b/tools/bots/update_flakiness.dart
@@ -17,6 +17,9 @@
   parser.addFlag('help', help: 'Show the program usage.', negatable: false);
   parser.addOption('input', abbr: 'i', help: "Input flakiness file.");
   parser.addOption('output', abbr: 'o', help: "Output flakiness file.");
+  parser.addOption('build-id', help: "Logdog ID of this buildbot run");
+  parser.addOption('commit', help: "Commit hash of this buildbot run");
+
   final options = parser.parse(args);
   if (options["help"]) {
     print("""
@@ -38,28 +41,45 @@
   // Incrementally update the flakiness data with each observed result.
   for (final path in parameters) {
     final results = await loadResults(path);
-    for (final result in results) {
-      final String configuration = result["configuration"];
-      final String name = result["name"];
+    for (final resultObject in results) {
+      final String configuration = resultObject["configuration"];
+      final String name = resultObject["name"];
+      final String result = resultObject["result"];
       final key = "$configuration:$name";
-      final Map<String, dynamic> testData =
-          data.putIfAbsent(key, () => <String, dynamic>{});
+      newMap() => <String, dynamic>{};
+      final Map<String, dynamic> testData = data.putIfAbsent(key, newMap);
       testData["configuration"] = configuration;
       testData["name"] = name;
       final outcomes = testData.putIfAbsent("outcomes", () => []);
-      if (!outcomes.contains(result["result"])) {
-        outcomes.add(result["result"]);
-        outcomes..sort();
+      final time = DateTime.now().toIso8601String();
+      if (!outcomes.contains(result)) {
+        outcomes
+          ..add(result)
+          ..sort();
+        testData["last_new_result_seen"] = time;
       }
-      if (testData["current"] == result["result"]) {
+      if (testData["current"] == result) {
         testData["current_counter"]++;
       } else {
-        testData["current"] = result["result"];
+        testData["current"] = result;
         testData["current_counter"] = 1;
       }
-      var occurrences = testData.putIfAbsent("occurrences", () => <String, dynamic>{});
-      occurrences.putIfAbsent(result["result"], () => 0);
-      occurrences[result["result"]]++;
+      final occurrences = testData.putIfAbsent("occurrences", newMap);
+      occurrences.putIfAbsent(result, () => 0);
+      occurrences[result]++;
+      final firstSeen = testData.putIfAbsent("first_seen", newMap);
+      firstSeen.putIfAbsent(result, () => time);
+      final lastSeen = testData.putIfAbsent("last_seen", newMap);
+      lastSeen[result] = time;
+
+      if (options["build-id"] != null) {
+        final buildIds = testData.putIfAbsent("build_ids", newMap);
+        buildIds[result] = options["build-id"];
+      }
+      if (options["commit"] != null) {
+        final commits = testData.putIfAbsent("commits", newMap);
+        commits[result] = options["commit"];
+      }
     }
   }
 
@@ -72,16 +92,10 @@
   for (final key in keys) {
     final testData = data[key];
     if (testData["outcomes"].length < 2) continue;
-    // Remove this code once all files are updated
-    const occurrencesMisspelled = "occurences";
-    if (testData.containsKey(occurrencesMisspelled)) continue;
     // Forgive tests that have become deterministic again. If they flake less
     // than once in a 100 (p<1%), then if they flake again, the probability of
     // them getting past 5 runs of deflaking is 1%^5 = 0.00000001%.
-    // TODO(sortie): Transitional compatibility until all flaky.json files have
-    // this new field.
-    if (testData["current_counter"] != null &&
-        100 <= testData["current_counter"]) {
+    if (100 <= testData["current_counter"]) {
       continue;
     }
     sink.writeln(jsonEncode(testData));