deno 1.9.2

Provides the deno executable
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.

use super::Result;
use std::{collections::HashMap, path::Path, process::Command, time::Duration};
pub use test_util::{parse_wrk_output, WrkOutput as HttpBenchmarkResult};

// Some of the benchmarks in this file have been renamed. In case the history
// somehow gets messed up:
//   "node_http" was once called "node"
//   "deno_tcp" was once called "deno"
//   "deno_http" was once called "deno_net_http"

const DURATION: &str = "20s";

pub(crate) fn benchmark(
  target_path: &Path,
) -> Result<HashMap<String, HttpBenchmarkResult>> {
  let deno_exe = test_util::deno_exe_path();
  let deno_exe = deno_exe.to_str().unwrap();

  let hyper_hello_exe = target_path.join("test_server");
  let hyper_hello_exe = hyper_hello_exe.to_str().unwrap();

  let core_http_json_ops_exe = target_path.join("examples/http_bench_json_ops");
  let core_http_json_ops_exe = core_http_json_ops_exe.to_str().unwrap();

  let mut res = HashMap::new();

  // "deno_tcp" was once called "deno"
  res.insert("deno_tcp".to_string(), deno_tcp(deno_exe)?);
  // res.insert("deno_udp".to_string(), deno_udp(deno_exe)?);
  res.insert("deno_http".to_string(), deno_http(deno_exe)?);
  res.insert("deno_http_native".to_string(), deno_http_native(deno_exe)?);
  // TODO(ry) deno_proxy disabled to make fetch() standards compliant.
  // res.insert("deno_proxy".to_string(), deno_http_proxy(deno_exe) hyper_hello_exe))
  res.insert(
    "deno_proxy_tcp".to_string(),
    deno_tcp_proxy(deno_exe, hyper_hello_exe)?,
  );
  // "core_http_json_ops" previously had a "bin op" counterpart called "core_http_bin_ops",
  // which was previously also called "deno_core_http_bench", "deno_core_single"
  res.insert(
    "core_http_json_ops".to_string(),
    core_http_json_ops(core_http_json_ops_exe)?,
  );
  // "node_http" was once called "node"
  res.insert("node_http".to_string(), node_http()?);
  res.insert("node_proxy".to_string(), node_http_proxy(hyper_hello_exe)?);
  res.insert(
    "node_proxy_tcp".to_string(),
    node_tcp_proxy(hyper_hello_exe)?,
  );
  res.insert("node_tcp".to_string(), node_tcp()?);
  res.insert("hyper".to_string(), hyper_http(hyper_hello_exe)?);

  Ok(res)
}

fn run(
  server_cmd: &[&str],
  port: u16,
  env: Option<Vec<(String, String)>>,
  origin_cmd: Option<&[&str]>,
) -> Result<HttpBenchmarkResult> {
  // Wait for port 4544 to become available.
  // TODO Need to use SO_REUSEPORT with tokio::net::TcpListener.
  std::thread::sleep(Duration::from_secs(5));

  let mut origin = None;
  if let Some(cmd) = origin_cmd {
    let mut com = Command::new(cmd[0]);
    com.args(&cmd[1..]);
    if let Some(env) = env.clone() {
      com.envs(env);
    }
    origin = Some(com.spawn()?);
  };

  println!("{}", server_cmd.join(" "));
  let mut server = {
    let mut com = Command::new(server_cmd[0]);
    com.args(&server_cmd[1..]);
    if let Some(env) = env {
      com.envs(env);
    }
    com.spawn()?
  };

  std::thread::sleep(Duration::from_secs(5)); // wait for server to wake up. TODO racy.

  let wrk = test_util::prebuilt_tool_path("wrk");
  assert!(wrk.is_file());

  let wrk_cmd = &[
    wrk.to_str().unwrap(),
    "-d",
    DURATION,
    "--latency",
    &format!("http://127.0.0.1:{}/", port),
  ];
  println!("{}", wrk_cmd.join(" "));
  let output = test_util::run_collect(wrk_cmd, None, None, None, true).0;

  std::thread::sleep(Duration::from_secs(1)); // wait to capture failure. TODO racy.

  println!("{}", output);
  assert!(
    server.try_wait()?.map_or(true, |s| s.success()),
    "server ended with error"
  );

  server.kill()?;
  if let Some(mut origin) = origin {
    origin.kill()?;
  }

  Ok(parse_wrk_output(&output))
}

