Skip to content

Commit 64d0327

Browse files
authored
Merge pull request #7 from jscott3201/dev
0.8.0 Release
2 parents 0207dca + d29e9ad commit 64d0327

File tree

117 files changed

+14788
-5359
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

117 files changed

+14788
-5359
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -323,16 +323,11 @@ jobs:
323323
github-release:
324324
name: GitHub Release
325325
if: startsWith(github.ref, 'refs/tags/v')
326-
needs: [publish-crates, publish-pypi, publish-npm, publish-java, build-cli-binaries]
326+
needs: [publish-crates, publish-pypi, publish-npm, build-cli-binaries]
327327
runs-on: ubuntu-latest
328328
steps:
329329
- uses: actions/checkout@v4
330330

331-
- uses: actions/download-artifact@v4
332-
with:
333-
name: java-jar
334-
path: dist/java
335-
336331
- uses: actions/download-artifact@v4
337332
with:
338333
pattern: cli-*
@@ -343,7 +338,6 @@ jobs:
343338
with:
344339
generate_release_notes: true
345340
files: |
346-
dist/java/*.jar
347341
dist/cli/bacnet-*
348342
env:
349343
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -414,136 +408,6 @@ jobs:
414408
name: cli-${{ matrix.target }}
415409
path: ${{ matrix.binary }}
416410

417-
# ===========================================================================
418-
# Tier 2b — Java/Kotlin release
419-
# ===========================================================================
420-
421-
build-java-native:
422-
name: Java Native (${{ matrix.os }} ${{ matrix.target }})
423-
if: startsWith(github.ref, 'refs/tags/v')
424-
needs: validate
425-
runs-on: ${{ matrix.os }}
426-
strategy:
427-
fail-fast: false
428-
matrix:
429-
include:
430-
- os: ubuntu-latest
431-
target: x86_64-unknown-linux-gnu
432-
jna_dir: linux-x86-64
433-
lib_name: libbacnet_java.so
434-
features: "--features bacnet-transport/ipv6,bacnet-transport/sc-tls,bacnet-transport/serial,bacnet-transport/ethernet"
435-
- os: ubuntu-24.04-arm
436-
target: aarch64-unknown-linux-gnu
437-
jna_dir: linux-aarch64
438-
lib_name: libbacnet_java.so
439-
features: "--features bacnet-transport/ipv6,bacnet-transport/sc-tls,bacnet-transport/serial,bacnet-transport/ethernet"
440-
- os: macos-latest
441-
target: x86_64-apple-darwin
442-
jna_dir: darwin-x86-64
443-
lib_name: libbacnet_java.dylib
444-
features: "--features bacnet-transport/ipv6,bacnet-transport/sc-tls"
445-
- os: macos-latest
446-
target: aarch64-apple-darwin
447-
jna_dir: darwin-aarch64
448-
lib_name: libbacnet_java.dylib
449-
features: "--features bacnet-transport/ipv6,bacnet-transport/sc-tls"
450-
- os: windows-latest
451-
target: x86_64-pc-windows-msvc
452-
jna_dir: win32-x86-64
453-
lib_name: bacnet_java.dll
454-
features: "--features bacnet-transport/ipv6,bacnet-transport/sc-tls"
455-
steps:
456-
- uses: actions/checkout@v4
457-
- uses: dtolnay/rust-toolchain@master
458-
with:
459-
toolchain: "1.93"
460-
targets: ${{ matrix.target }}
461-
- uses: Swatinem/rust-cache@v2
462-
with:
463-
key: java-${{ matrix.target }}
464-
465-
- name: Install Linux aarch64 dependencies
466-
if: matrix.target == 'aarch64-unknown-linux-gnu' && runner.arch != 'ARM64'
467-
run: |
468-
sudo apt-get update
469-
sudo apt-get install -y gcc-aarch64-linux-gnu
470-
471-
- name: Build native library
472-
run: cargo build -p bacnet-java --release --target ${{ matrix.target }} ${{ matrix.features }}
473-
474-
- name: Upload native library
475-
uses: actions/upload-artifact@v4
476-
with:
477-
name: java-native-${{ matrix.jna_dir }}
478-
path: target/${{ matrix.target }}/release/${{ matrix.lib_name }}
479-
480-
publish-java:
481-
name: Publish Java/Kotlin JAR
482-
if: startsWith(github.ref, 'refs/tags/v')
483-
needs: [build-java-native]
484-
runs-on: ubuntu-latest
485-
permissions:
486-
packages: write
487-
contents: read
488-
steps:
489-
- uses: actions/checkout@v4
490-
491-
- uses: dtolnay/rust-toolchain@master
492-
with:
493-
toolchain: "1.93"
494-
- uses: Swatinem/rust-cache@v2
495-
496-
- uses: actions/setup-java@v4
497-
with:
498-
distribution: temurin
499-
java-version: "21"
500-
501-
- name: Download all native libraries
502-
uses: actions/download-artifact@v4
503-
with:
504-
pattern: java-native-*
505-
path: native-libs
506-
507-
- name: Build Linux x86_64 cdylib for binding generation
508-
run: cargo build -p bacnet-java --release
509-
510-
- name: Build uniffi-bindgen
511-
run: cargo build -p uniffi-bindgen
512-
513-
- name: Generate Kotlin bindings
514-
run: |
515-
cargo run -p uniffi-bindgen -- generate \
516-
--library target/release/libbacnet_java.so \
517-
--language kotlin \
518-
--no-format \
519-
--out-dir java/src/main/kotlin
520-
521-
- name: Assemble native libraries into JNA layout
522-
run: |
523-
for dir in native-libs/java-native-*/; do
524-
JNA_DIR=$(basename "$dir" | sed 's/java-native-//')
525-
mkdir -p "java/src/main/resources/$JNA_DIR"
526-
cp "$dir"/* "java/src/main/resources/$JNA_DIR/"
527-
done
528-
find java/src/main/resources -type f | sort
529-
530-
- name: Build JAR
531-
working-directory: java
532-
run: ./gradlew build
533-
534-
- name: Publish to GitHub Packages
535-
working-directory: java
536-
run: ./gradlew publish
537-
env:
538-
GITHUB_ACTOR: ${{ github.actor }}
539-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
540-
541-
- name: Upload JAR artifact
542-
uses: actions/upload-artifact@v4
543-
with:
544-
name: java-jar
545-
path: java/build/libs/*.jar
546-
547411
# ===========================================================================
548412
# Docker Hub (placeholder for 1.0)
549413
# ===========================================================================

.gitignore

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ CLAUDE.md
2929
.cursor/
3030
.aider*
3131
.codeium/
32+
.superpowers/
33+
.mcp/
34+
.mcp.json
35+
mcp.json
3236

3337
# Proprietary spec
3438
_spec/
@@ -44,16 +48,6 @@ Thumbs.db
4448
*.swo
4549
*~
4650

47-
# Java/Gradle
48-
java/.gradle/
49-
java/build/
50-
java/benchmarks/.gradle/
51-
java/benchmarks/build/
52-
java/src/main/kotlin/uniffi/
53-
java/src/main/resources/darwin-*/
54-
java/src/main/resources/linux-*/
55-
java/src/main/resources/win32-*/
56-
5751
# JavaScript/WASM
5852
node_modules/
5953
pkg/

