[web] Allow benchmarks to customize their score keys (#51493)

diff --git a/dev/benchmarks/macrobenchmarks/lib/src/web/recorder.dart b/dev/benchmarks/macrobenchmarks/lib/src/web/recorder.dart
index d93d89a..4ebcf6d 100644
--- a/dev/benchmarks/macrobenchmarks/lib/src/web/recorder.dart
+++ b/dev/benchmarks/macrobenchmarks/lib/src/web/recorder.dart
@@ -430,6 +430,7 @@
   Map<String, dynamic> toJson() {
     return <String, dynamic>{
       'name': name,
+      'scoreKeys': <String>['averageDrawFrameDuration'],
       'averageDrawFrameDuration': averageDrawFrameDuration.inMicroseconds,
       'drawFrameDurationNoise': drawFrameDurationNoise,
       'frames': frames
diff --git a/dev/devicelab/lib/tasks/web_benchmarks.dart b/dev/devicelab/lib/tasks/web_benchmarks.dart
index ee1da2d..0211fe0 100644
--- a/dev/devicelab/lib/tasks/web_benchmarks.dart
+++ b/dev/devicelab/lib/tasks/web_benchmarks.dart
@@ -124,10 +124,33 @@
       print('Received profile data');
       for (final Map<String, dynamic> profile in profiles) {
         final String benchmarkName = profile['name'] as String;
-        final String benchmarkScoreKey = '$benchmarkName.$backend.averageDrawFrameDuration';
-        taskResult[benchmarkScoreKey] = profile['averageDrawFrameDuration'].toDouble(); // micros
-        taskResult['$benchmarkName.$backend.drawFrameDurationNoise'] = profile['drawFrameDurationNoise'].toDouble(); // micros
-        benchmarkScoreKeys.add(benchmarkScoreKey);
+        if (benchmarkName.isEmpty) {
+          throw 'Benchmark name is empty';
+        }
+
+        final String namespace = '$benchmarkName.$backend';
+        final List<String> scoreKeys = List<String>.from(profile['scoreKeys'] as List<dynamic>);
+        if (scoreKeys == null || scoreKeys.isEmpty) {
+          throw 'No score keys in benchmark "$benchmarkName"';
+        }
+        for (final String scoreKey in scoreKeys) {
+          if (scoreKey == null || scoreKey.isEmpty) {
+            throw 'Score key is empty in benchmark "$benchmarkName". '
+                'Received [${scoreKeys.join(', ')}]';
+          }
+          if (scoreKey.contains('.')) {
+            throw 'Score key contain dots in benchmark "$benchmarkName". '
+                'Received [${scoreKeys.join(', ')}]';
+          }
+          benchmarkScoreKeys.add('$namespace.$scoreKey');
+        }
+
+        for (final String key in profile.keys) {
+          if (key == 'name' || key == 'scoreKeys') {
+            continue;
+          }
+          taskResult['$namespace.$key'] = profile[key];
+        }
       }
       return TaskResult.success(taskResult, benchmarkScoreKeys: benchmarkScoreKeys);
     } finally {