doPush impl almost done

This commit is contained in:
Martin Ptáček
2023-05-01 16:19:50 +02:00
parent 06e2f5866b
commit 7ca0d241a3
8 changed files with 127 additions and 53 deletions

View File

@ -1,2 +1,24 @@
# prometheus-android-exporter # prometheus-android-exporter
Prometheus exporter for android Prometheus exporter for android
# Not public
143.42.59.63:9090 - prometheus
# Client
Android app written in kotlin using Jetpack Compose.
# Server
## TL;DR
```
cd ./server
# edit ansible_inventory
# To apply ansible playbook
$ ansible-playbook ansible_playbook.yaml
# To only apply changed configuration
$ ansible-playbook ansible_playbook.yaml --tags config
```

View File

@ -59,9 +59,8 @@ dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.0-RC" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.0-RC"
// custom - ktor web client // custom - ktor web client
implementation("io.ktor:ktor-server-core:2.2.4") implementation("io.ktor:ktor-client-core:2.3.0")
runtimeOnly("io.ktor:ktor-client-core:2.2.4") implementation("io.ktor:ktor-client-cio:2.3.0")
implementation("io.ktor:ktor-client-cio:2.2.4")
implementation 'androidx.core:core-ktx:1.7.0' implementation 'androidx.core:core-ktx:1.7.0'

View File

@ -18,6 +18,7 @@ import com.birdthedeveloper.prometheus.android.prometheus.android.exporter.ui.th
import io.prometheus.client.Collector import io.prometheus.client.Collector
import io.prometheus.client.CollectorRegistry import io.prometheus.client.CollectorRegistry
import io.prometheus.client.exporter.common.TextFormat import io.prometheus.client.exporter.common.TextFormat
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.io.StringWriter import java.io.StringWriter
@ -27,6 +28,7 @@ class MainActivity : ComponentActivity() {
private lateinit var metricsEngine: MetricsEngine private lateinit var metricsEngine: MetricsEngine
private lateinit var customExporter: AndroidCustomExporter private lateinit var customExporter: AndroidCustomExporter
private var pushProxStarted : Boolean = false
private lateinit var pushProxClient : PushProxClient private lateinit var pushProxClient : PushProxClient
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -51,22 +53,32 @@ class MainActivity : ComponentActivity() {
customExporter = AndroidCustomExporter(metricsEngine).register(collectorRegistry) customExporter = AndroidCustomExporter(metricsEngine).register(collectorRegistry)
} }
fun CollectMetrics(): String{ private suspend fun reallyCollectMetrics() : String {
delay(500)
val writer = StringWriter()
TextFormat.write004(writer, collectorRegistry.metricFamilySamples())
return writer.toString()
}
private fun CollectMetrics(): String{
val writer = StringWriter() val writer = StringWriter()
TextFormat.write004(writer, collectorRegistry.metricFamilySamples()) TextFormat.write004(writer, collectorRegistry.metricFamilySamples())
// initialize PushProx // initialize PushProx
pushProxClient = PushProxClient( if (!pushProxStarted) {
config = PushProxConfig( pushProxClient = PushProxClient(
"test.example.com", config = PushProxConfig(
"143.42.59.63", "test.example.com",
1, "http://143.42.59.63:8080",
5, 1,
true 5,
collectorRegistry,
::reallyCollectMetrics,
)
) )
) pushProxClient.startBackground()
pushProxClient.startBackground() pushProxStarted = true
}
return writer.toString() return writer.toString()
} }

View File

