Skip to content

Commit

Permalink
Merge pull request #240 from amosproj/polish_ui_and_add_gc
Browse files Browse the repository at this point in the history
[UI] Polish UI, fix bugs, add GC feature, refactor
  • Loading branch information
fhilgers authored Jan 29, 2025
2 parents 196f8f3 + 6339021 commit d24ae9a
Show file tree
Hide file tree
Showing 58 changed files with 2,511 additions and 608 deletions.
13 changes: 2 additions & 11 deletions frontend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,9 @@ Format `./gradlew combinedFormat` <br/>
Check: `./gradlew ktfmtCheck`

## Installing for overlay mode
Overlay mode on AAOS requires elevated priviledges (priv-app + a runtime permission)
* `./gradlew assemble`
Overlay mode on AAOS requires elevated priviledges (a runtime permission not grantable as the settings
menu for granting it does not exist in our emulator)
* `adb root`
* `adb remount`
* `adb reboot`
* `adb remount`
* `adb shell mkdir /system/priv-app/ziofa`
* `adb push app/build/outputs/apk/mock/debug/app-mock-debug.apk /system/priv-app/ziofa`
* `adb shell sync`
* `adb shell reboot`
* `adb root`
* `adb push`
* `adb shell pm grant de.amosproj3.ziofa android.permission.SYSTEM_ALERT_WINDOW`
* `adb shell pm grant --user 10 de.amosproj3.ziofa android.permission.SYSTEM_ALERT_WINDOW`

Expand Down
2 changes: 2 additions & 0 deletions frontend/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ dependencies {
implementation(libs.arrow.fx.coroutines)
detektPlugins(libs.detekt.compose.rules)
implementation(libs.kotlinx.collections.immutable)
implementation(libs.ycharts)


}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import de.amosproj3.ziofa.platform.overlay.OverlayManager
import de.amosproj3.ziofa.platform.processes.PackageInformationProvider
import de.amosproj3.ziofa.platform.processes.RunningComponentsProvider
import de.amosproj3.ziofa.ui.configuration.ConfigurationViewModel
import de.amosproj3.ziofa.ui.init.InitViewModel
import de.amosproj3.ziofa.ui.overlay.OverlayViewModel
import de.amosproj3.ziofa.ui.processes.ProcessesViewModel
import de.amosproj3.ziofa.ui.reset.ResetViewModel
Expand Down Expand Up @@ -72,6 +73,7 @@ class ZiofaApplication : Application() {
}

private fun Module.createViewModelFactories() {
viewModel { InitViewModel(get()) }
viewModel { (pids: List<UInt>) ->
ConfigurationViewModel(configurationAccess = get(), pids = pids)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-FileCopyrightText: 2025 Luca Bretting <[email protected]>
//
// SPDX-License-Identifier: MIT

package de.amosproj3.ziofa.api.configuration

/** To be used in implementation of [SymbolsAccess.indexSymbols] */
sealed class IndexingRequestState {
data object NotStarted : IndexingRequestState()

data object Started : IndexingRequestState()

data object Done : IndexingRequestState()

data class Error(val error: Throwable) : IndexingRequestState()
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,6 @@ interface SymbolsAccess {
* @return a flow that describes the state of the request
*/
fun searchSymbols(pids: List<UInt>, searchQuery: String): Flow<GetSymbolsRequestState>

fun indexSymbols(): Flow<IndexingRequestState>
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,8 @@ interface DataStreamProvider {
fun jniReferenceEvents(pids: List<UInt>?): Flow<Event.JniReferences>

fun sigquitEvents(pids: List<UInt>?): Flow<Event.SysSigquit>

fun gcEvents(pids: List<UInt>?): Flow<Event.Gc>

fun fileDescriptorTrackingEvents(pids: List<UInt>?): Flow<Event.SysFdTracking>
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ package de.amosproj3.ziofa.platform.configuration

import de.amosproj3.ziofa.api.configuration.ConfigurationAction
import de.amosproj3.ziofa.client.Configuration
import de.amosproj3.ziofa.client.GcConfig
import de.amosproj3.ziofa.client.JniReferencesConfig
import de.amosproj3.ziofa.client.SysFdTrackingConfig
import de.amosproj3.ziofa.client.SysSendmsgConfig
import de.amosproj3.ziofa.client.SysSigquitConfig
import de.amosproj3.ziofa.client.UprobeConfig
Expand Down Expand Up @@ -84,6 +86,26 @@ fun Configuration.applyChange(action: ConfigurationAction.ChangeFeature): Config
)
)
}

is BackendFeatureOptions.GcOption -> {
this.copy(
gc =
this.gc.updatePIDs(
pidsToAdd = if (enable) pids else setOf(),
pidsToRemove = if (!enable) pids else setOf(),
)
)
}

is BackendFeatureOptions.OpenFileDescriptors -> {
this.copy(
sysFdTracking =
this.sysFdTracking.updatePIDs(
pidsToAdd = if (enable) pids else setOf(),
pidsToRemove = if (!enable) pids else setOf(),
)
)
}
}
}

