| // Copyright (c) 2023, 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"; |
| |
| int _perfPid = -1; |
| |
| /// Helper for creating a perf snapshot when running via perf or magic-trace. |
| /// |
| /// The linux `perf` tool (and the magic-trace tool which wraps the perf tool |
| /// and post-processes the result) can record "Intel Processor Trace" which |
| /// can then be used to produce nano-second level profiling. |
| /// |
| /// Notice that this (probably) only works on Linux, and certainly only on |
| /// "newer" Intel processors (the perf man page says "first supported in |
| /// Intel Core M and 5th generation Intel Core processors" - so no older than |
| /// that, and not on AMD processors for instance). |
| /// |
| /// The `perf` tool continuously records into a fixed-size ring-buffer, and only |
| /// "really saves" the data when asked to do a snapshot. |
| /// |
| /// This method asks it to take the snapshot, i.e. you calls it *after* the |
| /// thing one wants to profile has run. |
| /// |
| /// Notice that the actual snapshot is taken a short amount of time after the |
| /// call, likely because if the message propagating. |
| /// |
| /// Also notice that it only records a very short duration. One will then get |
| /// ~nanosecond resolution though. |
| /// |
| /// # Usage example: |
| /// |
| /// Compile an aot snapshot like this: |
| /// |
| /// ``` |
| /// out/ReleaseX64/dart-sdk/bin/dart compile aot-snapshot \ |
| /// <dart script> |
| /// ``` |
| /// |
| /// You might also want the dartaotruntime to include debug symbols: |
| /// |
| /// ``` |
| /// cp out/ReleaseX64/dart_precompiled_runtime_product \ |
| /// out/ReleaseX64/dart-sdk/bin/dartaotruntime |
| /// ``` |
| /// |
| /// Now one can e.g. run via perf with something like this: |
| /// |
| /// ``` |
| /// perf record --event=intel_pt/cyc=1,cyc_thresh=1,mtc_period=0/u \ |
| /// --timestamp --snapshot \ |
| /// out/ReleaseX64/dart-sdk/bin/dartaotruntime \ |
| /// <aot compiled script> <args> |
| /// ``` |
| /// |
| /// One could also use |
| /// `--event=intel_pt/cyc=1,cyc_thresh=1,mtc_period=0,noretcomp=1/u` and |
| /// possibly add something like `-m,256`. Or one could use `--snapshot="e"` to |
| /// automatically take a snapshot when the process dies. |
| /// |
| /// In the code, do an initial call to this method with [onlyInitialize] set to |
| /// true to find the perf pid, then, once the code you want profiled has |
| /// executed call it again (with [onlyInitialize] set to false). |
| /// |
| /// And one can then use something like |
| /// |
| /// ``` |
| /// perf script -F +cpu,-dso --tid=<tid> \ |
| /// --dsos=/path/to/aot/compiled/script.aot --call-ret-trace |
| /// ``` |
| /// |
| /// to inspect the result (though maybe first just via |
| /// `perf script --call-ret-trace` to extract the other parts). |
| /// |
| /// |
| /// Or run with magic-trace via something like: |
| /// |
| /// ``` |
| /// ./magic-trace run -trigger tcsetattr -multi-thread -snapshot-size 1M \ |
| /// -timer-resolution high \ |
| /// out/ReleaseX64/dart-sdk/bin/dartaotruntime -- \ |
| /// <aot compiled script> <args> |
| /// ``` |
| /// (here it's asked to trigger on `tcsetattr` but that part doesn't seem to |
| /// work for whatever reason which is why we now have this). |
| /// |
| /// and visualize the output file via |
| /// https://ui.perfetto.dev/ |
| /// or |
| /// https://magic-trace.org/ |
| /// |
| /// It seems that multiple signals is fine, but the output result will grow |
| /// accordingly and the tools can have a hard time processing files that are |
| /// too big. |
| void linuxAndIntelSpecificPerf({bool onlyInitialize = false}) { |
| if (!Platform.isLinux) { |
| print("Error: Only supports Linux!"); |
| return; |
| } |
| |
| if (_perfPid == -1) { |
| // Currently not looked for yet. First mark that we have looked for it. |
| _perfPid = -2; |
| |
| int? parentPidInt = _getParentPid(pid); |
| if (parentPidInt == null) { |
| print("Error: Couldn't find parent pid"); |
| return; |
| } |
| List<String>? cmdlines = _getPidCommandLine(parentPidInt); |
| if (cmdlines == null) { |
| print("Error: Parent cmdline file didn't exist."); |
| } else if (cmdlines[0] == "perf" || cmdlines[0] == "/usr/bin/perf") { |
| _perfPid = parentPidInt; |
| } else if (cmdlines[0] == "magic-trace" || |
| cmdlines[0].endsWith("/magic-trace")) { |
| // Search for perf process... For now blindly search from the parent pid |
| // and 50 pids forward. |
| pidSearch: |
| for (int i = parentPidInt + 1; i < parentPidInt + 50; i++) { |
| List<String>? cmdlines = _getPidCommandLine(i); |
| if (cmdlines == null) continue; |
| if (cmdlines[0] == "perf" || cmdlines[0] == "/usr/bin/perf") { |
| // Extra check --- the perf tool is started with "-p <$pid>", |
| // so verify that it contains this processes pid for good measure. |
| for (int j = 1; j < cmdlines.length; j++) { |
| if (cmdlines[j] == "$pid") { |
| _perfPid = i; |
| break pidSearch; |
| } |
| } |
| } |
| } |
| if (_perfPid <= 0) { |
| print("Error: Couldn't find perf."); |
| } |
| } else { |
| print("Error: Unknown parent command: ${cmdlines[0]}"); |
| } |
| } |
| if (onlyInitialize || _perfPid <= 0) return; |
| |
| // From the perf man page: "In Snapshot Mode trace data is captured only when |
| // signal SIGUSR2 is received and on exit if the above e option is given". |
| // So we now signal that we want a snapshot by sending the sigusr2 signal. |
| Process.killPid(_perfPid, ProcessSignal.sigusr2); |
| print("Notice: Send snapshot signal to perf."); |
| } |
| |
| int? _getParentPid(int pid) { |
| File f = new File("/proc/${pid}/status"); |
| if (!f.existsSync()) return null; |
| for (String line in f.readAsLinesSync()) { |
| if (line.startsWith("PPid:")) { |
| String parentPid = line.substring("PPid:".length).trim(); |
| return int.parse(parentPid); |
| } |
| } |
| return null; |
| } |
| |
| List<String>? _getPidCommandLine(int pid) { |
| File f = new File("/proc/$pid/cmdline"); |
| if (!f.existsSync()) return null; |
| String cmdline = f.readAsStringSync(); |
| return cmdline.split("\u{0}"); |
| } |