Benchmarks.md

Lines changed: 19 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
# Rusty BACnet — Benchmarks & Stress Test Results
22

3-
> Run date: 2026-03-05 | Platform: macOS (Apple Silicon) | Rust 1.93 | JDK 21.0.10 | Release mode
3+
> Last full run: 2026-03-05 | Platform: macOS (Apple Silicon) | Rust 1.93 | Release mode
44
>
55
> TLS provider: aws-lc-rs | All tests ran on localhost with zero errors unless noted.
6+
>
7+
> **2026-03-20 update:** Server dispatch loop now spawns per-request tasks for concurrent
8+
> multi-client handling. Quick benchmarks show ~44% read throughput improvement at 1000 ops.
9+
> Client multi-device batch API added (`read_property_from_devices`, etc.) with
10+
> `buffer_unordered` concurrency. Full benchmark run pending.
611
712
---
813

@@ -32,10 +37,13 @@
3237

3338
#### Throughput (batched requests)
3439

35-
| Operation | 10 ops | 100 ops | 1000 ops | Peak ops/s |
36-
|---|---|---|---|---|
37-
| ReadProperty | 278 µs | 2.77 ms | 27.6 ms | **~36.0 K/s** |
38-
| WriteProperty | 289 µs | 2.87 ms | 28.3 ms | **~35.3 K/s** |
40+
| Operation | 10 ops | 100 ops | 1000 ops | Peak ops/s | Δ vs pre-spawn |
41+
|---|---|---|---|---|---|
42+
| ReadProperty | 280 µs | 2.82 ms | 28.1 ms | **~35.6 K/s** | **~-44%** ¹ |
43+
| WriteProperty | 297 µs | 2.97 ms | 29.9 ms | **~33.4 K/s** | ~0% ² |
44+
45+
¹ Quick-run (sample-size 10) showed -44% to -47% improvement at 1000 ops from dispatch spawning. Full benchmark pending.
46+
² Write throughput unchanged — writes take exclusive `db.write()` lock so they naturally serialize.
3947

