Skip to content
Docs
foxborne.comRequest a pilot

Multirotor drones

How Foxborne rebuilds a multirotor incident from the flight log, the companion journal, process samples and the ground receive log, worked through example incident INC-0142, where telemetry stopped within a second of a companion service being killed.

ExplanationMarkdown
On this page10

On a multirotor, the flight controller rarely works alone. A companion computer runs perception and routes MAVLink to the radio, and range control listens on the ground. When telemetry stops, the review board asks: did the radio fail, or did the companion computer stop talking?

Example incident INC-0142 answers that question from four sources. Every value on this page comes from the console's example dataset.

The example platform#

The example dataset lists the Q4 recon quad for route reconnaissance, at 6.8 kg and 12 m/s. Its parts, and the log that records each one:

PartIn the example datasetRecorded by
Flight controllerPixhawk 6C, PX4 v1.15.4The flight log
Companion computerJetson Orin NX 16 GB, Ubuntu 22.04, running perception_node, mavlink-routerd and the collectorThe journal and the collector's process samples
Mesh radioMANET, EthernetNothing. It keeps no log Foxborne can read.
GNSS receiveru-blox M10The flight log
Depth cameraStereo depth, 10 HzThe flight log
Battery6S Li-ion, 22 AhThe flight log

The flight controller talks to the companion over a UART at 921600 baud, and the companion reaches the radio over Ethernet. Telemetry to the ground passes through mavlink-routerd on the companion, which is where INC-0142 happens. Vehicle parts and their logs maps each part in detail.

The sources#

INC-0142 happened on UAS-04, run R-0931, on the return leg of Route Iron on 24 Sep 2026. The run aligned four files with a widest bound of ±46 ms.

SourceFileClock and anchorBound
Flight log14_13_19.ulgPX4 boot clock, anchored by GPS time from sensor_gps, 1,081 fixes±40 ms
Companion journaluas04-orin-journal.jsonCompanion wall clock, chrony synchronized, offset −1.8 ms±6 ms
Process samplesuas04-orin-samples.jsonlCompanion monotonic clock, sharing boot ID 9b2e4c1d with the journal±6 ms
Ground receive logrc1-receive.jsonlRange control receiver clock, NTP, offset +0.4 ms±15 ms plus latency: median 96 ms, 95th percentile 412 ms

What happened#

The report's sequence has eight events. The level column is the one the report shows for each line.

EventTime, UTCSourceWhat it showsLevel
E114:32:01.204Process samplesperception_node memory above 6,144 MiB for 3 samplesObserved
E214:32:04.079Flight logLast obstacle_distance sample before a 7.5 s stallOrder against E3 unknown
E314:32:04.118JournalKernel killed perception_node (out of memory)Order against E2 unknown
E414:32:04.133Journalperception.service main process exited, status 9/KILLObserved
E514:32:04.152Journalmavlink-router.service stoppedObserved
E614:32:04.912Ground receiveHeartbeat due and not received. Nothing for 6.96 sCorrelated
E714:32:09.214Journalperception.service startedObserved
E814:32:10.874Ground receiveTelemetry received againObserved

Memory led the way. The collector's samples read 6,188, 6,402 and 6,655 MiB, and the rule confirmed the climb at 14:32:03.204. The kernel then killed process 2213, perception_node, with 6,823,516 kB of anonymous memory resident.

mavlink-router.service stopped 34 ms after the kill, and 19 ms after systemd recorded the exit of perception.service. Range control last heard system ID 4 at 14:32:03.912 and heard nothing more until 14:32:10.874.

The router came back with the service. systemd started it at 14:32:09.203, and mavlink-routerd opened the flight controller UART at 14:32:10.702 and the mesh link at 14:32:10.718. Range control heard the vehicle again 156 ms later.

The flight log saw the same gap from the other side. At 14:32:06.510, telemetry_status on TELEM2 reported no ground station heartbeat for 2.5 s, and PX4 logged GCS connection regained at 14:32:12.944.

The finding#

The investigator wrote the finding as a Hypothesis:

Telemetry stopped reaching range control within one second of the kernel killing perception_node for running out of memory. mavlink-router.service stopped 34 ms after the kill, 19 ms after systemd recorded the exit of perception.service, and came back with it, which suggests the router unit is bound to the perception unit. The binding is a hypothesis until the unit files are checked.

The report names the check that settles it. On the bench unit, run systemctl show mavlink-router.service -p BindsTo -p PartOf -p Requires. If the binding is there, kill perception_node with SIGKILL, confirm the gap reproduces and record /proc/pressure/memory at 10 Hz.

Where the clocks decide the order#

Two pairs in this sequence show how bounds work between sources. Alignment and event order covers the method.

PairApartCombined boundResult
E2, flight log ±40 ms, and E3, journal ±6 ms39 ms±46 msOrder unknown
E5, journal ±6 ms, and E6, receipt ±15 ms plus 412 ms latency0.76 s±0.43 sOrdered, filed as correlated

The stall and the kill sit inside their combined bound, so Foxborne claims no order between them. The heartbeat was due well after the router stopped, even allowing for the receiver's latency.

What the evidence shows#

  • perception_node held more than 6,144 MiB for three samples, and the kernel killed it for running out of memory.
  • The router stopped within 34 ms of the kill. The next heartbeat was due at range control 0.76 s later, and none arrived until the router reopened the mesh link.
  • PX4 recorded no data link failsafe. The 6.96 s gap is shorter than COM_DL_LOSS_T (10 s), so none was expected.

What it does not show#

  • Which came first, the stall or the kill. They are 39 ms apart, inside their 46 ms combined bound.
  • Why the router stopped. A unit binding would explain it, but the unit files have not been checked.
  • What the router did during the gap. mavlink-routerd wrote nothing between 14:32:04.152 and 14:32:10.702.
  • When the last heartbeat left the vehicle. With 412 ms of latency, it left between 14:32:03.485 and 14:32:03.927.
  • What the radio did. It keeps no log, so its state is never observed.

Memory on a computer that shares it with the GPU#

The Q4 carries the Orin NX 16 GB, where GPU buffers share memory with processes. At the last sample before the kill, nvmap held 8,212 MiB. That is why the memory_pressure rule holds Q4 vehicles to 6,144 MiB for 3 samples, while T4 vehicles use 24,576 MiB.

The journal also held an early warning. At 14:13:53.118, 18 minutes before the kill, perception_node logged map tile cache: cache_max_mb not set; eviction disabled. The report keeps "Why did the tile cache grow without a limit on this build?" as an open question.

What to send#

All four sources were present in INC-0142, and each one carried events the others could not.

SourceNeedIn INC-0142
PX4 flight log, .ulgRequired14_13_19.ulg, from the fleet log archive: the stall, the vehicle's own link view and the GPS anchor
Companion journal, journalctl -o jsonRecommendeduas04-orin-journal.json, 18,442 records: the kill, the exit and the router stop
Ground receive log, .jsonlRecommendedrc1-receive.jsonl, 1,359 records: the telemetry gap
Collector samples, .jsonlOptionaluas04-orin-samples.jsonl, 2,840 records: the memory climb

Without the journal, the kill and the router stop would not be on the timeline at all. Platforms gives the full list for PX4 vehicles.