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

[UI] Polish UI, fix bugs, add GC feature, refactor #240

Merged
merged 12 commits into from
Jan 29, 2025
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
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
Fixed Show fixed Hide fixed
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
Loading