update prom config function in view model

This commit is contained in:
Martin Ptáček
2023-05-30 13:44:01 +02:00
parent a0041d63b9
commit fe2057c445
5 changed files with 143 additions and 154 deletions

View File

@@ -47,6 +47,7 @@ android {
}
dependencies {
implementation 'androidx.work:work-multiprocess:2.8.1'
def core_version = "1.10.1"
// custom - prometheus client java library

View File

@@ -5,7 +5,6 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
@@ -99,19 +98,19 @@ private fun TabPage(
navController: NavHostController,
modifier: Modifier,
){
) {
val tabs = mapOf(0 to "Prom Server", 1 to "PushProx", 2 to "Remote write")
val uiState : PromUiState by promViewModel.uiState.collectAsState()
val uiState: PromUiState by promViewModel.uiState.collectAsState()
Column(modifier = modifier) {
TabRow(selectedTabIndex = uiState.tabIndex) {
tabs.forEach{ (index, text) ->
Tab(text = {Text(text)},
tabs.forEach { (index, text) ->
Tab(text = { Text(text) },
selected = index == uiState.tabIndex,
onClick = { promViewModel.updateTabIndex(index) })
}
}
when(uiState.tabIndex){
when (uiState.tabIndex) {
0 -> PrometheusServerPage(promViewModel, Modifier)
1 -> PushProxPage(promViewModel)
2 -> RemoteWritePage(promViewModel)
@@ -119,21 +118,6 @@ private fun TabPage(
}
}
private fun onCheckedChangeServer(
value : Boolean,
promViewModel: PromViewModel,
showDialog : MutableState<String>
){
if (value) {
val result : String? = promViewModel.turnServerOn()
if(result != null){
showDialog.value = result
}
} else {
promViewModel.turnServerOff()
}
}
@Composable
private fun PrometheusServerPage(
promViewModel: PromViewModel,
@@ -149,12 +133,14 @@ private fun PrometheusServerPage(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(text = "Turn on Android Exporter on default port ${promViewModel.getDefaultPort()}")
Text(
text = "Turn on Android Exporter on port ${uiState.promConfig.prometheusServerPort}"
)
Switch(
checked = uiState.serverTurnedOn,
checked = uiState.promConfig.prometheusServerEnabled,
onCheckedChange = {value : Boolean? ->
if(value != null){
onCheckedChangeServer(value, promViewModel, showDialogText)
promViewModel.updatePromConfig(UpdatePromConfig.prometheusServerEnabled, value)
}
}
)
@@ -197,22 +183,11 @@ private fun PushProxPage(
modifier = Modifier.padding(bottom = 30.dp)
)
if(uiState.pushProxTurnedOn){
Text(
text = """
To edit PushProx proxy URL or FQDN, turn it off first.
""".trimIndent(),
textAlign = TextAlign.Center,
modifier = Modifier.padding(bottom = 12.dp),
)
}
TextField(
value = uiState.fqdn,
value = uiState.promConfig.pushproxFqdn,
singleLine = true,
enabled = !uiState.pushProxTurnedOn,
onValueChange = {
promViewModel.updatePushProxFQDN(it)
promViewModel.updatePromConfig(UpdatePromConfig.pushproxFqdn, it)
},
label = {
Text(text = "Fully Qualified Domain Name")
@@ -221,11 +196,10 @@ private fun PushProxPage(
)
TextField(
value = uiState.pushProxURL,
value = uiState.promConfig.pushproxProxyUrl,
singleLine = true,
enabled = !uiState.pushProxTurnedOn,
onValueChange = {
promViewModel.updatePushProxURL(it)
promViewModel.updatePromConfig(UpdatePromConfig.pushproxProxyUrl, it)
},
label = {
Text(text = "PushProx proxy URL")

View File

@@ -11,37 +11,41 @@ import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.OutOfQuotaPolicy
import androidx.work.WorkManager
import com.birdthedeveloper.prometheus.android.prometheus.android.exporter.worker.PushProxConfig
import com.birdthedeveloper.prometheus.android.prometheus.android.exporter.worker.PushProxWorker
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import java.lang.Exception
enum class ConfigFileState {
LOADING,
ERROR, // file not found or was not parsed
ERROR, // file was not parsed succesfully
MISSING,
SUCCESS
}
enum class UpdatePromConfig {
prometheusServerEnabled,
prometheusServerPort,
pushproxEnabled,
pushproxFqdn,
pushproxProxyUrl,
remoteWriteEnabled,
remoteWriteScrapeInterval,
remoteWriteEndpoint,
}
data class PromUiState(
val tabIndex : Int = 0,
val serverTurnedOn : Boolean = false,
val pushProxTurnedOn : Boolean = false,
val serverPort : Int? = null, // if null, use default port
val fqdn : String = "test.example.com",
val pushProxURL : String = "143.42.59.63:8080",
val promConfig: PromConfiguration = PromConfiguration(),
val configFileState : ConfigFileState = ConfigFileState.LOADING,
)
private val TAG : String = "PROMVIEWMODEL"
class PromViewModel(): ViewModel() {
// constants
private val DEFAULT_SERVER_PORT : Int = 10101 //TODO register with prometheus community
private val PROM_UNIQUE_WORK : String = "prom_unique_job"
@@ -50,23 +54,44 @@ class PromViewModel(): ViewModel() {
private lateinit var getContext: () -> Context
init {
loadConfigurationFile()
}
private fun loadConfigurationFile(){
Log.v(TAG, "Checking for configuration file")
viewModelScope.launch {
//TODO check for configuration file
delay(1000)
_uiState.update { current ->
current.copy(configFileState = ConfigFileState.MISSING)
val fileExists = PromConfiguration.configFileExists(context = getContext())
if (fileExists) {
val tempPromConfiguration : PromConfiguration
try {
tempPromConfiguration = PromConfiguration.loadFromConfigFile()
_uiState.update { current ->
current.copy(
promConfig = tempPromConfiguration,
configFileState = ConfigFileState.SUCCESS,
)
}
}catch (e : Exception){
_uiState.update { current ->
current.copy(configFileState = ConfigFileState.ERROR)
}
}
}else{
_uiState.update { current ->
current.copy(configFileState = ConfigFileState.MISSING)
}
}
}
}
fun getDefaultPort() : Int {
return DEFAULT_SERVER_PORT
}
fun initializeWithApplicationContext(getContext : () -> Context){
this.getContext = getContext
loadConfigurationFile()
}
fun updateTabIndex(index : Int){
@@ -77,59 +102,11 @@ class PromViewModel(): ViewModel() {
}
}
private fun getPromServerPort() : Int{
return if(_uiState.value.serverPort != null){
_uiState.value.serverPort!!
}else{
DEFAULT_SERVER_PORT
}
}
// if result is not null, it contains an error message
fun turnServerOn() : String?{
try{
//TODO rewrite asap
// prometheusServer.startInBackground(
// PrometheusServerConfig(
// getPromServerPort(),
// ::performScrape
// )
// )
}catch(e : Exception){
Log.v(TAG, e.toString())
return "Prometheus server failed!"
}
_uiState.update { current ->
current.copy(
serverTurnedOn = true
)
}
return null;
}
fun turnServerOff(){
//TODO implement
}
private fun validatePushProxSettings() : String? {
val fqdn = _uiState.value.fqdn.trim().trim('\n')
val url = _uiState.value.pushProxURL.trim().trim('\n')
if( fqdn.isEmpty() ) return "Fully Qualified Domain Name cannot be empty!"
if( url.isEmpty() ) return "PushProx URL cannot be empty!"
return null
}
private fun launchPushProxUsingWorkManager(){
fun startWorker(){
val workManagerInstance = WorkManager.getInstance(getContext())
// worker configuration
val inputData : Data = PushProxConfig(
pushProxFqdn = _uiState.value.fqdn,
pushProxUrl = _uiState.value.pushProxURL,
).toData()
val inputData : Data = _uiState.value.promConfig.toWorkData()
// constraints
val constraints = Constraints.Builder()
@@ -151,47 +128,54 @@ class PromViewModel(): ViewModel() {
).enqueue()
}
// if result is not null, it contains an error message
fun turnPushProxOn() : String?{
val error : String? = validatePushProxSettings()
if(error != null){ return error }
// idempotent call
launchPushProxUsingWorkManager()
_uiState.update { current ->
current.copy(
pushProxTurnedOn = true
)
}
return null
}
fun turnPushProxOff(){
fun stopWorker(){
//TODO implement this thingy
val workerManagerInstance = WorkManager.getInstance(getContext())
workerManagerInstance.cancelUniqueWork(PROM_UNIQUE_WORK)
_uiState.update {current ->
current.copy(
pushProxTurnedOn = false
)
}
}
fun updatePushProxURL(url : String){
_uiState.update { current ->
current.copy(
pushProxURL = url
)
fun updatePromConfig(part : UpdatePromConfig, value : Any){
when(part){
UpdatePromConfig.prometheusServerEnabled -> _uiState.update { current ->
current.copy(promConfig = current.promConfig.copy(
prometheusServerEnabled = value as Boolean
))
}
UpdatePromConfig.prometheusServerPort -> _uiState.update { current ->
current.copy(promConfig = current.promConfig.copy(
prometheusServerPort = value as Int,
))
}
UpdatePromConfig.pushproxEnabled -> _uiState.update { current ->
current.copy(promConfig = current.promConfig.copy(
pushproxEnabled = value as Boolean,
))
}
UpdatePromConfig.pushproxFqdn -> _uiState.update { current ->
current.copy(promConfig = current.promConfig.copy(
pushproxFqdn = value as String,
))
}
UpdatePromConfig.pushproxProxyUrl -> _uiState.update { current ->
current.copy(promConfig = current.promConfig.copy(
pushproxProxyUrl = value as String,
))
}
UpdatePromConfig.remoteWriteEnabled -> _uiState.update { current ->
current.copy(promConfig = current.promConfig.copy(
remoteWriteEnabled = value as Boolean,
))
}
UpdatePromConfig.remoteWriteScrapeInterval -> _uiState.update { current ->
current.copy(promConfig = current.promConfig.copy(
remoteWriteScrapeInterval = value as Int,
))
}
UpdatePromConfig.remoteWriteEndpoint -> _uiState.update { current ->
current.copy(promConfig = current.promConfig.copy(
remoteWriteEndpoint = value as String,
))
}
}
}
fun updatePushProxFQDN(fqdn : String){
_uiState.update { current ->
current.copy(
fqdn = fqdn
)
}
}
}
}

View File

@@ -1,5 +1,6 @@
package com.birdthedeveloper.prometheus.android.prometheus.android.exporter.compose
import android.content.Context
import androidx.work.Data
import androidx.work.workDataOf
@@ -11,16 +12,17 @@ data class PromConfiguration(
val prometheusServerEnabled : Boolean = true,
val prometheusServerPort : Int = defaultPrometheusServerPort,
val pushproxEnabled : Boolean = false,
val pushproxFqdn : String? = null,
val pushproxProxyUrl : String? = null,
val pushproxFqdn : String = "",
val pushproxProxyUrl : String = "",
val remoteWriteEnabled : Boolean = false,
val remoteWriteScrapeInterval : Int = defaultRemoteWriteScrapeInterval,
val remoteWriteEndpoint : String? = null,
val remoteWriteEndpoint : String = "",
) {
private val filepath : String = ""
private val filepath : String = "config.yaml"
private val alternativeFilepath : String = "config.yml"
companion object {
suspend fun configFileExists(): Boolean {
suspend fun configFileExists(context : Context): Boolean {
//TODO implement this asap
return false
}
@@ -30,11 +32,11 @@ data class PromConfiguration(
prometheusServerEnabled = data.getBoolean("0", true),
prometheusServerPort = data.getInt("1", defaultPrometheusServerPort),
pushproxEnabled = data.getBoolean("2", false),
pushproxFqdn = data.getString("3"),
pushproxProxyUrl = data.getString("4"),
pushproxFqdn = data.getString("3") ?: "",
pushproxProxyUrl = data.getString("4") ?: "",
remoteWriteEnabled = data.getBoolean("5", false),
remoteWriteScrapeInterval = data.getInt("6", defaultRemoteWriteScrapeInterval),
remoteWriteEndpoint = data.getString("7"),
remoteWriteEndpoint = data.getString("7") ?: "",
)
}

View File

@@ -0,0 +1,28 @@
package com.birdthedeveloper.prometheus.android.prometheus.android.exporter.worker
import android.content.Context
import android.util.Log
import androidx.work.WorkerParameters
import androidx.work.multiprocess.RemoteCoroutineWorker
import com.birdthedeveloper.prometheus.android.prometheus.android.exporter.compose.PromConfiguration
private val TAG = "Worker"
class PromWorker(
val context : Context,
val parameters : WorkerParameters,
) : RemoteCoroutineWorker(context = context, parameters = parameters) {
override suspend fun doRemoteWork(): Result {
val inputConfiguration : PromConfiguration = PromConfiguration.fromWorkData(inputData)
while(true){
Log.v(TAG, "Worker is working")
}
//TODO implement this asap
return Result.success()
}
}