summaryrefslogtreecommitdiff
path: root/tools/flamebench.js
blob: 5d1fc161d5922e15ec2bdf745cf9ff4866f8b5e2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
#!/usr/bin/env -S deno run --unstable --allow-read --allow-run
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
import { join, ROOT_PATH as ROOT } from "./util.js";

const { 0: benchName, 1: benchFilter } = Deno.args;
// Print usage if no bench specified
if (!benchName) {
  console.log("flamebench.js <bench_name> [bench_filter]");
  // Also show available benches
  console.log("\nAvailable benches:");
  const benches = await availableBenches();
  console.log(benches.join("\n"));
  return Deno.exit(1);
}

// List available benches, hoping we don't have any benches called "ls" :D
if (benchName === "ls") {
  const benches = await availableBenches();
  console.log(benches.join("\n"));
  return;
}

// Ensure flamegraph is installed
if (!await binExists("flamegraph")) {
  console.log(
    "flamegraph (https://github.com/flamegraph-rs/flamegraph) not found, please run:",
  );
  console.log();
  console.log("cargo install flamegraph");
  return Deno.exit(1);
}

// Build bench with frame pointers
await bashThrough(
  `RUSTFLAGS='-C force-frame-pointers=y' cargo build --release --bench ${benchName}`,
);

// Get the freshly built bench binary
const benchBin = await latestBenchBin(benchName);

// Run flamegraph
const outputFile = join(ROOT, "flamebench.svg");
await runFlamegraph(benchBin, benchFilter, outputFile);

// Open flamegraph (in your browser / SVG viewer)
if (await binExists("open")) {
  await bashThrough(`open ${outputFile}`);
}

async function availableBenches() {
  // TODO(AaronO): maybe reimplement with fs.walk
  // it's important to prune the walked tree so this is fast (<50ms)
  const prunedDirs = ["third_party", ".git", "target", "docs", "test_util"];
  const pruneExpr = prunedDirs.map((d) => `-path ${ROOT}/${d}`).join(" -o ");
  return (await bashOut(`
  find ${ROOT} -type d \
      \\( ${pruneExpr} \\) \
      -prune -false -o  \
      -path "${ROOT}/*/benches/*" -type f -name "*.rs" \
      | xargs basename | cut -f1 -d.
  `)).split("\n");
}

function latestBenchBin(name) {
  return bashOut(`ls -t "${ROOT}/target/release/deps/${name}"* | head -n 1`);
}

function runFlamegraph(benchBin, benchFilter, outputFile) {
  return bashThrough(
    `sudo -E flamegraph -o ${outputFile} ${benchBin} ${benchFilter ?? ""}`,
    // Set $PROFILING env so benches can improve their flamegraphs
    { env: { "PROFILING": "1" } },
  );
}

async function bashOut(subcmd) {
  const { success, stdout } = await new Deno.Command("bash", {
    args: ["-c", subcmd],
    stdout: "piped",
    stderr: "null",
  }).output();

  // Check for failure
  if (!success) {
    throw new Error("subcmd failed");
  }
  // Gather output
  const output = new TextDecoder().decode(stdout);

  return output.trim();
}

async function bashThrough(subcmd, opts = {}) {
  const { success, code } = await new Deno.Command("bash", {
    ...opts,
    args: ["-c", subcmd],
    stdout: "inherit",
    stderr: "inherit",
  }).output();

  // Exit process on failure
  if (!success) {
    Deno.exit(code);
  }
}

async function binExists(bin) {
  try {
    await bashOut(`which ${bin}`);
    return true;
  } catch (_) {
    return false;
  }
}