4048
### 1.3 BACnet/IPv6 (BIP6) — UDP Transport
4149

@@ -220,14 +228,6 @@ Peak RSS: 9.2 MB. Scan time scales linearly.
220228
SC mTLS adds negligible overhead vs server-auth-only SC — the TLS handshake dominates, not per-message client cert verification.
221229
Python concurrent throughput is competitive with Rust single-threaded due to tokio's multi-threaded runtime handling the actual I/O.
222230

223-
### Kotlin/JVM (UniFFI/JNA, coroutines)
224-
225-
| Transport | RP Latency | Sequential Throughput | Overhead vs Rust |
226-
|---|---|---|---|
227-
| **BIP (Kt→Rust)** | ~74 µs | 14.0 K/s | +2.7× latency |
228-
229-
Kotlin/JNA overhead (~46 µs) is ~40% lower than Python/PyO3 (~80 µs) per async call.
230-
231231
---
232232

233233
## 4. Docker Cross-Network Tests
@@ -433,79 +433,12 @@ comparable to pure Rust's single-threaded 36K/s.
433433

434434
---
435435

436-
## 7. Kotlin/JVM ↔ Rust Benchmarks (UniFFI/JNA)
437-
438-
> JDK 21.0.10 (OpenJDK, Apple Silicon) | UniFFI 0.29 + JNA 5.15 | JMH 1.37 | kotlinx-coroutines 1.9.0
439-
>
440-
> All tests use the `bacnet-java` UniFFI bindings on localhost over BIP (UDP/IPv4).
441-
> Server hosts 15 mixed objects (analog-input/output, binary-value, multistate-input).
442-
443-
### 7.1 BACnet/IP — Kotlin Client → Rust Server
444-
445-
| Operation | Mean | ± Error |
446-
|---|---|---|
447-
| ReadProperty | **73.9 µs** | ± 1.1 µs |
448-
| WriteProperty | **80.6 µs** | ± 5.7 µs |
449-
| RPM (3×2 props) | **78.1 µs** | ± 0.7 µs |
450-
| COV Sub/Unsub | **132.8 µs** | ± 2.8 µs |
451-
| WhoIs | **30.7 µs** | ± 3.5 µs |
452-
453-
Sequential throughput: **~14,000 ops/s** (ReadProperty)
454-
455-
### 7.2 Concurrency Scaling
456-
457-
| Coroutines | Throughput | Per-coroutine |
458-
|---|---|---|
459-
| 1 | **13,258 ops/s** | 13,258 /s |
460-
| 5 | 4,534 ops/s | 907 /s |
461-
| 10 | 2,636 ops/s | 264 /s |
462-
| 25 | 1,116 ops/s | 45 /s |
463-
464-
Note: JMH measures one benchmark iteration at a time. Each iteration launches N coroutines
465-
that each do a full ReadProperty round-trip. The throughput decrease at higher concurrency
466-
reflects the cost of N sequential round-trips per iteration, not a server bottleneck.
467-
468-
### 7.3 JNA/FFI Overhead
469-
470-
| Operation | Mean | ± Error |
471-
|---|---|---|
472-
| ObjectIdentifier (create) | **10.9 µs** | ± 9.7 µs |
473-
| ObjectIdentifier (display) | **14.9 µs** | ± 5.7 µs |
474-
| PropertyValue (Real) | **3.2 ns** | ± 0.6 ns |
475-
| PropertyValue (String) | **3.1 ns** | ± 0.05 ns |
476-
| PropertyValue (Unsigned) | **4.1 ns** | ± 0.04 ns |
477-
478-
Simple Kotlin enum construction (PropertyValue variants) is **~3 ns** — pure JVM allocation.
479-
ObjectIdentifier creation crosses the JNA FFI boundary at **~11 µs** per call (higher variance
480-
due to JNA native library loading and GC interaction).
481-
482-
### 7.4 Object Creation
483-
484-
| Operation | Mean | ± Error |
485-
|---|---|---|
486-
| Add AnalogInput | **39.0 µs** | ± 13.1 µs |
487-
| Add 5 mixed objects | **190.4 µs** | ± 72.4 µs |
488-
489-
Server object creation includes FFI crossing + Rust object construction + database insertion.
490-
491-
### 7.5 Kotlin API Overhead Analysis
492-
493-
| Transport | Rust Latency | Kotlin Latency | Overhead |
494-
|---|---|---|---|
495-
| BIP ReadProperty | 27.5 µs | ~74 µs | ~46 µs (+2.7×) |
496-
| BIP WriteProperty | 28.7 µs | ~81 µs | ~52 µs (+2.8×) |
497-
| BIP RPM (3×2) | 32.0 µs | ~78 µs | ~46 µs (+2.4×) |
498-
499-
Kotlin/JNA overhead is **~46–52 µs** per async round-trip, lower than Python's ~80 µs.
500-
The overhead comes from: UniFFI async dispatch (~10 µs), JNA FFI boundary (~11 µs per crossing),
501-
and Kotlin coroutine suspension/resumption (~25 µs).
502-
503-
---
504-
505-
## 8. Key Takeaways
436+
## 7. Key Takeaways
506437