@ -1,5 +1,6 @@
package com.birdthedeveloper.prometheus.android.prometheus.android.exporter package com.birdthedeveloper.prometheus.android.prometheus.android.exporter
import android.preference.PreferenceActivity.Header
import android.util.Log import android.util.Log
import io.github.reugn.kotlin.backoff.StrategyBackoff import io.github.reugn.kotlin.backoff.StrategyBackoff
import io.github.reugn.kotlin.backoff.strategy.ExponentialStrategy import io.github.reugn.kotlin.backoff.strategy.ExponentialStrategy
@ -8,14 +9,17 @@ import io.ktor.client.call.body
import io.ktor.client.engine.cio.CIO import io.ktor.client.engine.cio.CIO
import io.ktor.client.request.HttpRequestBuilder import io.ktor.client.request.HttpRequestBuilder
import io.ktor.client.request.get import io.ktor.client.request.get
import io.ktor.client.request.header
import io.ktor.client.request.post import io.ktor.client.request.post
import io.ktor.client.request.request import io.ktor.client.request.request
import io.ktor.client.request.setBody import io.ktor.client.request.setBody
import io.ktor.client.statement.HttpResponse import io.ktor.client.statement.HttpResponse
import io.ktor.http.HttpHeaders
import io.ktor.http.HttpMethod import io.ktor.http.HttpMethod
import io.ktor.http.URLBuilder import io.ktor.http.URLBuilder
import io.ktor.http.Url import io.ktor.http.Url
import io.ktor.http.maxAge import io.ktor.http.maxAge
import io.prometheus.client.CollectorRegistry
import io.prometheus.client.Counter import io.prometheus.client.Counter
import kotlinx.coroutines.Deferred import kotlinx.coroutines.Deferred
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
@ -26,35 +30,38 @@ import kotlinx.coroutines.runBlocking
import kotlin.time.Duration import kotlin.time.Duration
// Counters for monitoring the pushprox itself, compatible with the reference implementation in go. // Counters for monitoring the pushprox itself, compatible with the reference implementation in go.
private class Counters(enabled: Boolean) { private class Counters(collectorRegistry: CollectorRegistry?) {
private val enabled : Boolean private val collectorRegistry : CollectorRegistry?
private lateinit var scrapeErrorCounter : Counter private lateinit var scrapeErrorCounter : Counter
private lateinit var pushErrorCounter : Counter private lateinit var pushErrorCounter : Counter
private lateinit var pollErrorCounter : Counter private lateinit var pollErrorCounter : Counter
private lateinit var pollSuccessCounter : Counter private lateinit var pollSuccessCounter : Counter
private var enabled : Boolean = false
init { init {
this.enabled = enabled this.collectorRegistry = collectorRegistry
if (enabled){ if (collectorRegistry != null){
this.enabled = true
// following 3 counters are compatible with reference implementation // following 3 counters are compatible with reference implementation
scrapeErrorCounter = Counter.build() scrapeErrorCounter = Counter.build()
.name("pushprox_client_scrape_errors_total") .name("pushprox_client_scrape_errors_total")
.help("Number of scrape errors") .help("Number of scrape errors")
.register() .register(collectorRegistry)
pushErrorCounter = Counter.build() pushErrorCounter = Counter.build()
.name("pushprox_client_push_errors_total") .name("pushprox_client_push_errors_total")
.help("Number of push errors") .help("Number of push errors")
.register() .register(collectorRegistry)
pollErrorCounter = Counter.build() pollErrorCounter = Counter.build()
.name("pushprox_client_poll_errors_total") .name("pushprox_client_poll_errors_total")
.help("Number of poll errors") .help("Number of poll errors")
.register() .register(collectorRegistry)
// custom // custom
pollSuccessCounter = Counter.build() pollSuccessCounter = Counter.build()
.name("pushprox_client_poll_total") .name("pushprox_client_poll_total")
.help("Number of succesfull polls") .help("Number of succesfull polls")
.register() .register(collectorRegistry)
} }
} }
@ -73,11 +80,13 @@ data class PushProxConfig(
val proxyURL: String, val proxyURL: String,
val retryInitialWaitSeconds: Int = 1, //TODO will this be even used? val retryInitialWaitSeconds: Int = 1, //TODO will this be even used?
val retryMaxWaitSeconds: Int = 5, //TODO will this be even used? val retryMaxWaitSeconds: Int = 5, //TODO will this be even used?
val enablePushProxClientMonitoring: Boolean = true, val collectorRegistry: CollectorRegistry? = null,
val performScrape: suspend () -> String,
) )
// This is a stripped down kotlin implementation of github.com/prometheus-community/PushProx client // This is a stripped down kotlin implementation of github.com/prometheus-community/PushProx client
class PushProxClient(config: PushProxConfig) { class PushProxClient(config: PushProxConfig) {
//TODO dispose this thing - delete http client
private val config: PushProxConfig private val config: PushProxConfig
private val pollURL: String private val pollURL: String
private val pushURL: String private val pushURL: String
@ -88,8 +97,8 @@ class PushProxClient(config: PushProxConfig) {
init { init {
this.config = config this.config = config
// make sure proxyURL ends with a single '/' // make sure proxyURL ends without '/'
val modifiedProxyURL = config.proxyURL.trim('/') + '/' val modifiedProxyURL = config.proxyURL.trim('/')
log("ModifiedUrl", modifiedProxyURL) log("ModifiedUrl", modifiedProxyURL)
pollURL = "$modifiedProxyURL/poll" pollURL = "$modifiedProxyURL/poll"
@ -99,7 +108,7 @@ class PushProxClient(config: PushProxConfig) {
// initialize resource - heavier objects // initialize resource - heavier objects
private fun setup(){ private fun setup(){
// init counters if they are enabled // init counters if they are enabled
counters = Counters(config.enablePushProxClientMonitoring) counters = Counters(config.collectorRegistry)
client = HttpClient(CIO) client = HttpClient(CIO)
} }
@ -110,10 +119,15 @@ class PushProxClient(config: PushProxConfig) {
} }
private suspend fun doPoll(){ private suspend fun doPoll(){
log("poll", "polling now")
log(pollURL, pollURL)
val response : HttpResponse = client.post(pollURL){ val response : HttpResponse = client.post(pollURL){
setBody(config.myFqdn) setBody(config.myFqdn)
} }
log("here", "here")
val responseBody: String = response.body<String>() val responseBody: String = response.body<String>()
doPush(responseBody)
// response body is not needed
log("responseBody in poll", responseBody) log("responseBody in poll", responseBody)
log("got scrape request", responseBody) log("got scrape request", responseBody)
@ -121,16 +135,43 @@ class PushProxClient(config: PushProxConfig) {
} }
private fun parseRequest(request : String) : HttpRequestBuilder { private fun getIdFromResponseBody(responseBody: String) : String {
var result : HttpRequestBuilder = HttpRequestBuilder()
//TODO implement this //TODO implement this
return "5"
return result //TODO throw custom exception if this is wrong
} }
private fun doPush() { // responseBody: response body of /poll request
private suspend fun doPush(responseBody : String) {
//TODO implement //TODO implement
// perform scrape
lateinit var scrapedMetrics : String
try {
scrapedMetrics = config.performScrape()
}catch(e : Exception){
counters.scrapeError()
log("scrape exception", e.toString())
return
}
try{
log("scraped metrics in doPush", scrapedMetrics)
val scrapeId : String = getIdFromResponseBody(responseBody)
val response : HttpResponse = client.request(pushURL) {
header("id", scrapeId)
header("X-prometheus-scrape-timeout", "4") //TODO this is dummy for now
method = HttpMethod.Post
setBody(scrapedMetrics)
}
}catch(e : Exception){
counters.pushError()
log("push exception", e.toString())
return
}
} }
private fun handleErr(){ private fun handleErr(){
@ -151,10 +192,7 @@ class PushProxClient(config: PushProxConfig) {
) )
} }
private suspend fun exceptionTest(){
delay(1000L)
throw IllegalArgumentException()
}
private fun loop(backoff: StrategyBackoff<Unit>) { private fun loop(backoff: StrategyBackoff<Unit>) {
// fire and forget a new coroutine // fire and forget a new coroutine
@ -162,26 +200,18 @@ class PushProxClient(config: PushProxConfig) {
launch { launch {
while (true) { while (true) {
val job = launch { val job = launch {
log("pushprox loop now", "-") log("pushprox main loop", "loop start")
var result = backoff.withRetries { var result = backoff.withRetries {
val result: Deferred<Unit> = async { // register poll error using try-catch block
delay(1000L)
}
log("progress", "after poll")
// register poll error
try { try {
result.await() doPoll()
} catch (e: Exception) { } catch (e: Exception) {
log("progress", "catched") log("exception encountered!", e.toString())
log("exception", e.toString())
counters.pollError() counters.pollError()
throw e throw e
} }
} }
log("pushprox main loop", "loop end")
log("pushprox loop end", "end")
} }
job.join() job.join()
} }

View File

@ -1,2 +0,0 @@
# Server configuration, intended use of this demo
#TODO demo , describe all files

View File

@ -105,6 +105,7 @@
mode: 0644 mode: 0644
force: true force: true
register: config_files register: config_files
tags: config
- name: Copy docker-compose.yaml - name: Copy docker-compose.yaml
ansible.builtin.copy: ansible.builtin.copy:
@ -115,19 +116,22 @@
mode: 0644 mode: 0644
force: true force: true
register: compose_file register: compose_file
tags: config
- name: Pull images - name: Pull images
community.docker.docker_compose: community.docker.docker_compose:
pull: true pull: true
project_src: "{{ '/home/' + new_user_name }}" project_src: "{{ '/home/' + new_user_name }}"
tags: config
- name: Start docker compose - name: Start docker compose
community.docker.docker_compose: community.docker.docker_compose:
state: present state: present
project_src: "{{ '/home/' + new_user_name }}" project_src: "{{ '/home/' + new_user_name }}"
restarted: "{{ (config_files.changed | bool) or (compose_file.changed | bool) }}" restarted: "{{ (config_files.changed | bool) or (compose_file.changed | bool) }}"
tags: config
- name: Create docker compose systemd service - name: Create docker compose systemd service to start docker compose on boot
block: block:
- name: Copy docker compose unit file - name: Copy docker compose unit file
ansible.builtin.template: ansible.builtin.template:

View File

@ -13,7 +13,7 @@ http{
} }
upstream pushprox{ upstream pushprox{
server pushprox:8080; server pushprox:8080;
} }
upstream grafana{ upstream grafana{
@ -46,9 +46,14 @@ http{
server { server {
listen 8080; listen 8080;
proxy_read_timeout 3600;
proxy_connect_timeout 3600;
proxy_send_timeout 3600;
location / { location / {
proxy_pass http://pushprox; proxy_pass http://pushprox;
} }
} }
# Prometheus UI server configuration # Prometheus UI server configuration

View File

@ -10,4 +10,8 @@ scrape_configs:
- job_name: "android phones" - job_name: "android phones"
proxy_url: "http://pushprox:8080" #TODO add mobile phones here proxy_url: "http://pushprox:8080" #TODO add mobile phones here
static_configs:
- targets: [
"test.example.com"
]