[CFE] Add perf event lister tool; more visible warning for benchmarker when scaling is in effect

I do some benchmarking with our benchmarker tool which uses "perf stat"
under the hood.

"perf stat" uses hardware counters, but we have a limited amount of
those. The benchmarker tool asks for 3 event types that will use 3
hardware counters. If there aren't 3 available it will do "scaling" and
only measure what it's asked some percentage of the time so it can still
measure all of the stuff it's asked.

This should be fine. My computer has 4 hardware counters. I think.
Only today I still experienced scaling.
And previously I asked for 4 event types because I had 4 hardware
counters, but then suddenly 1 went missing.

As it turns out another process was using hardware counters.
This CL includes a tool that - at least seeems to - be able to find
other processes using perf events (although it might not be events that
use hardware counters), thus possibly allowing you to kill that or those
process(es). I did, and I now have 4 hardware counters again.

I also made scaling more visible if it happens when using the benchmark
tool. Before it was hidden between other output, not it's also printed
at the end, together with a hint to use the new tool to attempt to fix
it.

Change-Id: Ia51db04a4f25d1d82c3d32b3901cded7d980e7f5
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/352525
Commit-Queue: Jens Johansen <jensj@google.com>
Reviewed-by: Chloe Stefantsova <cstefantsova@google.com>
diff --git a/pkg/front_end/test/spell_checking_list_tests.txt b/pkg/front_end/test/spell_checking_list_tests.txt
index b2adc06..1d56587 100644
--- a/pkg/front_end/test/spell_checking_list_tests.txt
+++ b/pkg/front_end/test/spell_checking_list_tests.txt
@@ -375,6 +375,7 @@
 hackish
 hardcoding
 hardly
+hardware
 hb
 hc
 helpfully
@@ -750,6 +751,7 @@
 subtool
 subtools
 successes
+sudo
 sueta
 suggest
 summarization
@@ -826,6 +828,7 @@
 upload
 upstream
 upward
+useless
 uuid
 val
 verbatim
diff --git a/pkg/front_end/tool/benchmarker.dart b/pkg/front_end/tool/benchmarker.dart
index bc93a33..01af037 100644
--- a/pkg/front_end/tool/benchmarker.dart
+++ b/pkg/front_end/tool/benchmarker.dart
@@ -49,12 +49,14 @@
 
   List<List<Map<String, num>>> runResults = [];
   List<GCInfo> gcInfos = [];
+  Warnings warnings = new Warnings();
   for (String snapshot in snapshots) {
     List<Map<String, num>> snapshotResults = [];
     runResults.add(snapshotResults);
     for (int iteration = 0; iteration < iterations; iteration++) {
-      Map<String, num> benchmarkRun =
-          _benchmark(aotRuntime, core, snapshot, [], arguments);
+      Map<String, num> benchmarkRun = _benchmark(
+          aotRuntime, core, snapshot, [], arguments,
+          warnings: warnings);
       if (checkFileSize != null) {
         File f = new File(checkFileSize);
         if (f.existsSync()) {
@@ -79,6 +81,15 @@
     }
     printGcDiff(gcInfos.first, gcInfos[i]);
   }
+
+  if (warnings.scalingInEffect) {
+    print("Be aware the the above was with scaling in effect.");
+    print("As such the results are likely useless.");
+    print("Possibly some other process is using the hardware counters.");
+    print("Running this tool");
+    print("sudo out/ReleaseX64/dart pkg/front_end/tool/perf_event_tool.dart");
+    print("will attempt to give you such information.");
+  }
 }
 
 void _help() {
@@ -170,7 +181,7 @@
 
 Map<String, num> _benchmark(String aotRuntime, int core, String snapshot,
     List<String> extraVmArguments, List<String> arguments,
-    {bool silent = false}) {
+    {bool silent = false, Warnings? warnings}) {
   if (!silent) stdout.write(".");
   ProcessResult processResult = Process.runSync("perf", [
     "stat",
@@ -239,6 +250,7 @@
       result[caption] = value;
       if (scaling != null) {
         print("WARNING: $caption is scaled at $scaling!");
+        warnings?.scalingInEffect = true;
       }
     }
   }
@@ -369,3 +381,7 @@
 
   GCInfo(this.combinedTime, this.countWhat);
 }
+
+class Warnings {
+  bool scalingInEffect = false;
+}
diff --git a/pkg/front_end/tool/perf_event_tool.dart b/pkg/front_end/tool/perf_event_tool.dart
new file mode 100644
index 0000000..2c7fdce
--- /dev/null
+++ b/pkg/front_end/tool/perf_event_tool.dart
@@ -0,0 +1,74 @@
+// Copyright (c) 2024, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import "dart:io";
+
+const bool debug = false;
+
+/// Tool to find and display any process using perf_events which as a
+/// consequence might use one of the hardware counters, leaving fewer for
+/// `perf stat` stuff.
+/// Likely has to be run as root.
+void main() {
+  try {
+    mainImpl();
+  } catch (e) {
+    print("Got the error '$e'.");
+    print("");
+    print("┌───────────────────────────────────────────────────┐");
+    print("│ Note that this tool likely has to be run as root. │");
+    print("└───────────────────────────────────────────────────┘");
+    print("");
+    rethrow;
+  }
+}
+
+void mainImpl() {
+  if (!Platform.isLinux) {
+    throw "This tool only works in Linux.";
+  }
+  bool foundSomething = false;
+  Directory dir = new Directory("/proc/");
+  for (FileSystemEntity pidDir in dir.listSync(recursive: false)) {
+    if (pidDir is! Directory) continue;
+    Uri pidUri = pidDir.uri;
+    String possiblePid = pidUri.pathSegments[pidUri.pathSegments.length - 2];
+    int? candidatePid = int.tryParse(possiblePid);
+    if (candidatePid == null) continue;
+    Directory fdDir = new Directory.fromUri(pidDir.uri.resolve("fd/"));
+    int count = 0;
+    for (FileSystemEntity entry in fdDir.listSync()) {
+      if (debug) {
+        print("$entry");
+      }
+      if (entry is Link) {
+        String target = entry.targetSync();
+        if (debug) {
+          print(" => target: $target");
+        }
+        if (target.contains("perf_event")) {
+          count++;
+        }
+      }
+    }
+    if (count > 0) {
+      print("Found $count perf event file descriptors for "
+          "process with pid $candidatePid:");
+      runPsForPid(candidatePid);
+      print("");
+      foundSomething = true;
+    }
+  }
+
+  if (!foundSomething) {
+    print("Found no open perf file descriptors.");
+    return;
+  }
+}
+
+void runPsForPid(int pid) {
+  ProcessResult psRun = Process.runSync("ps", ["-p", "$pid", "u"]);
+  stdout.write(psRun.stdout);
+  stderr.write(psRun.stderr);
+}