refactor(MeshLog): add fields for query optimization

- Adds `from_num` and `port_num` fields as indices to the `MeshLog` entity to improve query performance.
- Sets default values for new columns for auto-migration and backward compatibility.
This commit is contained in:
andrekir 2024-09-26 05:34:37 -03:00 committed by Andre K
parent a075dfbd3a
commit 717c932ae2
7 changed files with 800 additions and 55 deletions

View file

@ -9,6 +9,7 @@ import com.geeksville.mesh.android.Logging
import com.google.protobuf.InvalidProtocolBufferException
import kotlinx.serialization.json.Json
@Suppress("TooManyFunctions")
class Converters : Logging {
@TypeConverter
fun dataFromString(value: String): DataPacket {
@ -22,6 +23,21 @@ class Converters : Logging {
return json.encodeToString(DataPacket.serializer(), value)
}
@TypeConverter
fun bytesToFromRadio(bytes: ByteArray): MeshProtos.FromRadio {
return try {
MeshProtos.FromRadio.parseFrom(bytes)
} catch (ex: InvalidProtocolBufferException) {
errormsg("bytesToFromRadio TypeConverter error:", ex)
MeshProtos.FromRadio.getDefaultInstance()
}
}
@TypeConverter
fun fromRadioToBytes(value: MeshProtos.FromRadio): ByteArray? {
return value.toByteArray()
}
@TypeConverter
fun bytesToUser(bytes: ByteArray): MeshProtos.User {
return try {

View file

@ -26,17 +26,15 @@ class MeshLogRepository @Inject constructor(private val meshLogDaoLazy: dagger.L
meshLogDao.getAllLogsInReceiveOrder(maxItems)
}
@OptIn(ExperimentalCoroutinesApi::class)
fun getMeshPacketsFrom(nodeNum: Int) = meshLogDao.getAllLogs(MAX_MESH_PACKETS)
.mapLatest { list -> list.mapNotNull { it.meshPacket }.filter { it.from == nodeNum } }
.distinctUntilChanged()
.flowOn(Dispatchers.IO)
private fun parseTelemetryLog(log: MeshLog): Telemetry? =
runCatching { Telemetry.parseFrom(log.fromRadio.packet.decoded.payload) }.getOrNull()
@OptIn(ExperimentalCoroutinesApi::class)
fun getTelemetryFrom(nodeNum: Int) = getMeshPacketsFrom(nodeNum).mapLatest { list ->
list.filter { it.hasDecoded() && it.decoded.portnum == Portnums.PortNum.TELEMETRY_APP }
.mapNotNull { runCatching { Telemetry.parseFrom(it.decoded.payload) }.getOrNull() }
}.flowOn(Dispatchers.IO)
fun getTelemetryFrom(nodeNum: Int): Flow<List<Telemetry>> =
meshLogDao.getLogsFrom(nodeNum, Portnums.PortNum.TELEMETRY_APP_VALUE, MAX_MESH_PACKETS)
.distinctUntilChanged()
.mapLatest { list -> list.mapNotNull(::parseTelemetryLog) }
.flowOn(Dispatchers.IO)
suspend fun insert(log: MeshLog) = withContext(Dispatchers.IO) {
meshLogDao.insert(log)

View file

@ -37,8 +37,9 @@ import com.geeksville.mesh.database.entity.QuickChatAction
AutoMigration (from = 8, to = 9),
AutoMigration (from = 9, to = 10),
AutoMigration (from = 10, to = 11),
AutoMigration (from = 11, to = 12),
],
version = 11,
version = 12,
exportSchema = true,
)
@TypeConverters(Converters::class)

View file

@ -9,16 +9,25 @@ import kotlinx.coroutines.flow.Flow
@Dao
interface MeshLogDao {
@Query("Select * from log order by received_date desc limit 0,:maxItem")
@Query("SELECT * FROM log ORDER BY received_date DESC LIMIT 0,:maxItem")
fun getAllLogs(maxItem: Int): Flow<List<MeshLog>>
@Query("Select * from log order by received_date asc limit 0,:maxItem")
@Query("SELECT * FROM log ORDER BY received_date ASC LIMIT 0,:maxItem")
fun getAllLogsInReceiveOrder(maxItem: Int): Flow<List<MeshLog>>
@Query(
"""
SELECT * FROM log
WHERE from_num = :fromNum AND port_num = :portNum
ORDER BY received_date DESC LIMIT 0,:maxItem
"""
)
fun getLogsFrom(fromNum: Int, portNum: Int, maxItem: Int): Flow<List<MeshLog>>
@Insert
fun insert(log: MeshLog)
@Query("DELETE from log")
@Query("DELETE FROM log")
fun deleteAll()
}

View file

@ -2,18 +2,30 @@ package com.geeksville.mesh.database.entity
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
import com.geeksville.mesh.MeshProtos
import com.geeksville.mesh.MeshProtos.FromRadio
import com.geeksville.mesh.Portnums
import com.google.protobuf.TextFormat
import java.io.IOException
@Entity(tableName = "log")
data class MeshLog(@PrimaryKey val uuid: String,
@ColumnInfo(name = "type") val message_type: String,
@ColumnInfo(name = "received_date") val received_date: Long,
@ColumnInfo(name = "message") val raw_message: String
@Entity(
tableName = "log",
indices = [
Index(value = ["from_num"]),
Index(value = ["port_num"]),
],
)
data class MeshLog(
@PrimaryKey val uuid: String,
@ColumnInfo(name = "type") val message_type: String,
@ColumnInfo(name = "received_date") val received_date: Long,
@ColumnInfo(name = "message") val raw_message: String,
@ColumnInfo(name = "from_num", defaultValue = "0") val fromNum: Int = 0,
@ColumnInfo(name = "port_num", defaultValue = "0") val portNum: Int = 0,
@ColumnInfo(name = "from_radio", typeAffinity = ColumnInfo.BLOB, defaultValue = "x''")
val fromRadio: FromRadio = FromRadio.getDefaultInstance(),
) {
val meshPacket: MeshProtos.MeshPacket?

View file

@ -216,10 +216,13 @@ class MeshService : Service(), Logging {
if (p.packet.hasDecoded()) {
val packetToSave = MeshLog(
UUID.randomUUID().toString(),
"Packet",
System.currentTimeMillis(),
p.packet.toString()
uuid = UUID.randomUUID().toString(),
message_type = "Packet",
received_date = System.currentTimeMillis(),
raw_message = p.packet.toString(),
fromNum = p.packet.from,
portNum = p.packet.decoded.portnumValue,
fromRadio = fromRadio { packet = p.packet },
)
insertMeshLog(packetToSave)
}
@ -1061,10 +1064,13 @@ class MeshService : Service(), Logging {
// debug("Recieved: $packet")
if (packet.hasDecoded()) {
val packetToSave = MeshLog(
UUID.randomUUID().toString(),
"Packet",
System.currentTimeMillis(),
packet.toString()
uuid = UUID.randomUUID().toString(),
message_type = "Packet",
received_date = System.currentTimeMillis(),
raw_message = packet.toString(),
fromNum = packet.from,
portNum = packet.decoded.portnumValue,
fromRadio = fromRadio { this.packet = packet },
)
insertMeshLog(packetToSave)
@ -1320,10 +1326,11 @@ class MeshService : Service(), Logging {
private fun handleDeviceConfig(config: ConfigProtos.Config) {
debug("Received config ${config.toOneLineString()}")
val packetToSave = MeshLog(
UUID.randomUUID().toString(),
"Config ${config.payloadVariantCase}",
System.currentTimeMillis(),
config.toString()
uuid = UUID.randomUUID().toString(),
message_type = "Config ${config.payloadVariantCase}",
received_date = System.currentTimeMillis(),
raw_message = config.toString(),
fromRadio = fromRadio { this.config = config },
)
insertMeshLog(packetToSave)
setLocalConfig(config)
@ -1334,10 +1341,11 @@ class MeshService : Service(), Logging {
private fun handleModuleConfig(config: ModuleConfigProtos.ModuleConfig) {
debug("Received moduleConfig ${config.toOneLineString()}")
val packetToSave = MeshLog(
UUID.randomUUID().toString(),
"ModuleConfig ${config.payloadVariantCase}",
System.currentTimeMillis(),
config.toString()
uuid = UUID.randomUUID().toString(),
message_type = "ModuleConfig ${config.payloadVariantCase}",
received_date = System.currentTimeMillis(),
raw_message = config.toString(),
fromRadio = fromRadio { moduleConfig = config },
)
insertMeshLog(packetToSave)
setLocalModuleConfig(config)
@ -1358,10 +1366,11 @@ class MeshService : Service(), Logging {
private fun handleChannel(ch: ChannelProtos.Channel) {
debug("Received channel ${ch.index}")
val packetToSave = MeshLog(
UUID.randomUUID().toString(),
"Channel",
System.currentTimeMillis(),
ch.toString()
uuid = UUID.randomUUID().toString(),
message_type = "Channel",
received_date = System.currentTimeMillis(),
raw_message = ch.toString(),
fromRadio = fromRadio { channel = ch },
)
insertMeshLog(packetToSave)
if (ch.role != ChannelProtos.Channel.Role.DISABLED) updateChannelSettings(ch)
@ -1404,10 +1413,11 @@ class MeshService : Service(), Logging {
debug("Received nodeinfo num=${info.num}, hasUser=${info.hasUser()}, hasPosition=${info.hasPosition()}, hasDeviceMetrics=${info.hasDeviceMetrics()}")
val packetToSave = MeshLog(
UUID.randomUUID().toString(),
"NodeInfo",
System.currentTimeMillis(),
info.toString()
uuid = UUID.randomUUID().toString(),
message_type = "NodeInfo",
received_date = System.currentTimeMillis(),
raw_message = info.toString(),
fromRadio = fromRadio { nodeInfo = info },
)
insertMeshLog(packetToSave)
@ -1465,10 +1475,11 @@ class MeshService : Service(), Logging {
*/
private fun handleMyInfo(myInfo: MeshProtos.MyNodeInfo) {
val packetToSave = MeshLog(
UUID.randomUUID().toString(),
"MyNodeInfo",
System.currentTimeMillis(),
myInfo.toString()
uuid = UUID.randomUUID().toString(),
message_type = "MyNodeInfo",
received_date = System.currentTimeMillis(),
raw_message = myInfo.toString(),
fromRadio = fromRadio { this.myInfo = myInfo },
)
insertMeshLog(packetToSave)
@ -1488,10 +1499,11 @@ class MeshService : Service(), Logging {
private fun handleMetadata(metadata: MeshProtos.DeviceMetadata) {
debug("Received deviceMetadata ${metadata.toOneLineString()}")
val packetToSave = MeshLog(
UUID.randomUUID().toString(),
"DeviceMetadata",
System.currentTimeMillis(),
metadata.toString()
uuid = UUID.randomUUID().toString(),
message_type = "DeviceMetadata",
received_date = System.currentTimeMillis(),
raw_message = metadata.toString(),
fromRadio = fromRadio { this.metadata = metadata },
)
insertMeshLog(packetToSave)
@ -1561,10 +1573,11 @@ class MeshService : Service(), Logging {
if (configCompleteId == configNonce) {
val packetToSave = MeshLog(
UUID.randomUUID().toString(),
"ConfigComplete",
System.currentTimeMillis(),
configCompleteId.toString()
uuid = UUID.randomUUID().toString(),
message_type = "ConfigComplete",
received_date = System.currentTimeMillis(),
raw_message = configCompleteId.toString(),
fromRadio = fromRadio { this.configCompleteId = configCompleteId },
)
insertMeshLog(packetToSave)