fn get_port() -> u16 {
  static mut NEXT_PORT: u16 = 4544;

  let port = unsafe { NEXT_PORT };

  unsafe {
    NEXT_PORT += 1;
  }

  port
}

fn server_addr(port: u16) -> String {
  format!("0.0.0.0:{}", port)
}

fn deno_tcp(deno_exe: &str) -> Result<HttpBenchmarkResult> {
  let port = get_port();
  println!("http_benchmark testing DENO tcp.");
  run(
    &[
      deno_exe,
      "run",
      "--allow-net",
      "cli/bench/deno_tcp.ts",
      &server_addr(port),
    ],
    port,
    None,
    None,
  )
}

fn deno_tcp_proxy(
  deno_exe: &str,
  hyper_exe: &str,
) -> Result<HttpBenchmarkResult> {
  let port = get_port();
  let origin_port = get_port();

  println!("http_proxy_benchmark testing DENO using net/tcp.");
  run(
    &[
      deno_exe,
      "run",
      "--allow-net",
      "--reload",
      "--unstable",
      "cli/bench/deno_tcp_proxy.ts",
      &server_addr(port),
      &server_addr(origin_port),
    ],
    port,
    None,
    Some(&[hyper_exe, &origin_port.to_string()]),
  )
}

fn deno_http(deno_exe: &str) -> Result<HttpBenchmarkResult> {
  let port = get_port();
  println!("http_benchmark testing DENO using net/http.");
  run(
    &[
      deno_exe,
      "run",
      "--allow-net",
      "--reload",
      "--unstable",
      "test_util/std/http/bench.ts",
      &server_addr(port),
    ],
    port,
    None,
    None,
  )
}

fn deno_http_native(deno_exe: &str) -> Result<HttpBenchmarkResult> {
  let port = get_port();
  println!("http_benchmark testing DENO using native bindings.");
  run(
    &[
      deno_exe,
      "run",
      "--allow-net",
      "--reload",
      "--unstable",
      "cli/bench/deno_http_native.js",
      &server_addr(port),
    ],
    port,
    None,
    None,
  )
}

#[allow(dead_code)]
fn deno_http_proxy(
  deno_exe: &str,
  hyper_exe: &str,
) -> Result<HttpBenchmarkResult> {
  let port = get_port();
  let origin_port = get_port();

  println!("http_proxy_benchmark testing DENO using net/http.");
  run(
    &[
      deno_exe,
      "run",
      "--allow-net",
      "--reload",
      "--unstable",
      "cli/bench/deno_http_proxy.ts",
      &server_addr(port),
      &server_addr(origin_port),
    ],
    port,
    None,
    Some(&[hyper_exe, &origin_port.to_string()]),
  )
}

fn core_http_json_ops(exe: &str) -> Result<HttpBenchmarkResult> {
  println!("http_benchmark testing CORE http_bench_json_ops");
  run(&[exe], 4544, None, None)
}

fn node_http() -> Result<HttpBenchmarkResult> {
  let port = get_port();
  println!("http_benchmark testing NODE.");
  run(
    &["node", "cli/bench/node_http.js", &port.to_string()],
    port,
    None,
    None,
  )
}

fn node_http_proxy(hyper_exe: &str) -> Result<HttpBenchmarkResult> {
  let port = get_port();
  let origin_port = get_port();
  let origin_port = origin_port.to_string();

  println!("http_proxy_benchmark testing NODE.");
  run(
    &[
      "node",
      "cli/bench/node_http_proxy.js",
      &port.to_string(),
      &origin_port,
    ],
    port,
    None,
    Some(&[hyper_exe, &origin_port]),
  )
}

fn node_tcp_proxy(exe: &str) -> Result<HttpBenchmarkResult> {
  let port = get_port();
  let origin_port = get_port();
  let origin_port = origin_port.to_string();

  println!("http_proxy_benchmark testing NODE tcp.");
  run(
    &[
      "node",
      "cli/bench/node_tcp_proxy.js",
      &port.to_string(),
      &origin_port,
    ],
    port,
    None,
    Some(&[exe, &origin_port]),
  )
}

fn node_tcp() -> Result<HttpBenchmarkResult> {
  let port = get_port();
  println!("http_benchmark testing node_tcp.js");
  run(
    &["node", "cli/bench/node_tcp.js", &port.to_string()],
    port,
    None,
    None,
  )
}

fn hyper_http(exe: &str) -> Result<HttpBenchmarkResult> {
  let port = get_port();
  println!("http_benchmark testing RUST hyper");
  run(&[exe, &port.to_string()], port, None, None)
}