|
| 1 | +# Chronos Test Suite |
| 2 | +# (c) Copyright 2021-Present |
| 3 | +# Status Research & Development GmbH |
| 4 | +# |
| 5 | +# Licensed under either of |
| 6 | +# Apache License, version 2.0, (LICENSE-APACHEv2) |
| 7 | +# MIT license (LICENSE-MIT) |
| 8 | +import |
| 9 | + std/[strformat, times], |
| 10 | + |
| 11 | + ../chronos, |
| 12 | + ../chronos/threadsync, |
| 13 | + ../chronos/apps/http/[httpserver, httpclient, httpcommon] |
| 14 | + |
| 15 | +{.used.} |
| 16 | + |
| 17 | +# Benchmark configuration |
| 18 | +const |
| 19 | + SmallRequestSize = 32 |
| 20 | + MediumRequestSize = 1024 * 1024 # 1MB |
| 21 | + MediumResponseSize = 1024 * 1024 # 1MB |
| 22 | + NumClients = 10 # Number of concurrent clients |
| 23 | + NumRequestsPerClient = 100 # Number of requests per client |
| 24 | + |
| 25 | +type BenchmarkResult = object |
| 26 | + testName: string |
| 27 | + totalTime: times.Duration |
| 28 | + totalBytesSent: int64 |
| 29 | + totalBytesReceived: int64 |
| 30 | + numRequests: int |
| 31 | + |
| 32 | +# Create a message of specified size |
| 33 | +proc createMessage(size: int): seq[byte] = |
| 34 | + let message = "Hello, World! This is a benchmark message. " |
| 35 | + result = newSeq[byte](size) |
| 36 | + for i in 0 ..< size: |
| 37 | + result[i] = byte(message[i mod len(message)]) |
| 38 | + |
| 39 | + |
| 40 | +const |
| 41 | + smallRequest = createMessage(SmallRequestSize) |
| 42 | + mediumMessage = createMessage(MediumRequestSize) |
| 43 | + |
| 44 | +proc inSecondsFloat(d: times.Duration): float = |
| 45 | + d.inNanoseconds() / 1000000000 |
| 46 | + |
| 47 | +# Create a simple HTTP server for benchmarking |
| 48 | +proc createBenchmarkServer(address: TransportAddress): HttpServerRef = |
| 49 | + proc process( |
| 50 | + r: RequestFence |
| 51 | + ): Future[HttpResponseRef] {.async: (raises: [CancelledError]).} = |
| 52 | + if r.isOk(): |
| 53 | + let request = r.get() |
| 54 | + try: |
| 55 | + case request.uri.path |
| 56 | + of "/small": |
| 57 | + # Small request, small response |
| 58 | + let data = await request.getBody() |
| 59 | + await request.respond(Http200, "Received " & $len(data) & " bytes") |
| 60 | + of "/medium": |
| 61 | + # Small request, medium response |
| 62 | + discard await request.getBody() |
| 63 | + await request.respond(Http200, mediumMessage) |
| 64 | + else: |
| 65 | + await request.respond(Http404, "Not found") |
| 66 | + except HttpError as exc: |
| 67 | + defaultResponse(exc) |
| 68 | + else: |
| 69 | + defaultResponse() |
| 70 | + |
| 71 | + let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr} |
| 72 | + let res = HttpServerRef.new(address, process, socketFlags = socketFlags) |
| 73 | + res.get() |
| 74 | + |
| 75 | +var |
| 76 | + serverAddress: TransportAddress |
| 77 | + tsp: ThreadSignalPtr |
| 78 | + |
| 79 | +proc runServer() {.thread, nimcall.} = |
| 80 | + let server = createBenchmarkServer(initTAddress("127.0.0.1:0")) |
| 81 | + server.start() |
| 82 | + serverAddress = server.instance.localAddress() |
| 83 | + discard tsp.fireSync().expect("ok") |
| 84 | + runForever() |
| 85 | + |
| 86 | +# Create an HTTP client session |
| 87 | +proc createBenchmarkClient(): HttpSessionRef = |
| 88 | + HttpSessionRef.new({HttpClientFlag.Http11Pipeline}) |
| 89 | + |
| 90 | +# Client worker that sends requests and measures performance |
| 91 | +proc clientWorker( |
| 92 | + session: HttpSessionRef, |
| 93 | + address: TransportAddress, |
| 94 | + testPath: string, |
| 95 | + requestData: seq[byte], |
| 96 | + results: ref BenchmarkResult, |
| 97 | +) {.async.} = |
| 98 | + let ha = getAddress(address, HttpClientScheme.NonSecure, testPath) |
| 99 | + |
| 100 | + for i in 0 ..< NumRequestsPerClient: |
| 101 | + var req = HttpClientRequestRef.new(session, ha, MethodPost, body = requestData) |
| 102 | + |
| 103 | + let response = await fetch(req) |
| 104 | + assert response.status == 200, "No failing requests in this benchmark" |
| 105 | + |
| 106 | + inc(results.numRequests) |
| 107 | + results.totalBytesReceived += int64(len(response.data)) |
| 108 | + |
| 109 | + await req.closeWait() |
| 110 | + |
| 111 | +# Run a benchmark test |
| 112 | +proc runBenchmark( |
| 113 | + testName: string, testPath: string, requestData: seq[byte] |
| 114 | +): Future[BenchmarkResult] {.async.} = |
| 115 | + var session = createBenchmarkClient() |
| 116 | + var futures = newSeq[Future[void]](NumClients) |
| 117 | + var results = (ref BenchmarkResult)( |
| 118 | + testName: testName, |
| 119 | + totalBytesSent: int64(len(requestData)) * int64(NumClients * NumRequestsPerClient), |
| 120 | + numRequests: 0, |
| 121 | + ) |
| 122 | + |
| 123 | + let startTime = getTime() |
| 124 | + |
| 125 | + # Start client workers |
| 126 | + for i in 0 ..< NumClients: |
| 127 | + futures[i] = clientWorker(session, serverAddress, testPath, requestData, results) |
| 128 | + |
| 129 | + # Wait for all workers to complete |
| 130 | + await allFutures(futures) |
| 131 | + |
| 132 | + await session.closeWait() |
| 133 | + let endTime = getTime() |
| 134 | + |
| 135 | + results.totalTime = endTime - startTime |
| 136 | + |
| 137 | + results[] |
| 138 | + |
| 139 | +# Print benchmark results as a table |
| 140 | +proc print(results: BenchmarkResult) = |
| 141 | + let v = ( |
| 142 | + name: results.testName, |
| 143 | + totalTime: results.totalTime.inSecondsFloat, |
| 144 | + numRequests: results.numRequests, |
| 145 | + reqsps: ((results.numRequests.float / results.totalTime.inSecondsFloat)), |
| 146 | + sent: results.totalBytesSent / (1024 * 1024), |
| 147 | + received: results.totalBytesReceived / (1024 * 1024), |
| 148 | + sendSpeed: |
| 149 | + (results.totalBytesSent.float / results.totalTime.inSecondsFloat / (1024 * 1024)), |
| 150 | + recvSpeed: ( |
| 151 | + results.totalBytesReceived.float / results.totalTime.inSecondsFloat / ( |
| 152 | + 1024 * 1024 |
| 153 | + ) |
| 154 | + ), |
| 155 | + ) |
| 156 | + |
| 157 | + 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 |" |
| 158 | + |
| 159 | +# Main benchmark function |
| 160 | +proc runBenchmarks() {.async.} = |
| 161 | + echo " Clients: " & $NumClients |
| 162 | + echo " Requests per client: " & $NumRequestsPerClient |
| 163 | + echo " Total requests: " & $(NumClients * NumRequestsPerClient) |
| 164 | + echo "" |
| 165 | + echo "| Benchmark | Time (s) | Reqs | Req/s | Bytes Sent | Bytes Recv | Send Speed | Recv Speed |" |
| 166 | + echo "|----------------|-----------|--------|----------|-------------|-------------|---------------|---------------|" |
| 167 | + |
| 168 | + print(await runBenchmark("Small/small", "/small", smallRequest)) |
| 169 | + print(await runBenchmark("Medium/small", "/small", mediumMessage)) |
| 170 | + print(await runBenchmark("Small/Medium", "/medium", smallRequest)) |
| 171 | + print(await runBenchmark("Medium/Medium", "/medium", mediumMessage)) |
| 172 | + |
| 173 | +when isMainModule: |
| 174 | + tsp = ThreadSignalPtr.new()[] |
| 175 | + var server: Thread[void] |
| 176 | + createThread(server, runServer) |
| 177 | + discard tsp.waitSync().expect("ok") |
| 178 | + |
| 179 | + waitFor runBenchmarks() |
0 commit comments