From 9a6cfd653d74ab23d9415e6ce67da22badb8101e Mon Sep 17 00:00:00 2001 From: Kurt Mackey Date: Thu, 6 Jun 2019 21:46:18 -0500 Subject: add tcp proxy benchmarks + split out website section for proxy req/s (#2464) --- tools/deno_tcp_proxy.ts | 29 +++++++++++ tools/http_benchmark.py | 26 +++++++++- tools/node_tcp_proxy.js | 68 +++++++++++++++++++++++++ website/app.js | 31 ++++++++++++ website/benchmarks.html | 130 ++++++++++++++++++++++++++++++++++++------------ 5 files changed, 252 insertions(+), 32 deletions(-) create mode 100644 tools/deno_tcp_proxy.ts create mode 100644 tools/node_tcp_proxy.js diff --git a/tools/deno_tcp_proxy.ts b/tools/deno_tcp_proxy.ts new file mode 100644 index 000000000..96877fa20 --- /dev/null +++ b/tools/deno_tcp_proxy.ts @@ -0,0 +1,29 @@ +// Used for benchmarking Deno's tcp proxy perfromance. See tools/http_benchmark.py +const addr = Deno.args[1] || "127.0.0.1:4500"; +const originAddr = Deno.args[2] || "127.0.0.1:4501"; + +const listener = Deno.listen("tcp", addr); + +async function handle(conn: Deno.Conn): Promise { + const origin = await Deno.dial("tcp", originAddr); + try { + await Promise.all([Deno.copy(conn, origin), Deno.copy(origin, conn)]); + } catch (err) { + if (err.message !== "read error" && err.message !== "write error") { + throw err; + } + } finally { + conn.close(); + origin.close(); + } +} + +async function main(): Promise { + console.log("Listening on", addr); + while (true) { + const conn = await listener.accept(); + handle(conn); + } +} + +main(); diff --git a/tools/http_benchmark.py b/tools/http_benchmark.py index 7348b6b5a..ca451a4e3 100755 --- a/tools/http_benchmark.py +++ b/tools/http_benchmark.py @@ -37,6 +37,18 @@ def deno_http(deno_exe): }) +def deno_tcp_proxy(deno_exe, hyper_hello_exe): + deno_cmd = [ + deno_exe, "run", "--allow-net", "tools/deno_tcp_proxy.ts", ADDR, + ORIGIN_ADDR + ] + print "http_proxy_benchmark testing DENO using net/tcp." + return run( + deno_cmd, + merge_env={"DENO_DIR": os.path.join(util.root_path, "js")}, + origin_cmd=http_proxy_origin(hyper_hello_exe)) + + def deno_http_proxy(deno_exe, hyper_hello_exe): deno_cmd = [ deno_exe, "run", "--allow-net", "tools/deno_http_proxy.ts", ADDR, @@ -71,6 +83,16 @@ def node_http_proxy(hyper_hello_exe): return run(node_cmd, None, http_proxy_origin(hyper_hello_exe)) +def node_tcp_proxy(hyper_hello_exe): + node_cmd = [ + "node", "tools/node_tcp_proxy.js", + ADDR.split(":")[1], + ORIGIN_ADDR.split(":")[1] + ] + print "http_proxy_benchmark testing NODE tcp." + return run(node_cmd, None, http_proxy_origin(hyper_hello_exe)) + + def node_tcp(): node_cmd = ["node", "tools/node_tcp.js", ADDR.split(":")[1]] print "http_benchmark testing node_tcp.js" @@ -97,11 +119,13 @@ def http_benchmark(build_dir): # "deno_http" was once called "deno_net_http" "deno_http": deno_http(deno_exe), "deno_proxy": deno_http_proxy(deno_exe, hyper_hello_exe), + "deno_proxy_tcp": deno_tcp_proxy(deno_exe, hyper_hello_exe), "deno_core_single": deno_core_single(core_http_bench_exe), "deno_core_multi": deno_core_multi(core_http_bench_exe), # "node_http" was once called "node" "node_http": node_http(), "node_proxy": node_http_proxy(hyper_hello_exe), + "node_proxy_tcp": node_tcp_proxy(hyper_hello_exe), "node_tcp": node_tcp(), "hyper": hyper_http(hyper_hello_exe) } @@ -127,7 +151,7 @@ def run(server_cmd, merge_env=None, origin_cmd=None): server = subprocess.Popen(server_cmd, env=env) - time.sleep(5) # wait for server to wake up. TODO racy. + time.sleep(15) # wait for server to wake up. TODO racy. try: cmd = "third_party/wrk/%s/wrk -d %s http://%s/" % (util.platform(), diff --git a/tools/node_tcp_proxy.js b/tools/node_tcp_proxy.js new file mode 100644 index 000000000..7dc1b2612 --- /dev/null +++ b/tools/node_tcp_proxy.js @@ -0,0 +1,68 @@ +const net = require("net"); + +process.on("uncaughtException", function(error) { + console.error(error); +}); + +if (process.argv.length != 4) { + console.log("usage: %s ", process.argv[1]); + process.exit(); +} + +const localport = process.argv[2]; +const remoteport = process.argv[3]; + +const remotehost = "127.0.0.1"; + +const server = net.createServer(function(localsocket) { + const remotesocket = new net.Socket(); + + remotesocket.connect(remoteport, remotehost); + + localsocket.on("data", function(data) { + const flushed = remotesocket.write(data); + if (!flushed) { + localsocket.pause(); + } + }); + + remotesocket.on("data", function(data) { + const flushed = localsocket.write(data); + if (!flushed) { + remotesocket.pause(); + } + }); + + localsocket.on("drain", function() { + remotesocket.resume(); + }); + + remotesocket.on("drain", function() { + localsocket.resume(); + }); + + localsocket.on("close", function() { + remotesocket.end(); + }); + + remotesocket.on("close", function() { + localsocket.end(); + }); + + localsocket.on("error", function() { + localsocket.end(); + }); + + remotesocket.on("error", function() { + remotesocket.end(); + }); +}); + +server.listen(localport); + +console.log( + "redirecting connections from 127.0.0.1:%d to %s:%d", + localport, + remotehost, + remoteport +); diff --git a/website/app.js b/website/app.js index 23618274b..9f16e8e45 100644 --- a/website/app.js +++ b/website/app.js @@ -42,6 +42,10 @@ export function createThroughputColumns(data) { return createColumns(data, "throughput"); } +export function createProxyColumns(data) { + return createColumns(data, "req_per_sec_proxy"); +} + export function createReqPerSecColumns(data) { return createColumns(data, "req_per_sec"); } @@ -197,15 +201,41 @@ export function drawCharts(dataUrl) { return drawChartsFromBenchmarkData(dataUrl); } +const proxyFields = [ + "req_per_sec" + //"max_latency" +]; +function extractProxyFields(data) { + for (const row of data) { + for (const field of proxyFields) { + const d = row[field]; + if (!d) continue; + const name = field + "_proxy"; + const newField = {}; + row[name] = newField; + for (const k of Object.getOwnPropertyNames(d)) { + if (k.includes("_proxy")) { + const v = d[k]; + delete d[k]; + newField[k] = v; + } + } + } + } +} /** * Draws the charts from the benchmark data stored in gh-pages branch. */ export async function drawChartsFromBenchmarkData(dataUrl) { const data = await getJson(dataUrl); + // hack to extract proxy fields from req/s fields + extractProxyFields(data); + const execTimeColumns = createExecTimeColumns(data); const throughputColumns = createThroughputColumns(data); const reqPerSecColumns = createReqPerSecColumns(data); + const proxyColumns = createProxyColumns(data); const maxLatencyColumns = createMaxLatencyColumns(data); const maxMemoryColumns = createMaxMemoryColumns(data); const binarySizeColumns = createBinarySizeColumns(data); @@ -235,6 +265,7 @@ export async function drawChartsFromBenchmarkData(dataUrl) { gen("#exec-time-chart", execTimeColumns, "seconds", logScale); gen("#throughput-chart", throughputColumns, "seconds", logScale); gen("#req-per-sec-chart", reqPerSecColumns, "1000 req/sec", formatReqSec); + gen("#proxy-req-per-sec-chart", proxyColumns, "req/sec"); gen("#max-latency-chart", maxLatencyColumns, "milliseconds", logScale); gen("#max-memory-chart", maxMemoryColumns, "megabytes", formatMB); gen("#binary-size-chart", binarySizeColumns, "megabytes", formatMB); diff --git a/website/benchmarks.html b/website/benchmarks.html index a307fcdad..fa1b140e9 100644 --- a/website/benchmarks.html +++ b/website/benchmarks.html @@ -3,17 +3,18 @@ Deno Benchmarks - +
-
` +
+ `
- +

Deno Continuous Benchmarks

@@ -21,7 +22,10 @@ master branch.

-

Make sure your adblocker is disabled as some can block the chart rendering.

+

+ Make sure your adblocker is disabled as some can block the chart + rendering. +

recent data

all data (takes a moment to load)

@@ -37,41 +41,53 @@
  • deno_tcp + >deno_tcp is a fake http server that doesn't parse HTTP. It is comparable to node_tcp + >node_tcp .
  • deno_http + >deno_http is a web server written in TypeScript. It is comparable to node_http + >node_http .
  • -
  • deno_core_single and deno_core_multi are two versions of - a minimal fake HTTP server. It blindly reads and writes fixed HTTP - packets. It is comparable to deno_tcp and node_tcp. - This is a standalone executable that uses the deno rust crate. The +
  • + deno_core_single and deno_core_multi are two versions of a minimal + fake HTTP server. It blindly reads and writes fixed HTTP packets. It + is comparable to deno_tcp and node_tcp. This is a standalone + executable that uses + the deno rust crate. The code is in http_bench.rs + >http_bench.rs and http_bench.js. single uses tokio::runtime::current_thread - and multi uses tokio::runtime::threadpool. + >http_bench.js. single uses + tokio::runtime::current_thread + and multi uses + tokio::runtime::threadpool.
  • @@ -86,10 +102,60 @@
    +

    + Proxy Req/Sec # +

    + +

    + Tests proxy performance. 10 keep-alive connections do as many + hello-world requests as possible. Bigger is better. +

    + +
      +
    • + deno_proxy_tcp + is a fake tcp proxy server that doesn't parse HTTP. It is comparable + to + node_proxy_tcp + . +
    • + +
    • + deno_proxy + is an HTTP proxy server written in TypeScript. It is comparable to + node_proxy + . +
    • + +
    • + + hyper + + is a Rust HTTP server used as the origin for the proxy tests +
    • +
    + +
    +

    Max Latency #

    - Max latency during the same test used above for requests/second. Smaller is better. + Max latency during the same test used above for requests/second. Smaller + is better.

    @@ -101,8 +167,8 @@ - tests/002_hello.ts - , + tests/002_hello.ts , tests/003_relative_import.ts import { drawCharts } from "./app.js"; window.chartWidth = 800; - const overlay = document.getElementById("spinner-overlay") + const overlay = document.getElementById("spinner-overlay"); - function showSpinner () { + function showSpinner() { overlay.style.display = "block"; } - function hideSpinner () { + function hideSpinner() { overlay.style.display = "none"; } - function updateCharts () { - const u = window.location.hash.match("all") ? "./data.json" : "recent.json"; + function updateCharts() { + const u = window.location.hash.match("all") + ? "./data.json" + : "recent.json"; + + showSpinner(); - showSpinner() - - drawCharts(u).finally(hideSpinner) + drawCharts(u).finally(hideSpinner); } - updateCharts() + updateCharts(); - window.onhashchange = updateCharts + window.onhashchange = updateCharts; -- cgit v1.2.3