Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Debug info: print all workers #1200

Open
wants to merge 2 commits into
base: main-ose
Choose a base branch
from
Open
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
8 changes: 6 additions & 2 deletions app/src/main/kotlin/at/bitfire/davdroid/TextTable.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ package at.bitfire.davdroid
import java.util.Collections

class TextTable(
vararg val headers: String
val headers: List<String>
) {

companion object {
Expand All @@ -18,17 +18,21 @@ class TextTable(

}

constructor(vararg headers: String): this(headers.toList())


private val lines = mutableListOf<Array<String>>()

fun addLine(vararg values: Any?) {
fun addLine(values: List<Any?>) {
if (values.size != headers.size)
throw IllegalArgumentException("Table line must have ${headers.size} column(s)")
lines += values.map {
it?.toString() ?: "—"
}.toTypedArray()
}

fun addLine(vararg values: Any?) = addLine(values.toList())

override fun toString(): String {
val sb = StringBuilder()

Expand Down
122 changes: 96 additions & 26 deletions app/src/main/kotlin/at/bitfire/davdroid/ui/DebugInfoModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import androidx.core.content.getSystemService
import androidx.core.content.pm.PackageInfoCompat
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.work.WorkInfo
import androidx.work.WorkManager
import androidx.work.WorkQuery
import at.bitfire.dav4jvm.exception.DavException
Expand All @@ -48,6 +49,7 @@ import at.bitfire.davdroid.settings.AccountSettings
import at.bitfire.davdroid.settings.SettingsManager
import at.bitfire.davdroid.sync.SyncDataType
import at.bitfire.davdroid.sync.SyncFrameworkIntegration
import at.bitfire.davdroid.sync.account.AccountsCleanupWorker
import at.bitfire.davdroid.sync.worker.BaseSyncWorker
import at.bitfire.ical4android.TaskProvider
import at.techbee.jtx.JtxContract
Expand Down Expand Up @@ -395,6 +397,11 @@ class DebugInfoModel @AssistedInject constructor(
dumpAddressBookAccount(account, accountManager, writer)
}

// general-purpose sync workers
writer.append("\nSync workers:\n")
.append(dumpSyncWorkersInfo())
.append("\n")

// database dump
writer.append("\n\nDATABASE DUMP\n\n")
db.dump(writer, arrayOf("webdav_document"))
Expand Down Expand Up @@ -553,38 +560,101 @@ class DebugInfoModel @AssistedInject constructor(
}

/**
* Gets sync workers info
* Note: WorkManager does not return worker names when queried, so we create them and ask
* whether they exist one by one
* Generates a table to display worker statuses.
*
* By default, the table provides the following columns:
* Tags, State, Next run, Retries, Generation, Periodicity
*
* If more tables are desired, they can be added using [extraColumns].
*
* The key of the map is the position of the column relative to the default ones, so for example,
* if a column is going to be added between "State" and "Next run", the index should be `2`.
*
* The value the map is a pair whose first element is the column name, and the second one is
* the generator of the value.
* The generator will be called on every worker info found after running the query, and should
* return a String that will be placed in the cell.
*
* @param query The query to use for fetching the workers.
* @param extraColumns Defaults to an empty map, pass extra columns to be added to the table.
*/
private fun dumpSyncWorkersInfo(account: Account): String {
val table = TextTable("Tags", "Authority", "State", "Next run", "Retries", "Generation", "Periodicity")
for (dataType in SyncDataType.entries) {
val tag = BaseSyncWorker.commonTag(account, dataType)
WorkManager.getInstance(context).getWorkInfos(
WorkQuery.Builder.fromTags(listOf(tag)).build()
).get().forEach { workInfo ->
table.addLine(
workInfo.tags.map { it.replace("\\bat\\.bitfire\\.davdroid\\.".toRegex(), ".") },
dataType,
"${workInfo.state} (${workInfo.stopReason})",
workInfo.nextScheduleTimeMillis.let { nextRun ->
when (nextRun) {
Long.MAX_VALUE -> "—"
else -> DateUtils.getRelativeTimeSpanString(nextRun)
}
},
workInfo.runAttemptCount,
workInfo.generation,
workInfo.periodicityInfo?.let { periodicity ->
"every ${periodicity.repeatIntervalMillis/60000} min"
} ?: "not periodic"
)
private fun workersInfoTable(
query: WorkQuery,
extraColumns: Map<Int, Pair<String, (WorkInfo) -> String>> = emptyMap()
): String {
val columnNames = mutableListOf("Tags", "State", "Next run", "Retries", "Generation", "Periodicity")
for ((index, column) in extraColumns) {
val (columnName) = column
columnNames.add(index, columnName)
}

val table = TextTable(columnNames)
val workInfos = WorkManager.getInstance(context).getWorkInfos(query).get()
for (workInfo in workInfos) {
val line = mutableListOf(
workInfo.tags.map { it.replace("\\bat\\.bitfire\\.davdroid\\.".toRegex(), ".") },
"${workInfo.state} (${workInfo.stopReason})",
workInfo.nextScheduleTimeMillis.let { nextRun ->
when (nextRun) {
Long.MAX_VALUE -> "—"
else -> DateUtils.getRelativeTimeSpanString(nextRun)
}
},
workInfo.runAttemptCount,
workInfo.generation,
workInfo.periodicityInfo?.let { periodicity ->
"every ${periodicity.repeatIntervalMillis/60000} min"
} ?: "not periodic"
)

for ((index, column) in extraColumns) {
val (_, transformer) = column
val value = transformer(workInfo)
line.add(index, value)
}

table.addLine(line)
}
return table.toString()
}

/**
* Gets sync workers info
* Note: WorkManager does not return worker names when queried, so we create them and ask
* whether they exist one by one
*/
private fun dumpSyncWorkersInfo(account: Account): String =
workersInfoTable(
WorkQuery.Builder.fromTags(
SyncDataType.entries.map { BaseSyncWorker.commonTag(account, it) }
).build(),
mapOf(
1 to ("Data Type" to { workInfo: WorkInfo ->
// See: BaseSyncWorker.commonTag
// "sync-$dataType ${account.type}/${account.name}"
workInfo.tags
// Search for the first tag that starts with sync-
.first { it.startsWith("sync-") }
// Get everything before the space (get rid of the account)
.substringBefore(' ')
// Remove the "sync-" prefix
.removePrefix("sync-")
})
)
)

/**
* Gets account-independent sync workers info
* Note: WorkManager does not return worker names when queried, so we create them and ask
* whether they exist one by one
*/
private fun dumpSyncWorkersInfo(): String =
workersInfoTable(
WorkQuery.Builder.fromUniqueWorkNames(
listOf(AccountsCleanupWorker.NAME)
).build()
)

}

data class AccountDumpInfo(
Expand Down
Loading