diff --git a/frameworks/Dart/dart3/.gitignore b/frameworks/Dart/dart3/.gitignore index 5929f0f21e5..a4aa0af72a0 100644 --- a/frameworks/Dart/dart3/.gitignore +++ b/frameworks/Dart/dart3/.gitignore @@ -1,3 +1,3 @@ .dart_tool/ *.lock - +!bin \ No newline at end of file diff --git a/frameworks/Dart/dart3/README.md b/frameworks/Dart/dart3/README.md index 837164c7eee..3be261226e2 100644 --- a/frameworks/Dart/dart3/README.md +++ b/frameworks/Dart/dart3/README.md @@ -1,22 +1,31 @@ # Dart 3 Benchmarking Test -## Test Type Implementation Source Code - -- [JSON](server.dart) -- [PLAINTEXT](server.dart) - ## Important Libraries The tests were run with: -- [Dart v3.10.8](https://dart.dev/) +- [Dart v3.11.0](https://dart.dev/) + +## Benchmark Variants + +### Native + +Minimal implementation with the smallest resource footprint. +Supports basic horizontal scaling via [Isolates](https://dart.dev/language/isolates) and socket sharing. +([source code](https://github.com/TechEmpower/FrameworkBenchmarks/tree/master/frameworks/Dart/dart3/dart_native)) + +Test URLs: -## Test URLs +- JSON: `http://localhost:8080/json` +- PLAINTEXT: `http://localhost:8080/plaintext` -### JSON +### AOT -`http://localhost:8080/json` +Performance-oriented AOT implementation for superior horizontal scaling. +Achieves lowest latency and higher throughput with a slightly larger footprint. +([source code](https://github.com/TechEmpower/FrameworkBenchmarks/tree/master/frameworks/Dart/dart3/dart_aot)) -### PLAINTEXT +Test URLs: -`http://localhost:8080/plaintext` +- JSON: `http://localhost:8080/json` +- PLAINTEXT: `http://localhost:8080/plaintext` diff --git a/frameworks/Dart/dart3/benchmark_config.json b/frameworks/Dart/dart3/benchmark_config.json index 24a5f9ff776..2b3d7df97e1 100644 --- a/frameworks/Dart/dart3/benchmark_config.json +++ b/frameworks/Dart/dart3/benchmark_config.json @@ -1,5 +1,6 @@ { "framework": "dart3", + "maintainers": ["iapicca"], "tests": [{ "default": { "display_name": "dart3_native", @@ -24,18 +25,6 @@ "language": "Dart", "os": "Linux", "database_os": "Linux" - }, - "hybrid": { - "display_name": "dart3_hybrid", - "json_url": "/json", - "plaintext_url": "/plaintext", - "port": 8080, - "approach": "Stripped", - "classification": "Platform", - "database": "None", - "language": "Dart", - "os": "Linux", - "database_os": "Linux" } }] } \ No newline at end of file diff --git a/frameworks/Dart/dart3/dart3-aot.dockerfile b/frameworks/Dart/dart3/dart3-aot.dockerfile index be6a6dff1a7..36fb653f23a 100644 --- a/frameworks/Dart/dart3/dart3-aot.dockerfile +++ b/frameworks/Dart/dart3/dart3-aot.dockerfile @@ -1,12 +1,11 @@ -FROM dart:3.10.8 AS build +FROM dart:3.11.0 AS build WORKDIR /app -# Define the build-time argument (Default to 8) ARG MAX_ISOLATES=8 COPY pubspec.yaml . -COPY bin bin +COPY dart_native/bin/ bin/ RUN dart compile aot-snapshot bin/server.dart \ --define=MAX_ISOLATES=${MAX_ISOLATES} \ @@ -20,5 +19,4 @@ COPY --from=build /app/server.aot /app/server.aot EXPOSE 8080 -# Distroless requires absolute paths ENTRYPOINT ["/usr/lib/dart/bin/dartaotruntime", "/app/server.aot"] diff --git a/frameworks/Dart/dart3/dart3-hybrid.dockerfile b/frameworks/Dart/dart3/dart3-hybrid.dockerfile deleted file mode 100644 index bcff9adc16d..00000000000 --- a/frameworks/Dart/dart3/dart3-hybrid.dockerfile +++ /dev/null @@ -1,34 +0,0 @@ - -FROM dart:3.10.8 AS build -WORKDIR /app - -# Define the maximum number of Dart Isolates at build time -ARG MAX_ISOLATES=10 - -COPY pubspec.yaml . -COPY bin bin - -RUN dart compile exe bin/server.dart \ - --define=MAX_ISOLATES=${MAX_ISOLATES} \ - -o server - -FROM traefik:latest AS traefik_source - -FROM busybox:glibc -WORKDIR /app -# Matches `ARG MAX_ISOLATES` in `build` -ENV MAX_ISOLATES=10 -# Define the minimum number of processes dedicated to `Traefik` -ENV MIN_TRAEFIK_PROCESSES=4 - -COPY --from=build /runtime/ / -COPY --from=build /app/server /app/server -COPY --from=traefik_source /usr/local/bin/traefik /usr/local/bin/traefik -COPY /dart_hybrid/traefik.yaml /etc/traefik/traefik.yaml -COPY /dart_hybrid/traefik_dynamic.yaml /etc/traefik/traefik_dynamic.yaml -COPY /dart_hybrid/run.sh /app/run.sh - -RUN chmod +x /app/run.sh - -EXPOSE 8080 -CMD ["/app/run.sh"] \ No newline at end of file diff --git a/frameworks/Dart/dart3/dart3.dockerfile b/frameworks/Dart/dart3/dart3.dockerfile index 5e5c6350ebb..6b9a5b82753 100644 --- a/frameworks/Dart/dart3/dart3.dockerfile +++ b/frameworks/Dart/dart3/dart3.dockerfile @@ -1,9 +1,9 @@ -FROM dart:3.10.8 AS build +FROM dart:3.11.0 AS build WORKDIR /app COPY pubspec.yaml . -COPY bin bin +COPY dart_native/bin/ bin/ RUN dart compile exe bin/server.dart -o server diff --git a/frameworks/Dart/dart3/bin/server.dart b/frameworks/Dart/dart3/dart_aot/bin/server.dart similarity index 60% rename from frameworks/Dart/dart3/bin/server.dart rename to frameworks/Dart/dart3/dart_aot/bin/server.dart index 78ee11a10e8..8d7d8205fe8 100755 --- a/frameworks/Dart/dart3/bin/server.dart +++ b/frameworks/Dart/dart3/dart_aot/bin/server.dart @@ -2,7 +2,6 @@ import 'dart:convert'; import 'dart:io'; import 'dart:isolate'; import 'dart:math' show min; -import 'package:args/args.dart' show ArgParser; /// Environment declarations are evaluated at compile-time. Use 'const' to /// ensure values are baked into AOT/Native binaries for the benchmark. @@ -14,6 +13,18 @@ import 'package:args/args.dart' show ArgParser; /// but most ahead-of-time compiled platforms will not have this information." const _maxIsolatesfromEnvironment = int.fromEnvironment('MAX_ISOLATES'); +/// The fixed TCP port used by the server. +/// Defined here for visibility and ease of configuration. +const _defaultPort = 8080; + +/// A reusable instance of the UTF-8 JSON encoder to efficiently +/// transform Dart objects into byte arrays for HTTP responses. +final _jsonEncoder = JsonUtf8Encoder(); + +/// Internal token used to notify newly spawned processes that they +/// belong to a secondary "worker group". +const workerGroupTag = '--workerGroup'; + void main(List args) { /// Defines local isolate quota, using MAX_ISOLATES if provided. /// Falls back to total available cores while respecting hardware limits. @@ -21,42 +32,35 @@ void main(List args) { ? min(_maxIsolatesfromEnvironment, Platform.numberOfProcessors) : Platform.numberOfProcessors; - /// Triggers process-level horizontal scaling when running in AOT. - if (Platform.script.toFilePath().endsWith('.aot')) { - /// Internal token used to notify newly spawned processes that they - /// belong to a secondary "worker group". - const workerGroupTag = '--workerGroup'; - - /// Determine if this process instance was initialized as a worker group. - final isWorkerGroup = args.contains(workerGroupTag); + /// Determine if this process instance was initialized as a worker group. + final isWorkerGroup = args.contains(workerGroupTag); - if (isWorkerGroup) { - /// Sanitize the argument list to ensure the internal token does not - /// interfere with application-level argument parsing. - args.removeAt(args.indexOf(workerGroupTag)); - } - /// Prevents recursive spawning - /// by ensuring only the primary process can spawn worker groups - else { - /// Calculate the number of secondary worker groups required - /// to fully utilize the available hardware capacity. - /// - /// Each group serves as a container for multiple isolates, - /// helping to bypass internal VM scaling bottlenecks. - final workerGroups = Platform.numberOfProcessors ~/ maxIsolates - 1; - - /// Bootstraps independent worker processes via AOT snapshots. - /// Each process initializes its own Isolate Group. - for (var i = 0; i < workerGroups; i++) { - /// [Platform.script] identifies the AOT snapshot or executable. - /// [Isolate.spawnUri] spawns a new process group via [main()]. - Isolate.spawnUri(Platform.script, [...args, workerGroupTag], null); - } - - /// Updates local isolate limits, assigning the primary group - /// the remaining cores after worker group allocation. - maxIsolates = Platform.numberOfProcessors - workerGroups * maxIsolates; + if (isWorkerGroup) { + /// Sanitize the argument list to ensure the internal token does not + /// interfere with application-level argument parsing. + args.removeAt(args.indexOf(workerGroupTag)); + } + /// Prevents recursive spawning + /// by ensuring only the primary process can spawn worker groups + else { + /// Calculate the number of secondary worker groups required + /// to fully utilize the available hardware capacity. + /// + /// Each group serves as a container for multiple isolates, + /// helping to bypass internal VM scaling bottlenecks. + final workerGroups = Platform.numberOfProcessors ~/ maxIsolates - 1; + + /// Bootstraps independent worker processes via AOT snapshots. + /// Each process initializes its own Isolate Group. + for (var i = 0; i < workerGroups; i++) { + /// [Platform.script] identifies the AOT snapshot or executable. + /// [Isolate.spawnUri] spawns a new process group via [main()]. + Isolate.spawnUri(Platform.script, [...args, workerGroupTag], null); } + + /// Updates local isolate limits, assigning the primary group + /// the remaining cores after worker group allocation. + maxIsolates = Platform.numberOfProcessors - workerGroups * maxIsolates; } /// Create an [Isolate] containing an [HttpServer] @@ -74,14 +78,14 @@ void _startServer(List args) async { /// Binds the [HttpServer] on `0.0.0.0:8080`. final server = await HttpServer.bind( InternetAddress.anyIPv4, - _portParser(args, defaultPort: 8080), + _defaultPort, shared: true, ); server ..defaultResponseHeaders.clear() /// Sets [HttpServer]'s [serverHeader]. - ..serverHeader = 'dart' + ..serverHeader = 'dart_aot' /// Handles [HttpRequest]'s from [HttpServer]. ..listen(_handleRequest); } @@ -142,18 +146,3 @@ void _plaintextTest(HttpRequest request) => _sendText( request, 'Hello, World!', ); - -final _jsonEncoder = JsonUtf8Encoder(); - -int _portParser( - List args, { - required int defaultPort, - portTag = 'port', -}) { - final parser = ArgParser() - ..addOption( - portTag, - defaultsTo: '$defaultPort', - ); - return int.tryParse(parser.parse(args)[portTag]) ?? defaultPort; -} diff --git a/frameworks/Dart/dart3/dart_hybrid/run.sh b/frameworks/Dart/dart3/dart_hybrid/run.sh deleted file mode 100644 index 18a8bd0a620..00000000000 --- a/frameworks/Dart/dart3/dart_hybrid/run.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/sh - -# --- 1. Environment & Hardware Detection --- -TOTAL_PROCESSES=$(grep -c ^processor /proc/cpuinfo) -MAX_ISO=${MAX_ISOLATES:-8} -RESERVED=${MIN_TRAEFIK_PROCESSES:-4} - -# --- 2. Scaling Logic --- -if [ "$TOTAL_PROCESSES" -le "$((MAX_ISO * 2))" ]; then - exec /app/server -else - DART_POOL=$((TOTAL_PROCESSES - RESERVED)) - NUM_WORKERS=$(( DART_POOL / MAX_ISO )) - DART_PROCESSES=$(( NUM_WORKERS * MAX_ISO )) - TRAEFIK_PROCESSES=$(( TOTAL_PROCESSES - DART_PROCESSES )) - - export GOMAXPROCS=$TRAEFIK_PROCESSES - -# --- 3. Generate Backend List --- - TMP_URLS="/tmp/urls.yaml" - true > "$TMP_URLS" - - for i in $(seq 1 $NUM_WORKERS); do - PORT=$((9000 + i)) - echo " - url: \"http://127.0.0.1:$PORT\"" >> "$TMP_URLS" - - /app/server --port=$PORT & - done - -# --- 4. Traefik Configuration --- - sed -i "/# DART_WORKERS_PLACEHOLDER/ { - r $TMP_URLS - d - }" /etc/traefik/traefik_dynamic.yaml - rm "$TMP_URLS" - -# --- 5. Readiness & Execution --- - until nc -z 127.0.0.1 9001; do sleep 0.1; done - - exec traefik --configfile=/etc/traefik/traefik.yaml -fi \ No newline at end of file diff --git a/frameworks/Dart/dart3/dart_hybrid/traefik.yaml b/frameworks/Dart/dart3/dart_hybrid/traefik.yaml deleted file mode 100644 index b7d0e8dee2e..00000000000 --- a/frameworks/Dart/dart3/dart_hybrid/traefik.yaml +++ /dev/null @@ -1,17 +0,0 @@ -global: - checkNewVersion: false - sendAnonymousUsage: false - -entryPoints: - web: - address: ":8080" - transport: - respondingTimeouts: - readTimeout: "0s" - writeTimeout: "0s" - idleTimeout: "0s" - -providers: - file: - filename: /etc/traefik/traefik_dynamic.yaml - watch: false \ No newline at end of file diff --git a/frameworks/Dart/dart3/dart_hybrid/traefik_dynamic.yaml b/frameworks/Dart/dart3/dart_hybrid/traefik_dynamic.yaml deleted file mode 100644 index f4c2dd2b150..00000000000 --- a/frameworks/Dart/dart3/dart_hybrid/traefik_dynamic.yaml +++ /dev/null @@ -1,19 +0,0 @@ -http: - routers: - to-dart: - rule: "PathPrefix(`/`)" - service: dart-service - middlewares: - - dart-header - entryPoints: - - web - middlewares: - dart-header: - headers: - customResponseHeaders: - Server: "dart" - services: - dart-service: - loadBalancer: - servers: - # DART_WORKERS_PLACEHOLDER \ No newline at end of file diff --git a/frameworks/Dart/dart3/dart_native/bin/server.dart b/frameworks/Dart/dart3/dart_native/bin/server.dart new file mode 100755 index 00000000000..bab4cf30e62 --- /dev/null +++ b/frameworks/Dart/dart3/dart_native/bin/server.dart @@ -0,0 +1,96 @@ +import 'dart:convert'; +import 'dart:io'; +import 'dart:isolate'; + +/// The fixed TCP port used by the server. +/// Defined here for visibility and ease of configuration. +const _defaultPort = 8080; + +/// A reusable instance of the UTF-8 JSON encoder to efficiently +/// transform Dart objects into byte arrays for HTTP responses. +final _jsonEncoder = JsonUtf8Encoder(); + +void main(List args) { + /// Create an [Isolate] containing an [HttpServer] + /// for each processor after the first + for (var i = 1; i < Platform.numberOfProcessors; i++) { + Isolate.spawn(_startServer, args); + } + + /// Create a [HttpServer] for the first processor + _startServer(args); +} + +/// Creates and setup a [HttpServer] +void _startServer(List args) async { + /// Binds the [HttpServer] on `0.0.0.0:8080`. + final server = await HttpServer.bind( + InternetAddress.anyIPv4, + _defaultPort, + shared: true, + ); + + server + ..defaultResponseHeaders.clear() + /// Sets [HttpServer]'s [serverHeader]. + ..serverHeader = 'dart_native' + /// Handles [HttpRequest]'s from [HttpServer]. + ..listen(_handleRequest); +} + +/// Dispatches requests to specific handlers. +void _handleRequest(HttpRequest request) { + switch (request.uri.path) { + case '/json': + _jsonTest(request); + break; + case '/plaintext': + _plaintextTest(request); + break; + default: + _sendResponse(request, HttpStatus.notFound); + } +} + +/// Completes the given [request] by writing the [bytes] with the given +/// [statusCode] and [type]. +void _sendResponse( + HttpRequest request, + int statusCode, { + ContentType? type, + List bytes = const [], +}) => request.response + ..statusCode = statusCode + ..headers.contentType = type + ..headers.date = DateTime.now() + ..contentLength = bytes.length + ..add(bytes) + ..close(); + +/// Completes the given [request] by writing the [response] as JSON. +void _sendJson(HttpRequest request, Object response) => _sendResponse( + request, + HttpStatus.ok, + type: ContentType.json, + bytes: _jsonEncoder.convert(response), +); + +/// Completes the given [request] by writing the [response] as plain text. +void _sendText(HttpRequest request, String response) => _sendResponse( + request, + HttpStatus.ok, + type: ContentType.text, + bytes: utf8.encode(response), +); + +/// Responds with the JSON test to the [request]. +void _jsonTest(HttpRequest request) => _sendJson( + request, + const {'message': 'Hello, World!'}, +); + +/// Responds with the plaintext test to the [request]. +void _plaintextTest(HttpRequest request) => _sendText( + request, + 'Hello, World!', +); diff --git a/frameworks/Dart/dart3/pubspec.yaml b/frameworks/Dart/dart3/pubspec.yaml index 28d0d22bec9..aeb53912216 100644 --- a/frameworks/Dart/dart3/pubspec.yaml +++ b/frameworks/Dart/dart3/pubspec.yaml @@ -1,11 +1,8 @@ name: dartbenchmark description: A benchmark of dart environment: - sdk: ^3.10.8 - -dependencies: - args: ^2.7.0 + sdk: ^3.11.0 dev_dependencies: - lints: ^6.0.0 + lints: ^6.1.0