Skip to content
This repository has been archived by the owner on Feb 17, 2024. It is now read-only.

Commit

Permalink
Perf: Use flows for log storage
Browse files Browse the repository at this point in the history
  • Loading branch information
wingio committed Apr 27, 2023
1 parent c705742 commit 3d625a0
Show file tree
Hide file tree
Showing 9 changed files with 109 additions and 50 deletions.
3 changes: 2 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
android:name=".app.App"
android:roundIcon="@drawable/ic_launcher"
android:supportsRtl="true"
android:theme="@style/Theme.Logra">
android:theme="@style/Theme.Logra"
android:largeHeap="true">
<activity
android:name=".MainActivity"
android:exported="true"
Expand Down
7 changes: 4 additions & 3 deletions app/src/main/java/xyz/wingio/logra/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,20 +29,20 @@ import xyz.wingio.logra.utils.Utils
import xyz.wingio.logra.utils.Utils.saveText
import xyz.wingio.logra.utils.hasLogsPermission
import xyz.wingio.logra.utils.initCrashService
import xyz.wingio.logra.utils.logcat.LogcatManager
import xyz.wingio.logra.domain.manager.LogcatManager
import java.text.SimpleDateFormat
import java.util.*

class MainActivity : ComponentActivity() {

val prefs: PreferenceManager by inject()
private val logcatManager: LogcatManager by inject()

@OptIn(ExperimentalAnimationApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
installSplashScreen()

LogcatManager.connect()
val showCrash =
intent.action == Intents.Actions.VIEW_CRASH && intent.hasExtra(Intents.Extras.CRASH)
if (prefs.crashDetectorEnabled) initCrashService()
Expand Down Expand Up @@ -94,6 +94,7 @@ class MainActivity : ComponentActivity() {
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == RESULT_OK && data != null) {
if (requestCode == 1) {
try {
Expand All @@ -118,7 +119,7 @@ class MainActivity : ComponentActivity() {
if(it.action == "logra.actions.EXPORT_LOGS") {
val pid = it.extras?.getInt("logra.extras.PID") ?: return
CoroutineScope(Dispatchers.IO).launch {
LogcatManager.getLogsFromPid(pid).also { logs ->
logcatManager.getLogsFromPid(pid).also { logs ->
saveText(
logs
.sortedBy { log -> log.createdAt }
Expand Down
5 changes: 5 additions & 0 deletions app/src/main/java/xyz/wingio/logra/app/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.os.Looper
import kotlinx.coroutines.MainScope
import org.koin.android.ext.koin.androidContext
import org.koin.core.context.startKoin
import xyz.wingio.logra.di.databaseModule
Expand Down Expand Up @@ -67,4 +68,8 @@ class App : Application() {
}
}

companion object {
val applicationScope = MainScope()
}

}
2 changes: 2 additions & 0 deletions app/src/main/java/xyz/wingio/logra/di/ManagerModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ package xyz.wingio.logra.di

import org.koin.core.module.dsl.singleOf
import org.koin.dsl.module
import xyz.wingio.logra.domain.manager.LogcatManager
import xyz.wingio.logra.domain.manager.PreferenceManager

fun managerModule() = module {

singleOf(::PreferenceManager)
singleOf(::LogcatManager)

}
Original file line number Diff line number Diff line change
@@ -1,41 +1,69 @@
package xyz.wingio.logra.utils.logcat
package xyz.wingio.logra.domain.manager

import android.os.Build
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import xyz.wingio.logra.app.App
import xyz.wingio.logra.domain.logcat.LogcatEntry
import xyz.wingio.logra.domain.manager.PreferenceManager
import xyz.wingio.logra.utils.Logger
import xyz.wingio.logra.utils.Utils.updateList
import xyz.wingio.logra.utils.mainThread
import java.io.BufferedReader
import java.io.IOException
import java.io.InputStreamReader
import kotlin.concurrent.thread

object LogcatManager : KoinComponent {
class LogcatManager : KoinComponent {

private const val command = "logcat -v epoch"
private val command = "logcat -v epoch -T 0"
private val prefs: PreferenceManager by inject()

private lateinit var reader: BufferedReader
private lateinit var proc: Process

val logs = MutableStateFlow(emptyList<LogcatEntry>())
private var job: Job? = null

fun stop() {
job?.cancel()
reader.close()
proc.destroy()
}

fun clear() {
logs.update { emptyList() }
}

fun connect() {
if (prefs.dumpLogs) Runtime.getRuntime().exec("logcat -c").waitFor()
proc = Runtime.getRuntime().exec(command)
reader = proc.inputStream.bufferedReader()
}

fun listen(callback: (LogcatEntry) -> Unit) {
thread(start = true) {
while (true) {
if (!proc.isAlive) {
connect(); continue
}
fun listen() {
if(!proc.isAlive) connect()
job = App.applicationScope.launch(Dispatchers.IO) {
while (proc.isAlive) {
reader.forEachLine {
val ent = LogcatEntry.fromLine(it)
mainThread.post {
ent?.let(callback)
ent?.let {
logs.updateList {
add(it)

while (size > 10_000) {
removeAt(0)
}
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,21 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.*
import androidx.compose.ui.text.font.FontFamily
import kotlinx.coroutines.launch
import org.koin.androidx.compose.get
import xyz.wingio.logra.BuildConfig
import xyz.wingio.logra.R
import xyz.wingio.logra.utils.ShizukuRequestResult
import xyz.wingio.logra.utils.Utils
import xyz.wingio.logra.utils.checkShizukuPermission
import xyz.wingio.logra.utils.grantPermissionsWithRoot
import xyz.wingio.logra.utils.logcat.LogcatManager
import xyz.wingio.logra.domain.manager.LogcatManager

@OptIn(ExperimentalTextApi::class)
@Composable
fun RequestPopup(onDialogChange: (PopupState) -> Unit) {
val context = LocalContext.current
val coroutineScope = rememberCoroutineScope()
val logcatManager: LogcatManager = get()

AlertDialog(
text = {
Expand Down Expand Up @@ -85,7 +87,8 @@ fun RequestPopup(onDialogChange: (PopupState) -> Unit) {
// Root
coroutineScope.launch {
grantPermissionsWithRoot().also {
LogcatManager.connect()
logcatManager.clear()
logcatManager.listen()
onDialogChange(PopupState.NONE)
}
}
Expand Down
17 changes: 13 additions & 4 deletions app/src/main/java/xyz/wingio/logra/ui/screens/main/MainScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@ import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.koin.getScreenModel
import cafe.adriel.voyager.navigator.LocalNavigator
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import xyz.wingio.logra.R
import xyz.wingio.logra.domain.manager.LogcatManager
import xyz.wingio.logra.ui.components.filter.FilterRow
import xyz.wingio.logra.ui.components.filter.RoundedTextBox
import xyz.wingio.logra.ui.screens.crashes.CrashesScreen
Expand All @@ -39,6 +41,7 @@ import xyz.wingio.logra.ui.theme.logLineAlt
import xyz.wingio.logra.ui.viewmodels.main.MainScreenViewModel
import xyz.wingio.logra.ui.widgets.logs.LogEntry
import xyz.wingio.logra.ui.widgets.selection.SelectionPopup
import xyz.wingio.logra.utils.Utils.matches
import xyz.wingio.logra.utils.Utils.saveText
import java.text.SimpleDateFormat
import java.util.*
Expand All @@ -54,9 +57,10 @@ class MainScreen : Screen {
viewModel: MainScreenViewModel = getScreenModel()
) {
val listState = rememberLazyListState()
val _logs by viewModel.logcatManager.logs.collectAsState()
val logs by remember {
derivedStateOf {
viewModel.filterLogs()
_logs.matches(viewModel.filter)
}
}

Expand Down Expand Up @@ -190,7 +194,10 @@ class MainScreen : Screen {
}

// Pause/Unpause logs
IconButton(onClick = { viewModel.paused = !viewModel.paused }) {
IconButton(onClick = {
viewModel.paused = !viewModel.paused
if(viewModel.paused) viewModel.stop() else viewModel.start()
}) {
if (viewModel.paused)
Icon(Icons.Filled.PlayArrow, contentDescription = stringResource(R.string.unpause_logs))
else
Expand Down Expand Up @@ -221,7 +228,7 @@ class MainScreen : Screen {
DropdownMenuItem(
text = { Text(stringResource(R.string.clear)) },
onClick = {
viewModel.logs.clear()
viewModel.clear()
viewModel.selectedLogs.clear()
menuOpened = false
}
Expand All @@ -232,7 +239,9 @@ class MainScreen : Screen {
text = { Text(stringResource(R.string.save)) },
onClick = {
ctx.saveText(
viewModel.logs
viewModel.logcatManager
.logs
.value
.sortedBy { it.createdAt }
.joinToString("\n") {
it.raw
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,28 @@ import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import cafe.adriel.voyager.core.model.ScreenModel
import cafe.adriel.voyager.core.model.coroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.getAndUpdate
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import xyz.wingio.logra.domain.logcat.LogcatEntry
import xyz.wingio.logra.domain.logcat.filter.Filter
import xyz.wingio.logra.domain.manager.PreferenceManager
import xyz.wingio.logra.utils.Utils.matches
import xyz.wingio.logra.utils.logcat.LogcatManager
import xyz.wingio.logra.domain.manager.LogcatManager

@OptIn(ExperimentalCoroutinesApi::class)
class MainScreenViewModel(
val prefs: PreferenceManager
val prefs: PreferenceManager,
val logcatManager: LogcatManager
) : ScreenModel {

var paused by mutableStateOf(false)
Expand All @@ -21,38 +35,22 @@ class MainScreenViewModel(
var searchByOpen by mutableStateOf(false)
val selectedLogs = mutableStateListOf<LogcatEntry>()
var filter by mutableStateOf(Filter())
var logs = mutableStateListOf<LogcatEntry>()

private val pausedLogs = mutableListOf<LogcatEntry>()
val logs = MutableStateFlow(emptyList<LogcatEntry>())

init {
LogcatManager.listen {
if (paused)
pausedLogs.add(it)
else {
addLogs(pausedLogs)
pausedLogs.clear()
addLog(it)
}
}
logcatManager.connect()
logcatManager.listen()
}

private fun addLog(entry: LogcatEntry) {
if (logs.size > 10000) {
logs.removeFirst()
}
logs.add(entry)
}
// fun filterLogs(): MutableStateFlow<List<LogcatEntry>> {
// return LogcatManager.logs.matches(filter)
// }

private fun addLogs(entries: List<LogcatEntry>) {
entries.forEach {
addLog(it)
}
}
fun stop() = logcatManager.stop()

fun filterLogs(): List<LogcatEntry> {
return logs.matches(filter)
}
fun start() = logcatManager.listen()

fun clear() = logcatManager.clear()

fun searchByText() {
filter.text = selectedLogs.firstOrNull()?.content ?: ""
Expand Down
12 changes: 12 additions & 0 deletions app/src/main/java/xyz/wingio/logra/utils/Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ import android.os.Handler
import android.os.Looper
import android.widget.Toast
import androidx.compose.ui.graphics.Color
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.withContext
import xyz.wingio.logra.BuildConfig
import xyz.wingio.logra.domain.logcat.LogcatEntry
import xyz.wingio.logra.domain.logcat.filter.Filter
Expand All @@ -31,6 +37,8 @@ object Utils {
it.matches(filter)
}

fun MutableStateFlow<List<LogcatEntry>>.matches(filter: Filter) = MutableStateFlow(this@matches.value.matches(filter))

fun LogcatEntry.matches(filter: Filter): Boolean {
val textMatches = filter.text.isBlank() || content.contains(filter.text)
val levelMatches = filter.levels.contains(level)
Expand Down Expand Up @@ -93,4 +101,8 @@ object Utils {
val b: Int = (blue * 255).toInt()
return java.lang.String.format(Locale.getDefault(), "%02X%02X%02X%02X", a, r, g, b)
}

fun <T> MutableStateFlow<List<T>>.updateList(block: MutableList<T>.() -> Unit) = update {
it.toMutableList().apply { block(this) }
}
}

0 comments on commit 3d625a0

Please sign in to comment.