Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,14 @@ open class MeshLogRepositoryImpl(
.distinctUntilChanged()
.conflate()

override fun getInboundRequestLogs(portNum: PortNum): Flow<List<MeshLog>> = dbManager
.observeCurrentDb { it.meshLogDao().getLogsOnPort(portNum.value, MeshLog.NODE_NUM_LOCAL, DEFAULT_MAX_LOGS) }
.map { list ->
list.map { it.asExternalModel() }.filter { it.fromRadio.packet?.decoded?.want_response == true }
}
.distinctUntilChanged()
.flowOn(dispatchers.io)

private fun parseTelemetryLog(log: MeshLog): Telemetry? = runCatching {
val decoded = log.fromRadio.packet?.decoded ?: return@runCatching null
// Requests for telemetry (want_response = true) should not be logged as data points.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,16 @@ interface MeshLogDao {
@Query(LOGS_FROM_QUERY)
fun getLogsFrom(fromNum: Int, portNum: Int, maxItem: Int): Flow<List<MeshLog>>

/** Retrieves [MeshLog]s on 'port_num' (PortNum) from any node except [excludeFromNum]. */
@Query(
"""
SELECT * FROM log
WHERE port_num = :portNum AND from_num != :excludeFromNum
ORDER BY received_date DESC LIMIT :maxItem
""",
)
fun getLogsOnPort(portNum: Int, excludeFromNum: Int, maxItem: Int): Flow<List<MeshLog>>

@Insert suspend fun insert(log: MeshLog)

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,19 @@ class MeshLogDaoTest {
assertEquals(1, survivorStillPresent.size, "unselected log from a different fromNum should remain")
}

@Test
fun testGetLogsOnPortSpansNodesAndSkipsExcludedNode() = runTest {
val traceroute = PortNum.TRACEROUTE_APP.value
meshLogDao.insert(logEntry("ours", portNum = traceroute, time = 100).copy(fromNum = 0))
meshLogDao.insert(logEntry("node-a", portNum = traceroute, time = 200))
meshLogDao.insert(logEntry("node-b", portNum = traceroute, time = 300).copy(fromNum = 999))
meshLogDao.insert(logEntry("other-port", time = 400))

val logs = meshLogDao.getLogsOnPort(traceroute, excludeFromNum = 0, maxItem = Int.MAX_VALUE).first()

assertEquals(listOf("node-b", "node-a"), logs.map { it.uuid })
}

@Test
fun testDeleteLogsByUuidAtomicRollsBackEarlierChunkWhenLaterChunkFails() = runTest {
val firstUuid = "first-chunk"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ interface MeshLogRepository {
*/
fun getRequestLogs(targetNodeNum: Int, portNum: PortNum): Flow<List<MeshLog>>

/** Retrieves the request logs other nodes sent on [portNum], where `want_response` is true. */
fun getInboundRequestLogs(portNum: PortNum): Flow<List<MeshLog>>

/** Returns the cached [MyNodeInfo] from the system logs. */
fun getMyNodeInfo(): Flow<MyNodeInfo?>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,14 @@ class FakeMeshLogRepository :
}
}

override fun getInboundRequestLogs(portNum: PortNum): Flow<List<MeshLog>> = logsFlow.map {
it.filter { log ->
log.fromNum != MeshLog.NODE_NUM_LOCAL &&
log.portNum == portNum.value &&
log.fromRadio.packet?.decoded?.want_response == true
}
}

override fun getMyNodeInfo(): Flow<MyNodeInfo?> = MutableStateFlow(null)

override suspend fun insert(log: MeshLog) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ private fun rememberTelemetricFeatures(
TelemetricFeature(
titleRes = LogsType.TRACEROUTE.titleRes,
icon = LogsType.TRACEROUTE.icon,
requestAction = { NodeMenuAction.TraceRoute(it) },
requestAction = if (isLocal) null else { n -> NodeMenuAction.TraceRoute(n) },
logsType = LogsType.TRACEROUTE,
cooldownTimestamp = lastTracerouteTime,
isVisible = { !isLocal },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.transformLatest
Expand Down Expand Up @@ -105,6 +106,12 @@ constructor(
meshLogRepository.getLogsFrom(effectiveNodeId, PortNum.NEIGHBORINFO_APP.value).onStart {
emit(emptyList())
},
// Traceroutes other nodes ran to us are logged under each tracer, so our own page gathers them all.
if (effectiveNodeId == MeshLog.NODE_NUM_LOCAL) {
meshLogRepository.getInboundRequestLogs(PortNum.TRACEROUTE_APP).onStart { emit(emptyList()) }
} else {
flowOf(emptyList())
},
) { args: Array<List<Any?>> ->
@Suppress("UNCHECKED_CAST")
LogsGroup(
Expand All @@ -114,6 +121,7 @@ constructor(
pax = args[3] as List<MeshLog>,
trRes = args[4] as List<MeshLog>,
niRes = args[5] as List<MeshLog>,
trInbound = args[6] as List<MeshLog>,
)
}

Expand Down Expand Up @@ -234,7 +242,8 @@ constructor(
tracerouteRequests = trReqs,
tracerouteResults = logs.trRes,
tracerouteObserved = observedTracerouteResponses(trReqs, logs.trRes),
tracerouteTracedBy = tracedByTracerouteRequests(logs.trRes, identity.ourNode?.num),
tracerouteTracedBy =
tracedByTracerouteRequests(if (isLocal) logs.trInbound else logs.trRes, identity.ourNode?.num),
neighborInfoRequests = niReqs,
neighborInfoResults = logs.niRes,
firmwareEdition = metadata.edition,
Expand Down Expand Up @@ -289,6 +298,7 @@ constructor(
val pax: List<MeshLog>,
val trRes: List<MeshLog>,
val niRes: List<MeshLog>,
val trInbound: List<MeshLog>,
)

private data class IdentityGroup(val ourNode: Node?, val myInfo: MyNodeInfo?, val profile: DeviceProfile)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ class TracerouteHistoryUseCaseTest {
// The fake maps every node to the local log id; the real repository does that only for our own node.
private val nodeRepository =
object : NodeRepository by fakeNodeRepository {
override fun effectiveLogNodeId(nodeNum: Int): Flow<Int> = flowOf(nodeNum)
override fun effectiveLogNodeId(nodeNum: Int): Flow<Int> =
flowOf(if (nodeNum == OUR_NODE) MeshLog.NODE_NUM_LOCAL else nodeNum)
}
private val meshLogRepository = FakeMeshLogRepository()
private val nodeRequestActions =
Expand Down Expand Up @@ -148,6 +149,27 @@ class TracerouteHistoryUseCaseTest {
assertFalse(metrics.hasTracerouteLogs())
}

@Test
fun `our own history gathers the traces every node ran to us`() = runTest {
meshLogRepository.setLogs(
listOf(
inboundRequest(from = NODE_X, to = OUR_NODE, receivedDate = 2_000L),
inboundRequest(from = OTHER_NODE, to = OUR_NODE, receivedDate = 3_000L),
inboundRequest(from = NODE_X, to = OTHER_NODE, receivedDate = 4_000L),
),
)

val state =
useCase(OUR_NODE).first {
it.metricsState.node?.num == OUR_NODE && it.metricsState.tracerouteTracedBy.isNotEmpty()
}
val history = state.metricsState.history()

assertTrue(history.all { it.kind == TracerouteKind.TRACED_BY })
assertEquals(listOf(OTHER_NODE, NODE_X), history.map { it.result?.fromRadio?.packet?.from })
assertTrue(LogsType.TRACEROUTE in state.availableLogs)
}

private fun log(uuid: String, fromNum: Int, receivedDate: Long, packet: MeshPacket) = MeshLog(
uuid = uuid,
message_type = "Packet",
Expand Down