add times to traceroute displays. (#2999)

pull/3001/head
DaneEvans 2025-09-06 23:34:03 +10:00 zatwierdzone przez GitHub
rodzic 80a7b9e081
commit 99938e97bd
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
4 zmienionych plików z 75 dodań i 42 usunięć

Wyświetl plik

@ -84,7 +84,7 @@ data class MetricsState(
val powerMetrics: List<Telemetry> = emptyList(),
val hostMetrics: List<Telemetry> = emptyList(),
val tracerouteRequests: List<MeshLog> = emptyList(),
val tracerouteResults: List<MeshPacket> = emptyList(),
val tracerouteResults: List<MeshLog> = emptyList(),
val positionLogs: List<Position> = emptyList(),
val deviceHardware: DeviceHardware? = null,
val isLocalDevice: Boolean = false,
@ -321,7 +321,7 @@ constructor(
combine(
meshLogRepository.getLogsFrom(nodeNum = 0, PortNum.TRACEROUTE_APP_VALUE),
meshLogRepository.getMeshPacketsFrom(destNum, PortNum.TRACEROUTE_APP_VALUE),
meshLogRepository.getLogsFrom(destNum ?: 0, PortNum.TRACEROUTE_APP_VALUE),
) { request, response ->
_state.update { state ->
state.copy(

Wyświetl plik

@ -22,48 +22,51 @@ import com.geeksville.mesh.MeshProtos.RouteDiscovery
import com.geeksville.mesh.Portnums
val MeshProtos.MeshPacket.fullRouteDiscovery: RouteDiscovery?
get() = with(decoded) {
if (hasDecoded() && !wantResponse && portnum == Portnums.PortNum.TRACEROUTE_APP) {
runCatching { RouteDiscovery.parseFrom(payload).toBuilder() }.getOrNull()?.apply {
val fullRoute = listOf(to) + routeList + from
clearRoute()
addAllRoute(fullRoute)
get() =
with(decoded) {
if (hasDecoded() && !wantResponse && portnum == Portnums.PortNum.TRACEROUTE_APP) {
runCatching { RouteDiscovery.parseFrom(payload).toBuilder() }
.getOrNull()
?.apply {
val fullRoute = listOf(to) + routeList + from
clearRoute()
addAllRoute(fullRoute)
val fullRouteBack = listOf(from) + routeBackList + to
clearRouteBack()
if (hopStart > 0 && snrBackCount > 0) { // otherwise back route is invalid
addAllRouteBack(fullRouteBack)
}
}?.build()
} else {
null
val fullRouteBack = listOf(from) + routeBackList + to
clearRouteBack()
if (hopStart > 0 && snrBackCount > 0) { // otherwise back route is invalid
addAllRouteBack(fullRouteBack)
}
}
?.build()
} else {
null
}
}
}
@Suppress("MagicNumber")
private fun formatTraceroutePath(nodesList: List<String>, snrList: List<Int>): String {
// nodesList should include both origin and destination nodes
// origin will not have an SNR value, but destination should
val snrStr = if (snrList.size == nodesList.size - 1) {
snrList
} else {
// use unknown SNR for entire route if snrList has invalid size
List(nodesList.size - 1) { -128 }
}.map { snr ->
val str = if (snr == -128) "?" else "${snr / 4f}"
"$str dB"
}
val snrStr =
if (snrList.size == nodesList.size - 1) {
snrList
} else {
// use unknown SNR for entire route if snrList has invalid size
List(nodesList.size - 1) { -128 }
}
.map { snr ->
val str = if (snr == -128) "?" else "${snr / 4f}"
"$str dB"
}
return nodesList.map { userName ->
"$userName"
}.flatMapIndexed { i, nodeStr ->
if (i == 0) listOf(nodeStr) else listOf(snrStr[i - 1], nodeStr)
}.joinToString("\n")
return nodesList
.map { userName -> "$userName" }
.flatMapIndexed { i, nodeStr -> if (i == 0) listOf(nodeStr) else listOf(snrStr[i - 1], nodeStr) }
.joinToString("\n")
}
private fun RouteDiscovery.getTracerouteResponse(
getUser: (nodeNum: Int) -> String,
): String = buildString {
private fun RouteDiscovery.getTracerouteResponse(getUser: (nodeNum: Int) -> String): String = buildString {
if (routeList.isNotEmpty()) {
append("Route traced toward destination:\n\n")
append(formatTraceroutePath(routeList.map(getUser), snrTowardsList))
@ -75,6 +78,10 @@ private fun RouteDiscovery.getTracerouteResponse(
}
}
fun MeshProtos.MeshPacket.getTracerouteResponse(
getUser: (nodeNum: Int) -> String,
): String? = fullRouteDiscovery?.getTracerouteResponse(getUser)
fun MeshProtos.MeshPacket.getTracerouteResponse(getUser: (nodeNum: Int) -> String): String? =
fullRouteDiscovery?.getTracerouteResponse(getUser)
/** Returns a traceroute response string only when the result is complete (both directions). */
fun MeshProtos.MeshPacket.getFullTracerouteResponse(getUser: (nodeNum: Int) -> String): String? = fullRouteDiscovery
?.takeIf { it.routeList.isNotEmpty() && it.routeBackList.isNotEmpty() }
?.getTracerouteResponse(getUser)

Wyświetl plik

@ -73,7 +73,7 @@ import com.geeksville.mesh.fromRadio
import com.geeksville.mesh.model.DeviceVersion
import com.geeksville.mesh.model.NO_DEVICE_SELECTED
import com.geeksville.mesh.model.Node
import com.geeksville.mesh.model.getTracerouteResponse
import com.geeksville.mesh.model.getFullTracerouteResponse
import com.geeksville.mesh.position
import com.geeksville.mesh.repository.datastore.RadioConfigRepository
import com.geeksville.mesh.repository.location.LocationRepository
@ -148,6 +148,8 @@ class MeshService :
@Inject lateinit var meshPrefs: MeshPrefs
private val tracerouteStartTimes = ConcurrentHashMap<Int, Long>()
companion object : Logging {
// Intents broadcast by MeshService
@ -848,7 +850,21 @@ class MeshService :
}
Portnums.PortNum.TRACEROUTE_APP_VALUE -> {
radioConfigRepository.setTracerouteResponse(packet.getTracerouteResponse(::getUserName))
val full = packet.getFullTracerouteResponse(::getUserName)
if (full != null) {
val requestId = packet.decoded.requestId
val start = tracerouteStartTimes.remove(requestId)
val response =
if (start != null) {
val elapsedMs = System.currentTimeMillis() - start
val seconds = elapsedMs / 1000.0
info("Traceroute $requestId complete in $seconds s")
"$full\n\nDuration: ${"%.1f".format(seconds)} s"
} else {
full
}
radioConfigRepository.setTracerouteResponse(response)
}
}
else -> debug("No custom processing needed for ${data.portnumValue}")
@ -2376,6 +2392,7 @@ class MeshService :
}
override fun requestTraceroute(requestId: Int, destNum: Int) = toRemoteExceptions {
tracerouteStartTimes[requestId] = System.currentTimeMillis()
packetHandler.sendToRadio(
newMeshPacketTo(destNum).buildMeshPacket(
wantAck = true,

Wyświetl plik

@ -63,6 +63,7 @@ import com.geeksville.mesh.model.fullRouteDiscovery
import com.geeksville.mesh.model.getTracerouteResponse
import com.geeksville.mesh.ui.common.components.SimpleAlertDialog
import com.geeksville.mesh.ui.common.theme.AppTheme
import com.geeksville.mesh.ui.metrics.CommonCharts.MS_PER_SEC
import java.text.DateFormat
@OptIn(ExperimentalFoundationApi::class)
@ -88,9 +89,9 @@ fun TracerouteLogScreen(modifier: Modifier = Modifier, viewModel: MetricsViewMod
items(state.tracerouteRequests, key = { it.uuid }) { log ->
val result =
remember(state.tracerouteRequests) {
state.tracerouteResults.find { it.decoded.requestId == log.fromRadio.packet.id }
state.tracerouteResults.find { it.fromRadio.packet.decoded.requestId == log.fromRadio.packet.id }
}
val route = remember(result) { result?.fullRouteDiscovery }
val route = remember(result) { result?.fromRadio?.packet?.fullRouteDiscovery }
val time = dateFormat.format(log.received_date)
val (text, icon) = route.getTextAndIcon()
@ -103,7 +104,15 @@ fun TracerouteLogScreen(modifier: Modifier = Modifier, viewModel: MetricsViewMod
modifier =
Modifier.combinedClickable(onLongClick = { expanded = true }) {
if (result != null) {
showDialog = result.getTracerouteResponse(::getUsername)
val full = route
if (full != null && full.routeList.isNotEmpty() && full.routeBackList.isNotEmpty()) {
val elapsedMs = (result.received_date - log.received_date).coerceAtLeast(0)
val seconds = elapsedMs.toDouble() / MS_PER_SEC
val base = result.fromRadio.packet.getTracerouteResponse(::getUsername)
showDialog = "$base\n\nDuration: ${"%.1f".format(seconds)} s"
} else {
showDialog = result.fromRadio.packet.getTracerouteResponse(::getUsername)
}
}
},
)