507438
- **Encoding is fast**: Full RP encode/decode stack in ~131 ns (CPU-bound, no allocation hot paths thanks to `Bytes` zero-copy)
508439
- **BIP throughput scales linearly**: 40K/s single-client → 161K/s at 50 clients with sub-millisecond p99
440+
- **Concurrent dispatch unlocks RwLock parallelism**: Server now spawns per-request tasks — multiple ReadProperty requests run truly concurrently via `db.read()`. Quick benchmarks show ~44% read throughput improvement (full run pending)
441+
- **Multi-device batch API**: Client `read_property_from_devices()` / `read_property_multiple_from_devices()` / `write_property_to_devices()` fan out to N devices concurrently with configurable `max_concurrent` (default 32). Available in Rust and Python
509442
- **Object count doesn't matter**: 100 → 5,000 objects shows zero latency degradation (RwLock contention minimal)
510443
- **COV is reliable**: 100% notification delivery at 25 concurrent subscriptions
511444
- **SC overhead is ~2.5×**: TLS WebSocket adds ~40 µs per operation vs raw UDP — acceptable for secure deployments
@@ -515,8 +448,6 @@ and Kotlin coroutine suspension/resumption (~25 µs).
515448
- **Musl/Alpine parity**: Docker (static musl) matches native performance — no penalty for containerized deployment
516449
- **Python API is production-ready**: ~80 µs PyO3 overhead per call; 36K concurrent ops/s from Python matches pure Rust throughput
517450
- **SC from Python works**: ScHub + SC client/server all work via PyO3; 29K ops/s at 25 concurrent clients
518-
- **Kotlin/JNA is faster than Python**: ~46 µs UniFFI overhead per async call vs Python's ~80 µs; 14K sequential ops/s
519-
- **JNA primitive overhead is negligible**: PropertyValue enum construction is ~3 ns (pure JVM); ObjectIdentifier FFI crossing ~11 µs
520451

521452
---
522453

@@ -529,6 +460,9 @@ cargo bench -p bacnet-benchmarks
529460
# Individual benchmark
530461
cargo bench -p bacnet-benchmarks --bench bip_latency
531462

463+
# Quick run (reduced samples, ~10s per suite instead of ~60s)
464+
cargo bench -p bacnet-benchmarks --bench bip_latency -- --sample-size 10 --warm-up-time 1
465+
532466
# Stress tests
533467
cargo run --release -p bacnet-benchmarks --bin stress-test -- clients --steps 1,5,10,25,50 --duration 5
534468
cargo run --release -p bacnet-benchmarks --bin stress-test -- objects --steps 100,500,1000,2500,5000 --duration 5
@@ -553,10 +487,4 @@ uv run pytest bench_py_client_rust_server.py -v # BIP: Py client → Rust serv
553487
uv run pytest bench_rust_client_py_server.py -v # BIP: Rust client → Py server
554488
uv run pytest bench_py_py.py -v # BIP: Py ↔ Py
555489
uv run pytest bench_sc.py -v # SC: Py client → Rust server via ScHub
556-
557-
# Kotlin/JVM JMH benchmarks (requires JDK 21+)
558-
cd java
559-
./build-local.sh --release # Build native lib + Kotlin bindings + JAR
560-
./gradlew :benchmarks:jmh # Full benchmark suite (~10 min)
561-
# Results: java/benchmarks/build/reports/jmh/results.json
562490
```

0 commit comments

Comments
 (0)