mirror of
https://github.com/status-im/nim-chronos.git
synced 2026-08-27 13:01:12 +00:00
The http client currently uses an inefficient byte-by-byte copy in its `BoundedStreamReader` - the real solution here is to get rid of the copy completely but until then, we can use bulk-copy the data at least. Ditto `read`, `readN` and similar helpers - this ~doubles throughput for bulk reading. Pre: ``` | Small/small | 0.070s | 1000 | 14226.009 | 0.031 MB | 0.016 MB | 0.434 MB/s | 0.231 MB/s | | Medium/small | 1.834s | 1000 | 545.267 | 1000.000 MB | 0.021 MB | 545.267 MB/s | 0.011 MB/s | | Small/Medium | 2.238s | 1000 | 446.735 | 0.031 MB | 1000.000 MB | 0.014 MB/s | 446.735 MB/s | | Medium/Medium | 2.583s | 1000 | 387.196 | 1000.000 MB | 1000.000 MB | 387.196 MB/s | 387.196 MB/s | ``` Post: ``` | Small/small | 0.066s | 1000 | 15038.890 | 0.031 MB | 0.016 MB | 0.459 MB/s | 0.244 MB/s | | Medium/small | 0.954s | 1000 | 1048.475 | 1000.000 MB | 0.021 MB | 1048.475 MB/s | 0.022 MB/s | | Small/Medium | 1.264s | 1000 | 791.318 | 0.031 MB | 1000.000 MB | 0.024 MB/s | 791.318 MB/s | | Medium/Medium | 1.615s | 1000 | 619.083 | 1000.000 MB | 1000.000 MB | 619.083 MB/s | 619.083 MB/s | ```
180 lines
5.6 KiB
Nim
180 lines
5.6 KiB
Nim
# Chronos Test Suite
|
|
# (c) Copyright 2021-Present
|
|
# Status Research & Development GmbH
|
|
#
|
|
# Licensed under either of
|
|
# Apache License, version 2.0, (LICENSE-APACHEv2)
|
|
# MIT license (LICENSE-MIT)
|
|
import
|
|
std/[strformat, times],
|
|
|
|
../chronos,
|
|
../chronos/threadsync,
|
|
../chronos/apps/http/[httpserver, httpclient, httpcommon]
|
|
|
|
{.used.}
|
|
|
|
# Benchmark configuration
|
|
const
|
|
SmallRequestSize = 32
|
|
MediumRequestSize = 1024 * 1024 # 1MB
|
|
MediumResponseSize = 1024 * 1024 # 1MB
|
|
NumClients = 10 # Number of concurrent clients
|
|
NumRequestsPerClient = 100 # Number of requests per client
|
|
|
|
type BenchmarkResult = object
|
|
testName: string
|
|
totalTime: times.Duration
|
|
totalBytesSent: int64
|
|
totalBytesReceived: int64
|
|
numRequests: int
|
|
|
|
# Create a message of specified size
|
|
proc createMessage(size: int): seq[byte] =
|
|
let message = "Hello, World! This is a benchmark message. "
|
|
result = newSeq[byte](size)
|
|
for i in 0 ..< size:
|
|
result[i] = byte(message[i mod len(message)])
|
|
|
|
|
|
const
|
|
smallRequest = createMessage(SmallRequestSize)
|
|
mediumMessage = createMessage(MediumRequestSize)
|
|
|
|
proc inSecondsFloat(d: times.Duration): float =
|
|
d.inNanoseconds() / 1000000000
|
|
|
|
# Create a simple HTTP server for benchmarking
|
|
proc createBenchmarkServer(address: TransportAddress): HttpServerRef =
|
|
proc process(
|
|
r: RequestFence
|
|
): Future[HttpResponseRef] {.async: (raises: [CancelledError]).} =
|
|
if r.isOk():
|
|
let request = r.get()
|
|
try:
|
|
case request.uri.path
|
|
of "/small":
|
|
# Small request, small response
|
|
let data = await request.getBody()
|
|
await request.respond(Http200, "Received " & $len(data) & " bytes")
|
|
of "/medium":
|
|
# Small request, medium response
|
|
discard await request.getBody()
|
|
await request.respond(Http200, mediumMessage)
|
|
else:
|
|
await request.respond(Http404, "Not found")
|
|
except HttpError as exc:
|
|
defaultResponse(exc)
|
|
else:
|
|
defaultResponse()
|
|
|
|
let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr}
|
|
let res = HttpServerRef.new(address, process, socketFlags = socketFlags)
|
|
res.get()
|
|
|
|
var
|
|
serverAddress: TransportAddress
|
|
tsp: ThreadSignalPtr
|
|
|
|
proc runServer() {.thread, nimcall.} =
|
|
let server = createBenchmarkServer(initTAddress("127.0.0.1:0"))
|
|
server.start()
|
|
serverAddress = server.instance.localAddress()
|
|
discard tsp.fireSync().expect("ok")
|
|
runForever()
|
|
|
|
# Create an HTTP client session
|
|
proc createBenchmarkClient(): HttpSessionRef =
|
|
HttpSessionRef.new({HttpClientFlag.Http11Pipeline})
|
|
|
|
# Client worker that sends requests and measures performance
|
|
proc clientWorker(
|
|
session: HttpSessionRef,
|
|
address: TransportAddress,
|
|
testPath: string,
|
|
requestData: seq[byte],
|
|
results: ref BenchmarkResult,
|
|
) {.async.} =
|
|
let ha = getAddress(address, HttpClientScheme.NonSecure, testPath)
|
|
|
|
for i in 0 ..< NumRequestsPerClient:
|
|
var req = HttpClientRequestRef.new(session, ha, MethodPost, body = requestData)
|
|
|
|
let response = await fetch(req)
|
|
assert response.status == 200, "No failing requests in this benchmark"
|
|
|
|
inc(results.numRequests)
|
|
results.totalBytesReceived += int64(len(response.data))
|
|
|
|
await req.closeWait()
|
|
|
|
# Run a benchmark test
|
|
proc runBenchmark(
|
|
testName: string, testPath: string, requestData: seq[byte]
|
|
): Future[BenchmarkResult] {.async.} =
|
|
var session = createBenchmarkClient()
|
|
var futures = newSeq[Future[void]](NumClients)
|
|
var results = (ref BenchmarkResult)(
|
|
testName: testName,
|
|
totalBytesSent: int64(len(requestData)) * int64(NumClients * NumRequestsPerClient),
|
|
numRequests: 0,
|
|
)
|
|
|
|
let startTime = getTime()
|
|
|
|
# Start client workers
|
|
for i in 0 ..< NumClients:
|
|
futures[i] = clientWorker(session, serverAddress, testPath, requestData, results)
|
|
|
|
# Wait for all workers to complete
|
|
await allFutures(futures)
|
|
|
|
await session.closeWait()
|
|
let endTime = getTime()
|
|
|
|
results.totalTime = endTime - startTime
|
|
|
|
results[]
|
|
|
|
# Print benchmark results as a table
|
|
proc print(results: BenchmarkResult) =
|
|
let v = (
|
|
name: results.testName,
|
|
totalTime: results.totalTime.inSecondsFloat,
|
|
numRequests: results.numRequests,
|
|
reqsps: ((results.numRequests.float / results.totalTime.inSecondsFloat)),
|
|
sent: results.totalBytesSent / (1024 * 1024),
|
|
received: results.totalBytesReceived / (1024 * 1024),
|
|
sendSpeed:
|
|
(results.totalBytesSent.float / results.totalTime.inSecondsFloat / (1024 * 1024)),
|
|
recvSpeed: (
|
|
results.totalBytesReceived.float / results.totalTime.inSecondsFloat / (
|
|
1024 * 1024
|
|
)
|
|
),
|
|
)
|
|
|
|
echo &"| {v.name:14} | {v.totalTime:8.3f}s | {v.numRequests:6} | {v.reqsps:8.3f} | {v.sent:8.3f} MB | {v.received:8.3f} MB | {v.sendSpeed:8.3f} MB/s | {v.recvSpeed:8.3f} MB/s |"
|
|
|
|
# Main benchmark function
|
|
proc runBenchmarks() {.async.} =
|
|
echo " Clients: " & $NumClients
|
|
echo " Requests per client: " & $NumRequestsPerClient
|
|
echo " Total requests: " & $(NumClients * NumRequestsPerClient)
|
|
echo ""
|
|
echo "| Benchmark | Time (s) | Reqs | Req/s | Bytes Sent | Bytes Recv | Send Speed | Recv Speed |"
|
|
echo "|----------------|-----------|--------|----------|-------------|-------------|---------------|---------------|"
|
|
|
|
print(await runBenchmark("Small/small", "/small", smallRequest))
|
|
print(await runBenchmark("Medium/small", "/small", mediumMessage))
|
|
print(await runBenchmark("Small/Medium", "/medium", smallRequest))
|
|
print(await runBenchmark("Medium/Medium", "/medium", mediumMessage))
|
|
|
|
when isMainModule:
|
|
tsp = ThreadSignalPtr.new()[]
|
|
var server: Thread[void]
|
|
createThread(server, runServer)
|
|
discard tsp.waitSync().expect("ok")
|
|
|
|
waitFor runBenchmarks()
|