diff --git a/frameworks/typev/Dockerfile b/frameworks/typev/Dockerfile index 2f7cb5c2b..1b1ea558b 100644 --- a/frameworks/typev/Dockerfile +++ b/frameworks/typev/Dockerfile @@ -12,7 +12,7 @@ WORKDIR /app # typev VM — binary + FFI plugins (json, stdcore, stdsocket, ...). Pre-built # (-O3 -march=x86-64-v3, static liburing) and fetched from object storage RUN wget -q -O /tmp/typev.zip \ - https://typev.s3.fr-par.scw.cloud/typev-16-05-2026.zip && \ + https://typev.s3.fr-par.scw.cloud/typev-17-05-2026.zip && \ unzip -q /tmp/typev.zip -d /app && \ rm /tmp/typev.zip diff --git a/frameworks/typev/README.md b/frameworks/typev/README.md index 3f8ae6367..d1c741f5d 100644 --- a/frameworks/typev/README.md +++ b/frameworks/typev/README.md @@ -4,8 +4,8 @@ Type-V is a bytecode-interpreting concurrent VM, using io_uring socket I/O. The benchmark server is written in Type-C (typev's own language) and compiled ahead of time to bytecode that the VM runs. -- **Tier:** tuned -- **Profile:** pipelined +- **Tier:** engine +- **Profiles:** pipelined, baseline, limited-conn, json ## Layout @@ -14,7 +14,7 @@ compiled ahead of time to bytecode that the VM runs. | `Dockerfile` | Downloads the prebuilt typev VM, runs the server | | `meta.json` | Framework metadata | | `bundle/output.tvbc` | The compiled benchmark server | -| `bundle/benchmark-code/` | Benchmark source — Type-C `main.tc` plus the `std.io` / `std.socket` modules it uses | +| `bundle/benchmark-code/` | Benchmark source — a Type-C module: `src/` (server, HTTP parser, response builders) plus the `std.io` / `std.socket` modules it uses | The typev VM itself (binary + FFI plugins) is not vendored here — the Dockerfile fetches it from object storage at build time. Only the compiled benchmark and diff --git a/frameworks/typev/bundle/benchmark-code/src/dataset.tc b/frameworks/typev/bundle/benchmark-code/src/dataset.tc new file mode 100644 index 000000000..a2ce29d17 --- /dev/null +++ b/frameworks/typev/bundle/benchmark-code/src/dataset.tc @@ -0,0 +1,211 @@ +// Dataset loading + the json profile response writer. + +from std.fs import readText +from response import writeBytes, writeInt, digitCount + +// A loaded dataset; prefixes[i] is item i's JSON without its closing '}'. +type Dataset = struct { + prefixes: byte[][], + prices: int[], + quantities: int[], + n: int +} + +// JSON structural literals, byte-encoded once. +let local const HDR_JSON: byte[] = "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: ".bytes() +let local const J_CONN_KA: byte[] = "\r\nConnection: keep-alive\r\n\r\n".bytes() +let local const J_CONN_CL: byte[] = "\r\nConnection: close\r\n\r\n".bytes() +let local const J_OPEN: byte[] = "{\"items\":[".bytes() +let local const J_TOTAL: byte[] = ",\"total\":".bytes() +let local const J_COUNT: byte[] = "],\"count\":".bytes() + +// Decimal integer following `key` in data[start..end); 0 if not found. +local fn fieldInt(data: byte[], start: uint, end: uint, key: byte[]) -> int { + let kl: uint = key.length + if kl == 0u || end < start + kl { + return 0 + } + let limit: uint = end - kl + foreach i: uint in start, limit + 1u { + let hit: bool = true + foreach k: uint in 0u, kl { + if (data[i + k] as int) != (key[k] as int) { + hit = false + break + } + } + if hit { + let p: uint = i + kl + while p < end && (data[p] as int) == 32 { + p = p + 1u + } + let v: int = 0 + while p < end { + let d: int = data[p] as int + if d < 48 || d > 57 { + break + } + v = v * 10 + (d - 48) + p = p + 1u + } + return v + } + } + return 0 +} + +// Strip insignificant (out-of-string) whitespace from data[start..end) and +// return the compacted bytes. +local fn compactItem(data: byte[], start: uint, end: uint) -> byte[] { + let out: byte[] = new byte[](end - start) + let w: uint = 0u + let inStr: bool = false + let escaped: bool = false + foreach p: uint in start, end { + let c: int = data[p] as int + if inStr { + out[w] = data[p] + w = w + 1u + if escaped { + // this byte was escaped by a preceding backslash — consume it + escaped = false + } else if c == 92 { + escaped = true + } else if c == 34 { + inStr = false + } + } else { + if c == 34 { + inStr = true + out[w] = data[p] + w = w + 1u + } else if c != 32 && c != 10 && c != 13 && c != 9 { + out[w] = data[p] + w = w + 1u + } + } + } + out.resize(w) + return out +} + +// Read the dataset file and split the top-level JSON array into its objects. +fn loadDataset(path: string) -> Dataset { + let ds: Dataset = {prefixes: [], prices: [], quantities: [], n: 0} + let (txt, st) = readText(path) + if st < 0 { + return ds + } + let data: byte[] = txt.bytes() + let len: uint = data.length + let priceKey: byte[] = "\"price\":".bytes() + let qtyKey: byte[] = "\"quantity\":".bytes() + + // skip to the opening '[' + let p: uint = 0u + while p < len && (data[p] as int) != 91 { + p = p + 1u + } + p = p + 1u + + let count: int = 0 + while p < len { + // skip whitespace and commas between items + while p < len { + let c: int = data[p] as int + if c == 32 || c == 10 || c == 13 || c == 9 || c == 44 { + p = p + 1u + } else { + break + } + } + if p >= len || (data[p] as int) == 93 { + break + } + // scan to the matching '}', string-aware + let itemStart: uint = p + let depth: int = 0 + let inStr: bool = false + while p < len { + let c: int = data[p] as int + if inStr { + if c == 92 { + p = p + 1u + } else if c == 34 { + inStr = false + } + } else { + if c == 34 { + inStr = true + } else if c == 123 { + depth = depth + 1 + } else if c == 125 { + depth = depth - 1 + } + } + p = p + 1u + if !inStr && depth == 0 { + break + } + } + let itemEnd: uint = p + // prefix = item minus the trailing '}', whitespace-compacted + ds.prefixes.push(compactItem(data, itemStart, itemEnd - 1u)) + ds.prices.push(fieldInt(data, itemStart, itemEnd, priceKey)) + ds.quantities.push(fieldInt(data, itemStart, itemEnd, qtyKey)) + count = count + 1 + } + ds.n = count + return ds +} + +// Loaded once at module init; the container mounts it at /data/dataset.json. +let const DATASET_PATH: string = "/data/dataset.json" +let const DATASET: Dataset = loadDataset(DATASET_PATH) + +// /json/{count}?m=M -> {"items":[...],"count":N}; total = price*quantity*mult. +fn writeJson(dst: byte[], pos: uint, count: int, mult: int, keepAlive: bool) -> uint { + let limit: int = if count > DATASET.n => DATASET.n else count + + // pass 1: body length + let bodyLen: uint = J_OPEN.length + foreach i in 0, limit { + if i > 0 { + bodyLen = bodyLen + 1u + } + bodyLen = bodyLen + DATASET.prefixes[i as uint].length + bodyLen = bodyLen + J_TOTAL.length + bodyLen = bodyLen + digitCount(DATASET.prices[i as uint] * DATASET.quantities[i as uint] * mult) + bodyLen = bodyLen + 1u + } + bodyLen = bodyLen + J_COUNT.length + bodyLen = bodyLen + digitCount(limit) + bodyLen = bodyLen + 1u + + // headers + let p: uint = writeBytes(dst, pos, HDR_JSON) + p = writeInt(dst, p, bodyLen as int) + if keepAlive { + p = writeBytes(dst, p, J_CONN_KA) + } else { + p = writeBytes(dst, p, J_CONN_CL) + } + + // pass 2: body + p = writeBytes(dst, p, J_OPEN) + foreach i in 0, limit { + if i > 0 { + dst[p] = 44 as byte + p = p + 1u + } + p = writeBytes(dst, p, DATASET.prefixes[i as uint]) + p = writeBytes(dst, p, J_TOTAL) + p = writeInt(dst, p, DATASET.prices[i as uint] * DATASET.quantities[i as uint] * mult) + dst[p] = 125 as byte + p = p + 1u + } + p = writeBytes(dst, p, J_COUNT) + p = writeInt(dst, p, limit) + dst[p] = 125 as byte + return p + 1u +} diff --git a/frameworks/typev/bundle/benchmark-code/src/http.tc b/frameworks/typev/bundle/benchmark-code/src/http.tc new file mode 100644 index 000000000..6d9f30415 --- /dev/null +++ b/frameworks/typev/bundle/benchmark-code/src/http.tc @@ -0,0 +1,340 @@ +// HTTP/1.1 request parsing. parseRequest fills a caller-owned Req — created +// once per connection and reused — so parsing allocates nothing per request. + +// A parsed request; `consumed` is its byte length in the buffer. +type Req = struct { + complete: bool, + consumed: uint, + isPipeline: bool, + isBaseline: bool, + isJson: bool, + isPost: bool, + keepAlive: bool, + result: int, + jcount: int, + jmult: int +} + +// Result of decoding a chunked body. +type Chunk = struct { + ok: bool, + value: int, + endPos: uint +} + +// Allocate one Req / Chunk — a handler makes these once and reuses them. +fn newReq() -> Req { + let r: Req = {complete: false, consumed: 0u, isPipeline: false, isBaseline: false, isJson: false, isPost: false, keepAlive: true, result: 0, jcount: 0, jmult: 0} + return r +} + +fn newChunk() -> Chunk { + let c: Chunk = {ok: false, value: 0, endPos: 0u} + return c +} + +// Header-matching patterns, byte-encoded once at module init. +let local const HDR_CONTENT_LENGTH: byte[] = "content-length".bytes() +let local const HDR_TRANSFER_ENCODING: byte[] = "transfer-encoding".bytes() +let local const HDR_CONNECTION: byte[] = "connection".bytes() +let local const VAL_CHUNKED: byte[] = "chunked".bytes() +let local const VAL_CLOSE: byte[] = "close".bytes() + +// Parse a decimal integer from buf[start..end), ignoring non-digit bytes. +local fn parseDigits(buf: byte[], start: uint, end: uint) -> int { + let v: int = 0 + let p: uint = start + while p < end { + let d: int = buf[p] as int + if d >= 48 && d <= 57 { + v = v * 10 + (d - 48) + } + p = p + 1u + } + return v +} + +// Case-insensitive match of `name` against the header name at buf[ls..), `:`-terminated. +local fn matchHeader(buf: byte[], ls: uint, le: uint, name: byte[]) -> bool { + let const nb = name + let nl: uint = nb.length + if ls + nl >= le { + return false + } + foreach k: uint in 0u, nl { + let c: int = buf[ls + k] as int + if c >= 65 && c <= 90 { + c = c + 32 + } + if c != (nb[k] as int) { + return false + } + } + return (buf[ls + nl] as int) == 58 +} + +// Integer value of a header line (the part after ':'). +local fn headerNumber(buf: byte[], ls: uint, le: uint) -> int { + let p: uint = ls + while p < le && (buf[p] as int) != 58 { + p = p + 1u + } + p = p + 1u + return parseDigits(buf, p, le) +} + +// Case-insensitive substring search for `lit` within buf[start..end). +local fn containsCI(buf: byte[], start: uint, end: uint, lit: byte[]) -> bool { + let const lb = lit + let ll: uint = lb.length + if ll == 0u { + return true + } + if end < start + ll { + return false + } + let limit: uint = end - ll + foreach i: uint in start, limit + 1u { + let m: bool = true + foreach k: uint in 0u, ll { + let c: int = buf[i + k] as int + if c >= 65 && c <= 90 { + c = c + 32 + } + if c != (lb[k] as int) { + m = false + break + } + } + if m { + return true + } + } + return false +} + +// Decode a chunked body into `res` (reused); res.ok stays false if incomplete. +local fn decodeChunked(buf: byte[], start: uint, len: uint, mut res: Chunk) -> void { + res.ok = false + res.value = 0 + res.endPos = 0u + let p: uint = start + let acc: int = 0 + while true { + if p >= len { + return + } + let size: int = 0 + let any: bool = false + while p < len { + let c: int = buf[p] as int + let hv: int = -1 + if c >= 48 && c <= 57 { + hv = c - 48 + } else if c >= 97 && c <= 102 { + hv = c - 87 + } else if c >= 65 && c <= 70 { + hv = c - 55 + } + if hv < 0 { + break + } + size = size * 16 + hv + any = true + p = p + 1u + } + if !any { + return + } + if p + 1u >= len { + return + } + if (buf[p] as int) != 13 || (buf[p + 1u] as int) != 10 { + return + } + p = p + 2u + if size == 0 { + if p + 1u >= len { + return + } + res.ok = true + res.value = acc + res.endPos = p + 2u + return + } + let sz: uint = size as uint + if p + sz + 2u > len { + return + } + foreach q: uint in p, p + sz { + let d: int = buf[q] as int + if d >= 48 && d <= 57 { + acc = acc * 10 + (d - 48) + } + } + p = p + sz + 2u + } + return +} + +// Parse one HTTP request from buf[0..len) into the reused `r` (`c` is chunked- +// decode scratch); r.complete is left false if the request is incomplete. +fn parseRequest(buf: byte[], len: uint, mut r: Req, mut c: Chunk) -> void { + r.complete = false + r.consumed = 0u + r.isPipeline = false + r.isBaseline = false + r.isJson = false + r.isPost = false + r.keepAlive = true + r.result = 0 + r.jcount = 0 + r.jmult = 0 + if len < 4u { + return + } + + // end of headers ("\r\n\r\n") + let headerEnd: int = -1 + foreach i: uint in 0u, len - 3u { + if (buf[i] as int) == 13 && (buf[i + 1u] as int) == 10 && (buf[i + 2u] as int) == 13 && (buf[i + 3u] as int) == 10 { + headerEnd = i as int + break + } + } + if headerEnd < 0 { + return + } + let he: uint = headerEnd as uint + let bodyStart: uint = he + 4u + + // first space -> start of the request target ('/...') + let sp: uint = 0u + while sp < he && (buf[sp] as int) != 32 { + sp = sp + 1u + } + let pathStart: uint = sp + 1u + + // method length == first-space position: POST is 4, GET is 3. Stored on + // the struct — a long-lived `let bool` from a comparison miscompiles -O. + r.isPost = sp == 4u + + // route on the character after '/': 'p'(112)=pipeline, 'b'(98)=baseline + let rc: int = if pathStart + 1u < he => (buf[pathStart + 1u] as int) else 0 + r.isPipeline = rc == 112 + r.isBaseline = rc == 98 + r.isJson = rc == 106 + + // end of request line (first CR) + let lineEnd: uint = pathStart + while lineEnd < he && (buf[lineEnd] as int) != 13 { + lineEnd = lineEnd + 1u + } + + // header scan + let contentLen: int = -1 + let chunked: bool = false + let h: uint = lineEnd + 2u + while h < he { + let le: uint = h + while le < he && (buf[le] as int) != 13 { + le = le + 1u + } + if matchHeader(buf, h, le, HDR_CONTENT_LENGTH) { + contentLen = headerNumber(buf, h, le) + } else if matchHeader(buf, h, le, HDR_TRANSFER_ENCODING) { + if containsCI(buf, h, le, VAL_CHUNKED) { + chunked = true + } + } else if matchHeader(buf, h, le, HDR_CONNECTION) { + if containsCI(buf, h, le, VAL_CLOSE) { + r.keepAlive = false + } + } + h = le + 2u + } + + // body + let bodyVal: int = 0 + let consumed: uint = bodyStart + if r.isPost { + if chunked { + decodeChunked(buf, bodyStart, len, c) + if !c.ok { + return + } + bodyVal = c.value + consumed = c.endPos + } else { + let cl: uint = if contentLen > 0 => (contentLen as uint) else 0u + if len < bodyStart + cl { + return + } + bodyVal = parseDigits(buf, bodyStart, bodyStart + cl) + consumed = bodyStart + cl + } + } + + r.complete = true + r.consumed = consumed + + if r.isBaseline { + // sum the first two digit-runs in the query (after '?') + let q: uint = pathStart + while q < lineEnd && (buf[q] as int) != 63 { + q = q + 1u + } + let sum: int = 0 + let cnt: int = 0 + let p: uint = q + while p < lineEnd && cnt < 2 { + let c2: int = buf[p] as int + if c2 >= 48 && c2 <= 57 { + let v: int = 0 + while p < lineEnd { + let d: int = buf[p] as int + if d < 48 || d > 57 { + break + } + v = v * 10 + (d - 48) + p = p + 1u + } + sum = sum + v + cnt = cnt + 1 + } else { + p = p + 1u + } + } + r.result = sum + bodyVal + } + + if r.isJson { + // /json/{count}?m=M — count and m are the first two digit-runs + let cnt: int = 0 + let p: uint = pathStart + while p < lineEnd && cnt < 2 { + let c3: int = buf[p] as int + if c3 >= 48 && c3 <= 57 { + let v: int = 0 + while p < lineEnd { + let d: int = buf[p] as int + if d < 48 || d > 57 { + break + } + v = v * 10 + (d - 48) + p = p + 1u + } + if cnt == 0 { + r.jcount = v + } else { + r.jmult = v + } + cnt = cnt + 1 + } else { + p = p + 1u + } + } + } + + return +} diff --git a/frameworks/typev/bundle/benchmark-code/src/main.tc b/frameworks/typev/bundle/benchmark-code/src/main.tc index f00a9c0d4..d36587c72 100644 --- a/frameworks/typev/bundle/benchmark-code/src/main.tc +++ b/frameworks/typev/bundle/benchmark-code/src/main.tc @@ -1,37 +1,188 @@ -from std.io import println -from std.socket import tcp_listen, tcp_accept, tcp_read, tcp_write, tcp_close, count_http_terminators - -let const RESPONSE: string = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 2\r\nConnection: keep-alive\r\n\r\nok" +// typev HttpArena server — HTTP/1.1: pipelined, baseline, limited-conn, json. +// +// Each connection runs in its own process. /pipeline takes a zero-parse, +// zero-alloc fast path (count fixed-length requests, blast canned responses +// from a prebuilt batch); other routes use the http.tc parser with a +// per-connection Req/Chunk reused so parsing also allocates nothing. -let const MAX_DEPTH: uint = 256u +from std.io import println +from std.socket import tcp_listen, tcp_accept, tcp_read, tcp_write, tcp_close +from http import parseRequest, newReq, newChunk +from response import writePipeline, writeBaseline, PIPE_BATCH, PIPE_BATCH_N, RESP_LEN +from dataset import writeJson + +// Per-connection receive / output buffer capacities. +let const BUFCAP: uint = 65536u +let const OBUFCAP: uint = 65536u + +let const PORT: uint = 8080u + +// Write all `len` bytes of buf to fd, looping over partial sends. tcp_write +// has no offset arg, so a short send is followed by shifting the unsent tail +// to the front of buf and retrying. false on socket error. +fn writeAll(fd: int, buf: byte[], len: uint) -> bool { + let remaining: uint = len + while remaining > 0u { + let w = tcp_write(fd, buf, remaining) + if w <= 0 { + return false + } + let n: uint = w as uint + if n >= remaining { + return true + } + let rest: uint = remaining - n + foreach k: uint in 0u, rest { + buf[k] = buf[n + k] + } + remaining = rest + } + return true +} -let const READ_BUF: uint = 262144u +// Send `total` bytes straight out of the shared prebuilt PIPE_BATCH. A partial +// send is finished via `scratch` — PIPE_BATCH is a const and must not shift. +fn blastPipe(fd: int, scratch: byte[], total: uint) -> bool { + let w = tcp_write(fd, PIPE_BATCH, total) + if w < 0 { + return false + } + let sent: uint = w as uint + if sent >= total { + return true + } + let rest: uint = total - sent + foreach k: uint in 0u, rest { + scratch[k] = PIPE_BATCH[sent + k] + } + return writeAll(fd, scratch, rest) +} -fn handle_client(client_fd: int) -> void { +// Serve one connection. +fn handle_client(fd: int) -> void { let buf: byte[] = [] - buf.resize(READ_BUF) - - let resp_len: uint = RESPONSE.bytes().length - let batch: byte[] = RESPONSE.repeat(MAX_DEPTH).bytes() - - while true { - let n = tcp_read(client_fd, buf, READ_BUF) - if n <= 0 { break } - - let count = count_http_terminators(buf, n as uint) - let reply: uint = if count == 0u => 1u else if count > MAX_DEPTH => MAX_DEPTH else count - - let written = tcp_write(client_fd, batch, reply * resp_len) - if written < 0 { break } + buf.resize(BUFCAP) + let tmp: byte[] = [] + tmp.resize(BUFCAP) + let obuf: byte[] = [] + obuf.resize(OBUFCAP) + // Parse scratch — created once, reused for every request. + let req = newReq() + let chunk = newChunk() + let len: uint = 0u + let running: bool = true + // Pipeline fast path: armed on the first /pipeline request; reqLen is its + // byte length, and every later /pipeline request is byte-identical. + let pipeMode: bool = false + let reqLen: uint = 0u + + while running { + if pipeMode { + // FAST PATH — drain the socket, count whole requests, blast canned + // responses. `len` carries a request split across the recv edge. + let got: int = tcp_read(fd, buf, BUFCAP) + if got <= 0 { + running = false + } else { + let total: uint = len + (got as uint) + let count: uint = total / reqLen + len = total - count * reqLen + let remaining: uint = count + while remaining > 0u { + let n: uint = if remaining > PIPE_BATCH_N => PIPE_BATCH_N else remaining + if !blastPipe(fd, obuf, n * RESP_LEN) { + running = false + remaining = 0u + } else { + remaining = remaining - n + } + } + } + } else { + // GENERAL PATH — incremental parse for baseline / json / + // limited-conn, and a /pipeline connection's first batch. + let got: int = 0 + if len == 0u { + got = tcp_read(fd, buf, BUFCAP) + } else { + // leftover present — read into scratch, then append + got = tcp_read(fd, tmp, BUFCAP - len) + if got > 0 { + let g: uint = got as uint + foreach k: uint in 0u, g { + buf[len + k] = tmp[k] + } + } + } + + if got <= 0 { + running = false + } else { + len = len + (got as uint) + let opos: uint = 0u + let closeAfter: bool = false + let parsing: bool = true + while parsing { + parseRequest(buf, len, req, chunk) + if !req.complete { + parsing = false + } else { + if opos + 32768u > OBUFCAP { + if !writeAll(fd, obuf, opos) { + running = false + } + opos = 0u + } + if req.isJson { + opos = writeJson(obuf, opos, req.jcount, req.jmult, req.keepAlive) + } else if req.isBaseline { + opos = writeBaseline(obuf, opos, req.result, req.keepAlive) + } else { + opos = writePipeline(obuf, opos, req.keepAlive) + } + // arm the fast path on the first /pipeline request + if !pipeMode && req.isPipeline && req.consumed > 0u { + pipeMode = true + reqLen = req.consumed + } + if !req.keepAlive { + closeAfter = true + } + // drop the consumed request, shift the leftover down + let rem: uint = len - req.consumed + foreach k: uint in 0u, rem { + buf[k] = buf[req.consumed + k] + } + len = rem + if closeAfter { + parsing = false + } + } + } + if opos > 0u { + if !writeAll(fd, obuf, opos) { + running = false + } + } + if closeAfter { + running = false + } + if len >= BUFCAP { + running = false + } + } + } } - tcp_close(client_fd) + tcp_close(fd) } -fn accept_loop(port: uint) -> void { - let server = tcp_listen(port, 4096) - if server < 0 { return } - +// Accept loop — one of N SO_REUSEPORT shards. +fn accept_loop() -> void { + let server = tcp_listen(PORT, 4096) + if server < 0 { + return + } while true { let client = tcp_accept(server) if client >= 0 { @@ -41,15 +192,13 @@ fn accept_loop(port: uint) -> void { } fn main() -> uint { - let port: uint = 8080u - - println("typev HttpArena server listening on :8080 (4 SO_REUSEPORT shards)") + println("typev HttpArena server listening on :8080") foreach i in 0, 3, 1 { - spawn accept_loop(port) + spawn accept_loop() } - let server = tcp_listen(port, 4096) + let server = tcp_listen(PORT, 4096) if server < 0 { println("Failed to start server") return 1u diff --git a/frameworks/typev/bundle/benchmark-code/src/response.tc b/frameworks/typev/bundle/benchmark-code/src/response.tc new file mode 100644 index 000000000..19a9601f7 --- /dev/null +++ b/frameworks/typev/bundle/benchmark-code/src/response.tc @@ -0,0 +1,76 @@ +// HTTP/1.1 response writers — append bytes into a caller-supplied buffer. + +// Fixed response chunks, byte-encoded once. +let local const PIPE_STR: string = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 2\r\nConnection: keep-alive\r\n\r\nok" +let local const PIPE_KA: byte[] = PIPE_STR.bytes() +let local const PIPE_CLOSE: byte[] = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 2\r\nConnection: close\r\n\r\nok".bytes() + +// Pipeline fast path: PIPE_BATCH_N canned responses concatenated, prebuilt once. +let const RESP_LEN: uint = PIPE_KA.length +let const PIPE_BATCH_N: uint = 512u +let const PIPE_BATCH: byte[] = PIPE_STR.repeat(PIPE_BATCH_N).bytes() +let local const HDR_PLAIN: byte[] = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: ".bytes() +let local const CONN_KA: byte[] = "\r\nConnection: keep-alive\r\n\r\n".bytes() +let local const CONN_CLOSE: byte[] = "\r\nConnection: close\r\n\r\n".bytes() + +// Copy src into dst at pos; return the position after. The copy is a single +// native blit (copyFrom); dst is grown first if it can't hold the span. +fn writeBytes(dst: byte[], pos: uint, src: byte[]) -> uint { + let need: uint = pos + src.length + if need > dst.length { + dst.resize(need) + } + dst.copyFrom(pos, src) + return need +} + +// Decimal digit count of a non-negative integer. +fn digitCount(n: int) -> uint { + if n <= 0 { + return 1u + } + let c: uint = 0u + let v: int = n + while v > 0 { + c = c + 1u + v = v / 10 + } + return c +} + +// Write n as decimal ASCII into dst at pos; return the position after. +fn writeInt(dst: byte[], pos: uint, n: int) -> uint { + if n <= 0 { + dst[pos] = 48 as byte + return pos + 1u + } + let end: uint = pos + digitCount(n) + let v: int = n + let p: uint = end + while v > 0 { + p = p - 1u + dst[p] = ((v % 10) + 48) as byte + v = v / 10 + } + return end +} + +// /pipeline -> fixed "ok" response. +fn writePipeline(dst: byte[], pos: uint, keepAlive: bool) -> uint { + if keepAlive { + return writeBytes(dst, pos, PIPE_KA) + } + return writeBytes(dst, pos, PIPE_CLOSE) +} + +// /baseline11 -> text/plain body = the decimal of `result`. +fn writeBaseline(dst: byte[], pos: uint, result: int, keepAlive: bool) -> uint { + let p: uint = writeBytes(dst, pos, HDR_PLAIN) + p = writeInt(dst, p, digitCount(result) as int) + if keepAlive { + p = writeBytes(dst, p, CONN_KA) + } else { + p = writeBytes(dst, p, CONN_CLOSE) + } + return writeInt(dst, p, result) +} diff --git a/frameworks/typev/bundle/benchmark-code/stdlib/fs.tc b/frameworks/typev/bundle/benchmark-code/stdlib/fs.tc new file mode 100755 index 000000000..ad17b9938 --- /dev/null +++ b/frameworks/typev/bundle/benchmark-code/stdlib/fs.tc @@ -0,0 +1,509 @@ +/** + * Filesystem API — file I/O, metadata, and path utilities. + * + * Usage: + * from std.fs import File, readText, writeText, fs + * + * High-level functions (readText, writeText, readBytes, writeBytes, appendText) + * handle open/read/write/close in one call with error returns. + * + * The File class provides cursor-based sequential I/O for more control. + * + * The fs namespace wraps low-level metadata operations (exists, mkdir, etc.). + */ + +from std.internal.file import file +from std.internal.fs import fs as fs_api +from std.internal.io import io + +// ============================================================ +// File open flags (portable — same on all platforms) +// Combine with bitwise OR: WRONLY | CREATE | TRUNCATE +// ============================================================ + +let const RDONLY: int = 0x00 +let const WRONLY: int = 0x01 +let const RDWR: int = 0x02 +let const CREATE: int = 0x10 +let const TRUNCATE: int = 0x20 +let const APPEND: int = 0x40 +let const EXCL: int = 0x80 + +// ============================================================ +// High-level convenience functions +// ============================================================ + +/** + * Read an entire file as a UTF-8 string. + * @param path - file path + * Returns (content, 0) on success, ("", negative errno) on error. + */ +fn readText(path: string) -> (string, int) { + let fd = file.file_open(path, RDONLY, 0) + if fd < 0 { + return ("", fd) + } + let (size, stat_err) = file.file_stat(fd) + if stat_err != 0 { + file.file_close(fd) + return ("", stat_err) + } + + if size == 0 { + file.file_close(fd) + return ("", 0) + } + + let buf: uint[] = new uint[](size) + + let n = file.file_read(fd, buf, size, 0) + file.file_close(fd) + + if n < 0 { + return ("", n) + } + + let data = io.string_from_bytes(buf) + + return (data, 0) +} + +/** + * Write a UTF-8 string to a file (creates or truncates). + * @param path - file path + * @param content - string to write + * Returns 0 on success, negative errno on error. + */ +fn writeText(path: string, content: string) -> int { + let fd = file.file_open(path, WRONLY | CREATE | TRUNCATE, 0o644) + if fd < 0 { + return fd + } + + let data = content.bytes() + let n = file.file_write(fd, data, content.bytesLength(), 0) + file.file_close(fd) + + if n < 0 { + return n + } + + return 0 +} + +/** + * Append a UTF-8 string to a file (creates if needed). + * @param path - file path + * @param content - string to append + * Returns 0 on success, negative errno on error. + */ +fn appendText(path: string, content: string) -> int { + let fd = file.file_open(path, WRONLY | CREATE | APPEND, 0o644) + if fd < 0 { + return fd + } + + let (size, stat_err) = file.file_stat(fd) + let offset: uint = 0 + if stat_err == 0 && size > 0 { + offset = size + } + + let data = content.bytes() + let n = file.file_write(fd, data, content.bytesLength(), offset) + file.file_close(fd) + + if n < 0 { + return n + } + + return 0 +} + +/** + * Read an entire file as a byte array. + * @param path - file path + * Returns (data, 0) on success, ([], negative errno) on error. + */ +fn readBytes(path: string) -> (uint[], int) { + let fd = file.file_open(path, RDONLY, 0) + if fd < 0 { + return ([], fd) + } + + let (size, stat_err) = file.file_stat(fd) + if stat_err != 0 { + file.file_close(fd) + return ([], stat_err) + } + + if size == 0 { + file.file_close(fd) + return ([], 0) + } + + let buf: uint[] = [] + buf.resize(size) + + let n = file.file_read(fd, buf, size, 0) + file.file_close(fd) + + if n < 0 { + return ([], n) + } + + return (buf, 0) +} + +/** + * Write a byte array to a file (creates or truncates). + * @param path - file path + * @param data - bytes to write + * Returns 0 on success, negative errno on error. + */ +fn writeBytes(path: string, data: uint[]) -> int { + let fd = file.file_open(path, WRONLY | CREATE | TRUNCATE, 0o644) + if fd < 0 { + return fd + } + + let n = file.file_write(fd, data, data.length, 0) + file.file_close(fd) + + if n < 0 { + return n + } + + return 0 +} + +// ============================================================ +// File class — cursor-based sequential I/O +// ============================================================ + +/** + * A file handle with an internal cursor for sequential read/write. + * + * The cursor advances automatically after each read or write. + * Use seek() to reposition. Use the raw fd for direct pread/pwrite access. + * + * Example: + * let f = File.open("data.txt", RDONLY) + * let (line, n) = f.read(1024) + * f.close() + */ +type File = class { + let const fd: int + let cursor: uint + + /** + * Open a file. + * @param path - file path + * @param flags - open flags (RDONLY, WRONLY|CREATE|TRUNCATE, etc.) + * @param mode - Unix permission bits (default 0o644) + * Returns a File, or throws on error. + */ + fn init(path: string, flags: int, mode: int) { + let fd_ = file.file_open(path, flags, mode) + if fd_ < 0 { + throw "Failed to open file: " + path + } + this.{fd: fd_, cursor: 0} + } + + fn init(path: string, flags: int) { + let fd_ = file.file_open(path, flags, 0o644) + if fd_ < 0 { + throw "Failed to open file: " + path + } + this.{fd: fd_, cursor: 0} + } + + /** + * Read up to len bytes from the current cursor position. + * Advances the cursor by the number of bytes read. + * @param buf - buffer to read into + * @param len - max bytes to read + * Returns bytes read (0 = EOF, < 0 = error). + */ + fn read(buf: uint[], len: uint) -> int { + let n = file.file_read(this.fd, buf, len, this.cursor) + if n > 0 { + this.cursor = this.cursor + n as uint + } + return n + } + + /** + * Write bytes at the current cursor position. + * Advances the cursor by the number of bytes written. + * @param buf - buffer to write from + * @param len - bytes to write + * Returns bytes written, or < 0 on error. + */ + fn write(buf: uint[], len: uint) -> int { + let n = file.file_write(this.fd, buf, len, this.cursor) + if n > 0 { + this.cursor = this.cursor + n as uint + } + return n + } + + /** + * Write a string at the current cursor position. + * Advances the cursor by the number of bytes written. + */ + fn writeStr(s: string) -> int { + let data = s.bytes() + return this.write(data, s.bytesLength()) + } + + /** + * Seek to an absolute byte offset. + */ + fn seek(offset: uint) -> void { + this.cursor = offset + } + + /** + * Get the current cursor position. + */ + fn tell() -> uint { + return this.cursor + } + + /** + * Get the file size in bytes. + * Returns (size, error_code). On error: size=0, error_code=negative errno. + */ + fn size() -> (uint, int) { + return file.file_stat(this.fd) + } + + /** + * Flush file data to disk (fsync). + * Returns 0 on success, negative errno on error. + */ + fn fsync() -> int { + return file.file_fsync(this.fd) + } + + /** + * Close the file. + * Returns 0 on success, negative errno on error. + */ + fn close() -> int { + return file.file_close(this.fd) + } +} + +// ============================================================ +// Filesystem metadata — thin wrappers over internal/fs.tc +// ============================================================ + +namespace fs { + /** + * Check if a path exists (file or directory). + */ + fn exists(path: string) -> bool { + return fs_api.fs_exists(path) == 1 + } + + /** + * Check if a path is a directory. + */ + fn isDir(path: string) -> bool { + return fs_api.fs_is_dir(path) == 1 + } + + /** + * Check if a path is a regular file. + */ + fn isFile(path: string) -> bool { + return fs_api.fs_is_file(path) == 1 + } + + /** + * Delete a file. + * Returns 0 on success, negative errno on error. + */ + fn remove(path: string) -> int { + return fs_api.fs_unlink(path) + } + + /** + * Create a directory with default permissions (0o755). + * Returns 0 on success, negative errno on error. + */ + fn mkdir(path: string) -> int { + return fs_api.fs_mkdir(path, 0o755) + } + + /** + * Create a directory with specific permissions. + * Returns 0 on success, negative errno on error. + */ + fn mkdir(path: string, mode: int) -> int { + return fs_api.fs_mkdir(path, mode) + } + + /** + * Remove an empty directory. + * Returns 0 on success, negative errno on error. + */ + fn rmdir(path: string) -> int { + return fs_api.fs_rmdir(path) + } + + /** + * Rename or move a file/directory. + * Returns 0 on success, negative errno on error. + */ + fn rename(src: string, dest: string) -> int { + return fs_api.fs_rename(src, dest) + } + + /** + * Copy a file. Preserves permissions on Unix. + * Returns 0 on success, negative errno on error. + */ + fn copy(src: string, dest: string) -> int { + return fs_api.fs_copy(src, dest) + } + + /** + * List directory contents (excludes "." and ".."). + * When recursive is true, returns all entries under the directory tree + * with paths relative to the given path. + * Returns entries on success, empty array on error. + */ + fn listDir(path: string, recursive: bool = false) -> string[] { + let (entries, count) = fs_api.fs_listdir(path) + if count < 0 || !recursive { + return entries + } + + let result: string[] = [] + + foreach i in 0, count, 1 { + let entry = entries[i] + let fullPath = fs_api.fs_path_join(path, entry) + result = [...result, entry] + + if fs_api.fs_is_dir(fullPath) == 1 { + let subEntries = listDir(fullPath, true) + foreach j in 0, subEntries.length, 1 { + result = [...result, fs_api.fs_path_join(entry, subEntries[j])] + } + } + } + + return result + } + + /** + * Get file metadata. + * Returns (size_bytes, mode_bits, mtime_seconds). + * On error: size=-1. + */ + fn stat(path: string) -> (int, int, int) { + return fs_api.fs_stat_path(path) + } + + /** + * Create a symbolic link. + * @param target - what the link points to + * @param linkpath - where to create the link + * Returns 0 on success, negative errno on error. + */ + fn symlink(target: string, linkpath: string) -> int { + return fs_api.fs_symlink(target, linkpath) + } + + /** + * Read the target of a symbolic link. + * Returns the target path, or empty string on error. + */ + fn readlink(path: string) -> string { + return fs_api.fs_readlink(path) + } + + /** + * Resolve a path to its absolute, canonical form. + * Returns the resolved path, or empty string on error. + */ + fn realpath(path: string) -> string { + return fs_api.fs_realpath(path) + } + + /** + * Change file permissions. + * Returns 0 on success, negative errno on error. + */ + fn chmod(path: string, mode: int) -> int { + return fs_api.fs_chmod(path, mode) + } + + /** + * Get the current working directory. + * Returns the path, or empty string on error. + */ + fn cwd() -> string { + return fs_api.fs_getcwd() + } + + /** + * Change the current working directory. + * Returns 0 on success, negative errno on error. + */ + fn chdir(path: string) -> int { + return fs_api.fs_chdir(path) + } + + /** + * Get the platform path separator ('/' on Unix, '\\' on Windows). + */ + fn separator() -> uint { + return fs_api.fs_get_separator() + } + + /** + * Join two path components, normalizing separators and resolving . and .. + * Example: join("/usr", "./local/../bin") → "/usr/bin" + */ + fn join(a: string, b: string) -> string { + return fs_api.fs_path_join(a, b) + } + + /** + * Normalize a path: resolve . and .. segments, collapse double separators. + * Example: normalize("/usr/./local/../bin/") → "/usr/bin" + */ + fn normalize(path: string) -> string { + return fs_api.fs_path_normalize(path) + } + + /** + * Get the directory portion of a path. + * Example: dirname("/usr/bin/ls") → "/usr/bin" + */ + fn dirname(path: string) -> string { + return fs_api.fs_path_dirname(path) + } + + /** + * Get the filename portion of a path (last segment). + * Example: basename("/usr/bin/ls") → "ls" + */ + fn basename(path: string) -> string { + return fs_api.fs_path_basename(path) + } + + /** + * Get the file extension including the dot, or empty string if none. + * Example: extension("main.tc") → ".tc" + */ + fn extension(path: string) -> string { + return fs_api.fs_path_extension(path) + } +} diff --git a/frameworks/typev/bundle/benchmark-code/stdlib/internal/file.tc b/frameworks/typev/bundle/benchmark-code/stdlib/internal/file.tc new file mode 100644 index 000000000..1539b7852 --- /dev/null +++ b/frameworks/typev/bundle/benchmark-code/stdlib/internal/file.tc @@ -0,0 +1,71 @@ +/** + * Low-level async file I/O API. + * + * Uses fd-based pread/pwrite with explicit offsets. + * All read/write/fsync operations are async — the process suspends + * until the operation completes (other processes keep running). + * + * Portable open flags (combine with bitwise OR): + * RDONLY=0x00, WRONLY=0x01, RDWR=0x02, + * CREATE=0x10, TRUNCATE=0x20, APPEND=0x40, EXCL=0x80 + * + * Return conventions: + * - File descriptors: >= 0 on success, negative errno on error + * - Byte counts: > 0 on success, 0 = EOF, negative errno on error + * - Status codes: 0 on success, negative errno on error + */ +extern file from "stdfile" = { + + /** + * Open a file. + * @param path - file path + * @param flags - portable open flags (TV_FILE_* constants, combine with |) + * @param mode - Unix permission bits (e.g. 0o644), ignored on Windows + * Returns the file descriptor, or negative errno on error. + */ + fn file_open(path: string, flags: int, mode: int) -> int + + /** + * Read from a file at a specific offset (pread). + * Does not advance any internal cursor — the offset is explicit. + * @param fd - file descriptor + * @param buf - buffer to read into + * @param len - maximum bytes to read (clamped to buf.length) + * @param offset - byte offset in the file to read from + * Returns bytes read, 0 on EOF, or negative errno on error. + */ + fn file_read(fd: int, buf: uint[], len: uint, offset: uint) -> int + + /** + * Write to a file at a specific offset (pwrite). + * Does not advance any internal cursor — the offset is explicit. + * @param fd - file descriptor + * @param buf - buffer to write from + * @param len - bytes to write (clamped to buf.length) + * @param offset - byte offset in the file to write at + * Returns bytes written, or negative errno on error. + */ + fn file_write(fd: int, buf: uint[], len: uint, offset: uint) -> int + + /** + * Close a file descriptor. + * @param fd - file descriptor + * Returns 0 on success, negative errno on error. + */ + fn file_close(fd: int) -> int + + /** + * Flush file data to disk (fsync). + * @param fd - file descriptor + * Returns 0 on success, negative errno on error. + */ + fn file_fsync(fd: int) -> int + + /** + * Get the size of an open file (fstat). + * @param fd - file descriptor + * Returns (size_in_bytes, error_code). + * On success: error_code = 0. On error: size = 0, error_code = negative errno. + */ + fn file_stat(fd: int) -> (uint, int) +} diff --git a/frameworks/typev/bundle/benchmark-code/stdlib/internal/fs.tc b/frameworks/typev/bundle/benchmark-code/stdlib/internal/fs.tc new file mode 100755 index 000000000..10ecb6681 --- /dev/null +++ b/frameworks/typev/bundle/benchmark-code/stdlib/internal/fs.tc @@ -0,0 +1,149 @@ +/** + * Low-level filesystem metadata API. + * + * Path-based operations — no file handles, no async IO. + * For file read/write, use the stdfile API (file.tc). + * + * Return conventions: + * - Status codes: 0 on success, negative errno on error + * - Boolean queries: 1=yes, 0=no + * - String returns: empty string on error + */ +extern fs from "stdfs" = { + + /** + * Check if a path exists (file or directory). + * Returns 1 if exists, 0 if not. + */ + fn fs_exists(path: string) -> int + + /** + * Delete a file (not a directory — use fs_rmdir for that). + * Returns 0 on success, negative errno on error. + */ + fn fs_unlink(path: string) -> int + + /** + * Create a directory. + * @param path - directory path + * @param mode - Unix permission bits (e.g. 0o755), ignored on Windows + * Returns 0 on success, negative errno on error. + */ + fn fs_mkdir(path: string, mode: int) -> int + + /** + * Remove an empty directory. + * Returns 0 on success, negative errno on error (e.g. -ENOTEMPTY). + */ + fn fs_rmdir(path: string) -> int + + /** + * Rename/move a file or directory. + * Returns 0 on success, negative errno on error. + */ + fn fs_rename(src: string, dest: string) -> int + + /** + * Get file metadata by path. + * Returns (size_bytes, mode_bits, mtime_seconds). + * On error: size=-1, mode=0, mtime=0. + */ + fn fs_stat_path(path: string) -> (int, int, int) + + /** + * List directory contents (excludes "." and ".."). + * Returns (entries, count). + * On error: entries=empty array, count=negative errno. + */ + fn fs_listdir(path: string) -> (string[], int) + + /** + * Create a symbolic link. + * @param target - what the link points to + * @param linkpath - where to create the link + * Returns 0 on success, negative errno on error. + * Returns -ENOTSUP on Windows. + */ + fn fs_symlink(target: string, linkpath: string) -> int + + /** + * Read the target of a symbolic link. + * Returns the target path, or empty string on error. + */ + fn fs_readlink(path: string) -> string + + /** + * Resolve a path to its absolute, canonical form (resolves symlinks). + * Returns the resolved path, or empty string on error. + */ + fn fs_realpath(path: string) -> string + + /** + * Change file permissions. + * @param path - file path + * @param mode - Unix permission bits (e.g. 0o644) + * Returns 0 on success, negative errno on error. + * Returns -ENOTSUP on Windows. + */ + fn fs_chmod(path: string, mode: int) -> int + + /** + * Get the current working directory. + * Returns the path, or empty string on error. + */ + fn fs_getcwd() -> string + + /** + * Change the current working directory. + * Returns 0 on success, negative errno on error. + */ + fn fs_chdir(path: string) -> int + + /** + * Copy a file. Preserves permissions on Unix. + * Returns 0 on success, negative errno on error. + */ + fn fs_copy(src: string, dest: string) -> int + + /** + * Get the platform path separator ('/' on Unix, '\\' on Windows). + */ + fn fs_get_separator() -> uint + + /** + * Check if a path is a directory. + * Returns 1 if it is a directory, 0 otherwise. + */ + fn fs_is_dir(path: string) -> int + + /** + * Check if a path is a regular file. + * Returns 1 if it is a regular file, 0 otherwise. + */ + fn fs_is_file(path: string) -> int + + /** + * Join two path components, normalizing separators and ./.. segments. + */ + fn fs_path_join(a: string, b: string) -> string + + /** + * Normalize a path: resolve ./.. segments, collapse double separators. + */ + fn fs_path_normalize(path: string) -> string + + /** + * Get the directory portion of a path. + */ + fn fs_path_dirname(path: string) -> string + + /** + * Get the filename portion of a path (last segment). + */ + fn fs_path_basename(path: string) -> string + + /** + * Get the file extension (including the dot), or empty string if none. + */ + fn fs_path_extension(path: string) -> string +} diff --git a/frameworks/typev/bundle/output.tvbc b/frameworks/typev/bundle/output.tvbc index 3e53dcc17..768b58e93 100644 Binary files a/frameworks/typev/bundle/output.tvbc and b/frameworks/typev/bundle/output.tvbc differ diff --git a/frameworks/typev/meta.json b/frameworks/typev/meta.json index 45ae174b1..c8246896f 100644 --- a/frameworks/typev/meta.json +++ b/frameworks/typev/meta.json @@ -7,7 +7,10 @@ "repo": "https://github.com/praisethemoon/type-project", "enabled": true, "tests": [ - "pipelined" + "pipelined", + "baseline", + "limited-conn", + "json" ], "maintainers": ["praisethemoon"] } diff --git a/site/data/baseline-4096.json b/site/data/baseline-4096.json index 4c10e622d..afab66c36 100644 --- a/site/data/baseline-4096.json +++ b/site/data/baseline-4096.json @@ -1205,6 +1205,26 @@ "status_4xx": 0, "status_5xx": 0 }, + { + "framework": "typev", + "language": "Type-C", + "rps": 1918007, + "avg_latency": "199us", + "p99_latency": "1.20ms", + "cpu": "6737.7%", + "memory": "384MiB", + "connections": 4096, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "164.58MB/s", + "input_bw": "148.16MB/s", + "reconnects": 0, + "status_2xx": 9590035, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, { "framework": "userver", "language": "C++", diff --git a/site/data/baseline-512.json b/site/data/baseline-512.json index affa9887e..7162b2795 100644 --- a/site/data/baseline-512.json +++ b/site/data/baseline-512.json @@ -1205,6 +1205,26 @@ "status_4xx": 0, "status_5xx": 0 }, + { + "framework": "typev", + "language": "Type-C", + "rps": 1871533, + "avg_latency": "124us", + "p99_latency": "680us", + "cpu": "6498.3%", + "memory": "251MiB", + "connections": 512, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "160.59MB/s", + "input_bw": "144.57MB/s", + "reconnects": 0, + "status_2xx": 9357667, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, { "framework": "userver", "language": "C++", diff --git a/site/data/json-4096.json b/site/data/json-4096.json index 965a53f47..7756cbabc 100644 --- a/site/data/json-4096.json +++ b/site/data/json-4096.json @@ -953,6 +953,26 @@ "status_4xx": 0, "status_5xx": 0 }, + { + "framework": "typev", + "language": "Type-C", + "rps": 1077677, + "avg_latency": "3.34ms", + "p99_latency": "1.36ms", + "cpu": "6730.8%", + "memory": "402MiB", + "connections": 4096, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "3.63GB/s", + "input_bw": "51.39MB/s", + "reconnects": 215192, + "status_2xx": 5388386, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, { "framework": "uvicorn", "language": "Python", diff --git a/site/data/limited-conn-4096.json b/site/data/limited-conn-4096.json index b09654e84..977c07f35 100644 --- a/site/data/limited-conn-4096.json +++ b/site/data/limited-conn-4096.json @@ -1205,6 +1205,26 @@ "status_4xx": 0, "status_5xx": 0 }, + { + "framework": "typev", + "language": "Type-C", + "rps": 1282861, + "avg_latency": "3.13ms", + "p99_latency": "194.90ms", + "cpu": "6597.3%", + "memory": "304MiB", + "connections": 4096, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "110.08MB/s", + "input_bw": "99.10MB/s", + "reconnects": 641598, + "status_2xx": 6414306, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, { "framework": "userver", "language": "C++", diff --git a/site/data/limited-conn-512.json b/site/data/limited-conn-512.json index 67cec0b22..fe37b045b 100644 --- a/site/data/limited-conn-512.json +++ b/site/data/limited-conn-512.json @@ -1205,6 +1205,26 @@ "status_4xx": 0, "status_5xx": 0 }, + { + "framework": "typev", + "language": "Type-C", + "rps": 912050, + "avg_latency": "552us", + "p99_latency": "20.10ms", + "cpu": "4779.8%", + "memory": "211MiB", + "connections": 512, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "78.26MB/s", + "input_bw": "70.45MB/s", + "reconnects": 456012, + "status_2xx": 4560254, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, { "framework": "userver", "language": "C++", diff --git a/site/data/pipelined-4096.json b/site/data/pipelined-4096.json index 751e55896..17274fdd6 100644 --- a/site/data/pipelined-4096.json +++ b/site/data/pipelined-4096.json @@ -1145,18 +1145,18 @@ { "framework": "typev", "language": "Type-C", - "rps": 46023600, - "avg_latency": "199us", + "rps": 48398726, + "avg_latency": "246us", "p99_latency": "1.18ms", - "cpu": "6164.8%", - "memory": "505MiB", + "cpu": "6254.3%", + "memory": "355MiB", "connections": 4096, "threads": 64, "duration": "5s", "pipeline": 16, - "bandwidth": "3.86GB/s", - "reconnects": 202612, - "status_2xx": 230118000, + "bandwidth": "4.06GB/s", + "reconnects": 0, + "status_2xx": 241993632, "status_3xx": 0, "status_4xx": 0, "status_5xx": 0 diff --git a/site/data/pipelined-512.json b/site/data/pipelined-512.json index 16a758f98..6d9ede5ba 100644 --- a/site/data/pipelined-512.json +++ b/site/data/pipelined-512.json @@ -1145,18 +1145,18 @@ { "framework": "typev", "language": "Type-C", - "rps": 46259033, - "avg_latency": "140us", - "p99_latency": "743us", - "cpu": "6529.7%", - "memory": "315MiB", + "rps": 47930259, + "avg_latency": "169us", + "p99_latency": "981us", + "cpu": "6303.1%", + "memory": "276MiB", "connections": 512, "threads": 64, "duration": "5s", "pipeline": 16, - "bandwidth": "3.87GB/s", + "bandwidth": "4.02GB/s", "reconnects": 0, - "status_2xx": 231295168, + "status_2xx": 239651296, "status_3xx": 0, "status_4xx": 0, "status_5xx": 0 diff --git a/site/static/logs/baseline/4096/typev.log b/site/static/logs/baseline/4096/typev.log new file mode 100644 index 000000000..9178c2f16 --- /dev/null +++ b/site/static/logs/baseline/4096/typev.log @@ -0,0 +1,3 @@ +typev: loaded '/app/output.tvbc' (v15, 30 functions, entry=29) +typev: workers=64 (sysconf _SC_NPROCESSORS_ONLN=128, TV_WORKERS=64) +typev HttpArena server listening on :8080 diff --git a/site/static/logs/baseline/512/typev.log b/site/static/logs/baseline/512/typev.log new file mode 100644 index 000000000..9178c2f16 --- /dev/null +++ b/site/static/logs/baseline/512/typev.log @@ -0,0 +1,3 @@ +typev: loaded '/app/output.tvbc' (v15, 30 functions, entry=29) +typev: workers=64 (sysconf _SC_NPROCESSORS_ONLN=128, TV_WORKERS=64) +typev HttpArena server listening on :8080 diff --git a/site/static/logs/json/4096/typev.log b/site/static/logs/json/4096/typev.log new file mode 100644 index 000000000..9178c2f16 --- /dev/null +++ b/site/static/logs/json/4096/typev.log @@ -0,0 +1,3 @@ +typev: loaded '/app/output.tvbc' (v15, 30 functions, entry=29) +typev: workers=64 (sysconf _SC_NPROCESSORS_ONLN=128, TV_WORKERS=64) +typev HttpArena server listening on :8080 diff --git a/site/static/logs/limited-conn/4096/typev.log b/site/static/logs/limited-conn/4096/typev.log new file mode 100644 index 000000000..9178c2f16 --- /dev/null +++ b/site/static/logs/limited-conn/4096/typev.log @@ -0,0 +1,3 @@ +typev: loaded '/app/output.tvbc' (v15, 30 functions, entry=29) +typev: workers=64 (sysconf _SC_NPROCESSORS_ONLN=128, TV_WORKERS=64) +typev HttpArena server listening on :8080 diff --git a/site/static/logs/limited-conn/512/typev.log b/site/static/logs/limited-conn/512/typev.log new file mode 100644 index 000000000..9178c2f16 --- /dev/null +++ b/site/static/logs/limited-conn/512/typev.log @@ -0,0 +1,3 @@ +typev: loaded '/app/output.tvbc' (v15, 30 functions, entry=29) +typev: workers=64 (sysconf _SC_NPROCESSORS_ONLN=128, TV_WORKERS=64) +typev HttpArena server listening on :8080 diff --git a/site/static/logs/pipelined/4096/typev.log b/site/static/logs/pipelined/4096/typev.log index 26cf90479..9178c2f16 100644 --- a/site/static/logs/pipelined/4096/typev.log +++ b/site/static/logs/pipelined/4096/typev.log @@ -1,2 +1,3 @@ -typev: loaded '/app/output.tvbc' (v15, 11 functions, entry=10) -typev HttpArena server listening on :8080 (4 SO_REUSEPORT shards) +typev: loaded '/app/output.tvbc' (v15, 30 functions, entry=29) +typev: workers=64 (sysconf _SC_NPROCESSORS_ONLN=128, TV_WORKERS=64) +typev HttpArena server listening on :8080 diff --git a/site/static/logs/pipelined/512/typev.log b/site/static/logs/pipelined/512/typev.log index 26cf90479..9178c2f16 100644 --- a/site/static/logs/pipelined/512/typev.log +++ b/site/static/logs/pipelined/512/typev.log @@ -1,2 +1,3 @@ -typev: loaded '/app/output.tvbc' (v15, 11 functions, entry=10) -typev HttpArena server listening on :8080 (4 SO_REUSEPORT shards) +typev: loaded '/app/output.tvbc' (v15, 30 functions, entry=29) +typev: workers=64 (sysconf _SC_NPROCESSORS_ONLN=128, TV_WORKERS=64) +typev HttpArena server listening on :8080