Performance refactor: ETS routing, Forwarder, and slim Router#25
Merged
Conversation
Bump Elixir to ~> 1.14, circuits_uart to 1.5.5, and ex_doc to 0.34. Add :xmerl to extra_applications for parser tests. Add mix benchmark alias for TCP throughput bench. Co-authored-by: Robin Hilliard <robinhilliard@users.noreply.github.com>
- Three-layer router test suite (delegate, pipeline, routing, API) - Test support harness with TestMavlink dialect fixtures - TCP 1v1 benchmark harness; baseline ~1379 msg/s on CI host - elixirc_paths for test/support; consolidate_protocols disabled for bench Co-authored-by: Robin Hilliard <robinhilliard@users.noreply.github.com>
- Add RouteTable GenServer owning public ETS tables for routes, wire peers,
and subscribers with direct subscriber dispatch
- Add Forwarder for ETS-based wire and subscriber routing
- Promote LocalConnection to GenServer for pack_and_send API
- Slim Router: delegate subscribe/unsubscribe to RouteTable, routing to
Forwarder, handle {:mavlink_forward, ...} for wire I/O
- Update supervisor to start RouteTable and LocalConnection with one_for_one
- Fix TCPOutConnection gen_tcp.send bug
- Add route_table and forwarder tests; update existing tests for ETS routes
- Record after-phase4 TCP benchmark (1092 msg/s vs 1379 baseline on CI)
Co-authored-by: Robin Hilliard <robinhilliard@users.noreply.github.com>
The elixir:1.9.1-slim image is based on EOL Debian stretch; apt-get update fails with 404 before any project code runs. Align CI with mix.exs (~> 1.14) and install erlang-xmerl for parser tests. Co-authored-by: Robin Hilliard <robinhilliard@users.noreply.github.com>
- Add ConnectionSupervisor (DynamicSupervisor) to own wire connection processes - Promote TCPOut, UDPOut, UDPIn, and Serial to GenServers with direct I/O - Add WireConnection helper; Forwarder sends mavlink_forward_raw on wire path - Slim Router to API facade and test connection registry only - Rename delegate parsers to parse_incoming/3 to avoid GenServer clashes - Update integration tests to inject I/O into connection processes - Record after-phase5 TCP benchmark (895 msg/s on CI host) Co-authored-by: Robin Hilliard <robinhilliard@users.noreply.github.com>
Profile runs show both architectures are backlog-limited (~170k-190k TCP messages queued) with similar rates (~900-960 msg/s). Bottleneck is single-process active TCP parsing, not ETS routing or subscriber indexing. Co-authored-by: Robin Hilliard <robinhilliard@users.noreply.github.com>
- TCPOutConnection uses {:active, :once} with re-arm after each message
- GCSCounter uses :atomics instead of Agent for benchmark subscriber
- Throughput improves from ~900 to ~1200 msg/s; TCP mailbox max drops ~171k to ~17k
Co-authored-by: Robin Hilliard <robinhilliard@users.noreply.github.com>
After ingesting one {:tcp, ...} message, drain additional {:tcp, ^socket, _}
messages from the GenServer mailbox (including self-sent empty triggers for
multi-frame buffers) before calling inet:setopts once.
Fixes drain_tcp/2 argument order when used in a pipeline (state, socket).
Benchmark (mix benchmark.backpressure): ~200k msg/s vs ~1.2k with active-once
alone; the prior ceiling was GenServer dispatch overhead per frame in coalesced
TCP segments, not parse throughput.
Co-authored-by: Robin Hilliard <robinhilliard@users.noreply.github.com>
Introduce MAVLink.MailboxDrain with selective-receive drain functions for
tcp, udp, and circuits_uart message shapes. Refactor TCPOut to use the helper.
UDPIn and UDPOut now default to {:active, :once} with mailbox drain and a
single inet:setopts rearm per batch, matching TCPOut backpressure.
Serial uses passive UART mode (active: false) with a read loop for kernel
backpressure, plus circuits_uart mailbox drain for coalesced chunks and
self-sent multi-frame triggers. Test mode keeps direct message injection.
Co-authored-by: Robin Hilliard <robinhilliard@users.noreply.github.com>
Multi-vehicle / multi-GCS benchmark with realistic telemetry mix (VFR_HUD,
HEARTBEAT, DATA16) across N TCP vehicles and M GCS subscribers. Includes
throughput and profile harnesses plus profile_indexing_compare.sh for
before/after indexing comparison.
RouteTable indexing:
- :mavlink_subscriber_index bag keyed by {source_system, message|:all}
- matching_subscribers/1 uses :ets.lookup/2 on index keys instead of tab2list
- matching_peers/2 uses direct lookup for exact targets
- all_wire_peers/0 uses :ets.match_object/2
Frame fixtures extended for VfrHud and Data16. GCSCounter supports multi-slot
atomics with subscribe barrier.
Co-authored-by: Robin Hilliard <robinhilliard@users.noreply.github.com>
Bench vehicles no longer register as wire peers, preventing broadcast cross-forward between TCP connections that caused only one vehicle to deliver to GCS subscribers. Add delivery balance reporting, routing microbench, and compare scripts. Measured with 100 subscribers: indexing ~2.6x faster on matching_subscribers; end-to-end fleet throughput ~10% faster at 4 vehicles / 5 GCS. Co-authored-by: Robin Hilliard <robinhilliard@users.noreply.github.com>
- Validate checksum over binary slices instead of bin_to_list - Add prepare_for_route/2 to skip payload unpack when only as_raw subscribers need delivery; RouteTable tracks decode requirements - Subscribe with as_raw: true delivers mavlink_1_raw/mavlink_2_raw bytes - Generate array field unpack as bitstring comprehensions; add message_module_for/1 for routing without full decode - Regenerate test dialect via mix mavlink (DialectFixture) Co-authored-by: Robin Hilliard <robinhilliard@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Parse hot-path optimizations
Binary CRC (drop
bin_to_list)validate_checksum/2andvalidate_and_unpack/2compute X.25 CRC directly overbinary_part(raw, 1, len)using the existing binaryx25_crc/2path.Optional raw-byte subscriber path
Router.subscribe(as_raw: true)delivers wire bytes (mavlink_1_raw/mavlink_2_raw) instead of a decoded struct.Frame.prepare_for_route/2, which skips payload unpack whenRouteTable.decode_required?/2is false (e.g. broadcast message + onlyas_rawsubscribers, no wire peers).RouteTablecaches subscriber decode flags and wire-peer count; dialect gainsmessage_module_for/1for message-type matching without unpack.Array unpack as bitstring comprehensions
mix mavlinknow generates(for <<elem::... <- field_f>>, do: elem)for array fields instead ofunpack_array/2.Tests
test/frame_test.exs: binary vs list CRC equivalence,prepare_for_routeskip-unpack, Data16 comprehension unpacktest/route_table_test.exs:as_rawwire-byte deliveryMIX_ENV=test mix test --no-start)