Expand Down Expand Up @@ -136,3 +158,19 @@ fun SysSigquitConfig?.updatePIDs(
val config = this ?: SysSigquitConfig(listOf())
return config.copy(pids = config.pids.plus(pidsToAdd).minus(pidsToRemove))
}

fun SysFdTrackingConfig?.updatePIDs(
pidsToAdd: Set<UInt> = setOf(),
pidsToRemove: Set<UInt> = setOf(),
): SysFdTrackingConfig {
val config = this ?: SysFdTrackingConfig(listOf())
return config.copy(pids = config.pids.plus(pidsToAdd).minus(pidsToRemove))
}

fun GcConfig?.updatePIDs(
pidsToAdd: Set<UInt> = setOf(),
pidsToRemove: Set<UInt> = setOf(),
): GcConfig {
val config = this ?: GcConfig(setOf())
return config.copy(pids = config.pids.plus(pidsToAdd).minus(pidsToRemove))
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import de.amosproj3.ziofa.ui.configuration.utils.EMPTY_CONFIGURATION
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
Expand Down Expand Up @@ -61,6 +62,7 @@ class ConfigurationManager(clientFactory: ClientFactory) :
inState<ConfigurationState.Uninitialized> {
onEnter { state ->
try {
delay(1000) // for smoother transition on InitScreen
val client = state.snapshot.clientFactory.connect()
val configuration = client.initializeConfiguration()
state.override { configuration.synchronizedOrErrorState(client) }
Expand Down Expand Up @@ -124,7 +126,7 @@ class ConfigurationManager(clientFactory: ClientFactory) :
}
}

fun State<ConfigurationState.Synchronized>.applyChangeAndTransitionToDifferent(
private fun State<ConfigurationState.Synchronized>.applyChangeAndTransitionToDifferent(
action: ConfigurationAction.ChangeFeature
) =
this.override {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package de.amosproj3.ziofa.platform.configuration

import de.amosproj3.ziofa.api.configuration.GetSymbolsRequestState
import de.amosproj3.ziofa.api.configuration.IndexingRequestState
import de.amosproj3.ziofa.api.configuration.SymbolsAccess
import de.amosproj3.ziofa.client.ClientFactory
import de.amosproj3.ziofa.ui.symbols.data.SymbolsEntry
Expand Down Expand Up @@ -62,4 +63,12 @@ class UProbeManager(private val clientFactory: ClientFactory) : SymbolsAccess {
}
.onStart { Timber.i("searchSymbols pids=$pids searchQuery=$searchQuery") }
.flowOn(Dispatchers.IO)

/**
* Current problem: Symbols in the backend will be duplicated if they are already indexed and we
* call this again. We don't know whether indexing was already done.
*/
override fun indexSymbols(): Flow<IndexingRequestState> {
TODO()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,22 @@ class DataStreamManager(private val clientFactory: ClientFactory, coroutineScope
.mapNotNull { it as? Event.SysSigquit }
.filter { it.pid.isGlobalRequestedOrPidConfigured(pids) }

override fun gcEvents(pids: List<UInt>?): Flow<Event.Gc> =
dataFlow
.mapNotNull { it as? Event.Gc }
.filter {
it.pid.isGlobalRequestedOrPidConfigured(pids) ||
it.tid.isGlobalRequestedOrPidConfigured(pids)
}

override fun fileDescriptorTrackingEvents(pids: List<UInt>?): Flow<Event.SysFdTracking> =
dataFlow
.mapNotNull { it as? Event.SysFdTracking }
.filter {
it.pid.isGlobalRequestedOrPidConfigured(pids) ||
it.tid.isGlobalRequestedOrPidConfigured(pids)
}

private fun UInt.isGlobalRequestedOrPidConfigured(pids: List<UInt>?) =
pids?.contains(this) ?: true
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import timber.log.Timber
Expand All @@ -34,7 +36,14 @@ class RunningComponentsProvider(
private var client: Client? = null

override val runningComponentsList =
processesList.groupByProcessName().splitIntoAppsAndStandaloneProcesses()
processesList
.groupByProcessName()
.splitIntoAppsAndStandaloneProcesses()
.stateIn(
scope = coroutineScope,
started = SharingStarted.Lazily,
initialValue = listOf(),
)

init {
coroutineScope.launch {
Expand Down
1 change: 1 addition & 0 deletions frontend/app/src/main/java/de/amosproj3/ziofa/ui/Routes.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ enum class Routes {
Configuration,
Symbols,
Reset,
Init,
}
13 changes: 10 additions & 3 deletions frontend/app/src/main/java/de/amosproj3/ziofa/ui/ZiofaApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import de.amosproj3.ziofa.ui.about.AboutScreen
import de.amosproj3.ziofa.ui.configuration.ConfigurationScreen
import de.amosproj3.ziofa.ui.init.InitScreen
import de.amosproj3.ziofa.ui.navigation.ConfigurationMenu
import de.amosproj3.ziofa.ui.navigation.HomeScreen
import de.amosproj3.ziofa.ui.navigation.composables.DynamicTopBar
Expand Down Expand Up @@ -57,13 +58,19 @@ fun ZIOFAApp() {
NavHost(
navController,
modifier = Modifier.fillMaxSize(),
startDestination = Routes.Home.name,
startDestination = Routes.Init.name,
) {
screenWithDefaultAnimations(Routes.Init.name) {
InitScreen(
onInitFinished = { navController.navigate(Routes.Home.name) },
modifier = Modifier.padding(innerPadding),
)
}
screenWithDefaultAnimations(Routes.Home.name) {
HomeScreen(
toVisualize = { navController.navigate(Routes.Visualize.name) },
toConfiguration = { navController.navigate(Routes.Configuration.name) },
toAbout = { navController.navigate(Routes.About.name) },
toConfiguration = { navController.navigate(Routes.Processes.name) },
toReset = { navController.navigate(Routes.Reset.name) },
modifier = Modifier.padding(innerPadding),
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import org.koin.androidx.compose.koinViewModel
import org.koin.core.parameter.parametersOf

/** Screen for configuring eBPF programs */
@Suppress("LongMethod") // does not improve readability
@Preview(device = Devices.AUTOMOTIVE_1024p)
@Composable
fun ConfigurationScreen(
Expand All @@ -51,6 +52,16 @@ fun ConfigurationScreen(
is ConfigurationScreenState.Valid -> {
// Render list of options
LazyColumn(Modifier.fillMaxWidth()) {
item {
PresetFeatureOptionsGroup(
options = state.options,
type = FeatureType.MEMORY,
onOptionChanged = { option, newState ->
viewModel.optionChanged(option, newState)
},
)
}

item {
PresetFeatureOptionsGroup(
options = state.options,
Expand Down Expand Up @@ -95,7 +106,10 @@ fun ConfigurationScreen(
}

is ConfigurationScreenState.Invalid -> {
ErrorScreen(state.errorMessage)
ErrorScreen(
error = state.errorMessage,
title = "Error while reading/writing configuration",
)
}

is ConfigurationScreenState.Loading -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,18 @@ const val TITLE_TEXT_SIZE = 25f

@Preview(device = Devices.AUTOMOTIVE_1024p)
@Composable
fun ErrorScreen(error: String = "No error message available") {
Box(modifier = Modifier.fillMaxSize()) {
fun ErrorScreen(
modifier: Modifier = Modifier,
error: String = "No error message available",
title: String = "Error while communicating with backend",
) {
Box(modifier = modifier.fillMaxSize()) {
Column(
modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
text = "Error while communicating with backend",
text = title,
color = Color.Red,
fontSize = TextUnit(TITLE_TEXT_SIZE, TextUnitType.Sp),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
package de.amosproj3.ziofa.ui.configuration.data

enum class FeatureType(val displayName: String) {
IO("IO Observability Features"),
IO("IO Observability"),
SIGNALS("Linux Signals"),
MEMORY("Memory Usage"),
UPROBES("Uprobes"),
}

Expand Down Expand Up @@ -36,7 +37,7 @@ sealed class BackendFeatureOptions(
data class JniReferencesOption(val enabled: Boolean, val pids: Set<UInt>) :
BackendFeatureOptions(
name = "Local & Global Indirect JNI References",
type = FeatureType.IO,
type = FeatureType.MEMORY,
description =
"Detect JNI memory leaks by tracing the number of indirect JNI references.",
active = enabled,
Expand All @@ -51,6 +52,22 @@ sealed class BackendFeatureOptions(
active = enabled,
)

data class GcOption(val enabled: Boolean, val pids: Set<UInt>) :
BackendFeatureOptions(
name = "Garbage Collector Analysis & Heap Usage",
type = FeatureType.MEMORY,
description = "View live GC invocations, used Java heap and total Java heap size.",
active = enabled,
)

data class OpenFileDescriptors(val enabled: Boolean, val pids: Set<UInt>) :
BackendFeatureOptions(
name = "Open File Descriptors",
type = FeatureType.IO,
description = "View the number of opened file descriptors.",
active = enabled,
)

data class UprobeOption(
val method: String,
val enabled: Boolean,
Expand Down
Loading

0 comments on commit d24ae9a

Please sign in to comment.