diff --git a/README.md b/README.md index 576d08e..4db3c77 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,24 @@ # prometheus-android-exporter 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 +``` + diff --git a/client/app/build.gradle b/client/app/build.gradle index d1364ef..ceab13f 100644 --- a/client/app/build.gradle +++ b/client/app/build.gradle @@ -59,9 +59,8 @@ dependencies { implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.0-RC" // custom - ktor web client - implementation("io.ktor:ktor-server-core:2.2.4") - runtimeOnly("io.ktor:ktor-client-core:2.2.4") - implementation("io.ktor:ktor-client-cio:2.2.4") + implementation("io.ktor:ktor-client-core:2.3.0") + implementation("io.ktor:ktor-client-cio:2.3.0") implementation 'androidx.core:core-ktx:1.7.0' diff --git a/client/app/src/main/java/com/birdthedeveloper/prometheus/android/prometheus/android/exporter/MainActivity.kt b/client/app/src/main/java/com/birdthedeveloper/prometheus/android/prometheus/android/exporter/MainActivity.kt index 26bdc9a..bd33f3a 100644 --- a/client/app/src/main/java/com/birdthedeveloper/prometheus/android/prometheus/android/exporter/MainActivity.kt +++ b/client/app/src/main/java/com/birdthedeveloper/prometheus/android/prometheus/android/exporter/MainActivity.kt @@ -18,6 +18,7 @@ import com.birdthedeveloper.prometheus.android.prometheus.android.exporter.ui.th import io.prometheus.client.Collector import io.prometheus.client.CollectorRegistry import io.prometheus.client.exporter.common.TextFormat +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import java.io.StringWriter @@ -27,6 +28,7 @@ class MainActivity : ComponentActivity() { private lateinit var metricsEngine: MetricsEngine private lateinit var customExporter: AndroidCustomExporter + private var pushProxStarted : Boolean = false private lateinit var pushProxClient : PushProxClient override fun onCreate(savedInstanceState: Bundle?) { @@ -51,22 +53,32 @@ class MainActivity : ComponentActivity() { 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() TextFormat.write004(writer, collectorRegistry.metricFamilySamples()) // initialize PushProx - pushProxClient = PushProxClient( - config = PushProxConfig( - "test.example.com", - "143.42.59.63", - 1, - 5, - true - + if (!pushProxStarted) { + pushProxClient = PushProxClient( + config = PushProxConfig( + "test.example.com", + "http://143.42.59.63:8080", + 1, + 5, + collectorRegistry, + ::reallyCollectMetrics, + ) ) - ) - pushProxClient.startBackground() + pushProxClient.startBackground() + pushProxStarted = true + } return writer.toString() } diff --git a/client/app/src/main/java/com/birdthedeveloper/prometheus/android/prometheus/android/exporter/PushProxClient.kt b/client/app/src/main/java/com/birdthedeveloper/prometheus/android/prometheus/android/exporter/PushProxClient.kt index b8a5be8..f8e325e 100644 --- a/client/app/src/main/java/com/birdthedeveloper/prometheus/android/prometheus/android/exporter/PushProxClient.kt +++ b/client/app/src/main/java/com/birdthedeveloper/prometheus/android/prometheus/android/exporter/PushProxClient.kt @@ -1,5 +1,6 @@ package com.birdthedeveloper.prometheus.android.prometheus.android.exporter +import android.preference.PreferenceActivity.Header import android.util.Log import io.github.reugn.kotlin.backoff.StrategyBackoff 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.request.HttpRequestBuilder import io.ktor.client.request.get +import io.ktor.client.request.header import io.ktor.client.request.post import io.ktor.client.request.request import io.ktor.client.request.setBody import io.ktor.client.statement.HttpResponse +import io.ktor.http.HttpHeaders import io.ktor.http.HttpMethod import io.ktor.http.URLBuilder import io.ktor.http.Url import io.ktor.http.maxAge +import io.prometheus.client.CollectorRegistry import io.prometheus.client.Counter import kotlinx.coroutines.Deferred import kotlinx.coroutines.GlobalScope @@ -26,35 +30,38 @@ import kotlinx.coroutines.runBlocking import kotlin.time.Duration // Counters for monitoring the pushprox itself, compatible with the reference implementation in go. -private class Counters(enabled: Boolean) { - private val enabled : Boolean +private class Counters(collectorRegistry: CollectorRegistry?) { + private val collectorRegistry : CollectorRegistry? private lateinit var scrapeErrorCounter : Counter private lateinit var pushErrorCounter : Counter private lateinit var pollErrorCounter : Counter private lateinit var pollSuccessCounter : Counter + private var enabled : Boolean = false init { - this.enabled = enabled - if (enabled){ + this.collectorRegistry = collectorRegistry + if (collectorRegistry != null){ + this.enabled = true + // following 3 counters are compatible with reference implementation scrapeErrorCounter = Counter.build() .name("pushprox_client_scrape_errors_total") .help("Number of scrape errors") - .register() + .register(collectorRegistry) pushErrorCounter = Counter.build() .name("pushprox_client_push_errors_total") .help("Number of push errors") - .register() + .register(collectorRegistry) pollErrorCounter = Counter.build() .name("pushprox_client_poll_errors_total") .help("Number of poll errors") - .register() + .register(collectorRegistry) // custom pollSuccessCounter = Counter.build() .name("pushprox_client_poll_total") .help("Number of succesfull polls") - .register() + .register(collectorRegistry) } } @@ -73,11 +80,13 @@ data class PushProxConfig( val proxyURL: String, val retryInitialWaitSeconds: Int = 1, //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 class PushProxClient(config: PushProxConfig) { + //TODO dispose this thing - delete http client private val config: PushProxConfig private val pollURL: String private val pushURL: String @@ -88,8 +97,8 @@ class PushProxClient(config: PushProxConfig) { init { this.config = config - // make sure proxyURL ends with a single '/' - val modifiedProxyURL = config.proxyURL.trim('/') + '/' + // make sure proxyURL ends without '/' + val modifiedProxyURL = config.proxyURL.trim('/') log("ModifiedUrl", modifiedProxyURL) pollURL = "$modifiedProxyURL/poll" @@ -99,7 +108,7 @@ class PushProxClient(config: PushProxConfig) { // initialize resource - heavier objects private fun setup(){ // init counters if they are enabled - counters = Counters(config.enablePushProxClientMonitoring) + counters = Counters(config.collectorRegistry) client = HttpClient(CIO) } @@ -110,10 +119,15 @@ class PushProxClient(config: PushProxConfig) { } private suspend fun doPoll(){ + log("poll", "polling now") + log(pollURL, pollURL) val response : HttpResponse = client.post(pollURL){ setBody(config.myFqdn) } + log("here", "here") val responseBody: String = response.body() + doPush(responseBody) + // response body is not needed log("responseBody in poll", responseBody) log("got scrape request", responseBody) @@ -121,16 +135,43 @@ class PushProxClient(config: PushProxConfig) { } - private fun parseRequest(request : String) : HttpRequestBuilder { - var result : HttpRequestBuilder = HttpRequestBuilder() - + private fun getIdFromResponseBody(responseBody: String) : String { //TODO implement this - - return result + return "5" + //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 + + // 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(){ @@ -151,10 +192,7 @@ class PushProxClient(config: PushProxConfig) { ) } - private suspend fun exceptionTest(){ - delay(1000L) - throw IllegalArgumentException() - } + private fun loop(backoff: StrategyBackoff) { // fire and forget a new coroutine @@ -162,26 +200,18 @@ class PushProxClient(config: PushProxConfig) { launch { while (true) { val job = launch { - log("pushprox loop now", "-") + log("pushprox main loop", "loop start") var result = backoff.withRetries { - val result: Deferred = async { - delay(1000L) - } - - log("progress", "after poll") - - // register poll error + // register poll error using try-catch block try { - result.await() + doPoll() } catch (e: Exception) { - log("progress", "catched") - log("exception", e.toString()) + log("exception encountered!", e.toString()) counters.pollError() throw e } } - - log("pushprox loop end", "end") + log("pushprox main loop", "loop end") } job.join() } diff --git a/server/README.md b/server/README.md deleted file mode 100644 index 3b37807..0000000 --- a/server/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# Server configuration, intended use of this demo -#TODO demo , describe all files \ No newline at end of file diff --git a/server/ansible_playbook.yaml b/server/ansible_playbook.yaml index 18fa96c..df28a31 100644 --- a/server/ansible_playbook.yaml +++ b/server/ansible_playbook.yaml @@ -105,6 +105,7 @@ mode: 0644 force: true register: config_files + tags: config - name: Copy docker-compose.yaml ansible.builtin.copy: @@ -115,19 +116,22 @@ mode: 0644 force: true register: compose_file + tags: config - name: Pull images community.docker.docker_compose: pull: true project_src: "{{ '/home/' + new_user_name }}" + tags: config - name: Start docker compose community.docker.docker_compose: state: present project_src: "{{ '/home/' + new_user_name }}" 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: - name: Copy docker compose unit file ansible.builtin.template: diff --git a/server/configuration/nginx.conf b/server/configuration/nginx.conf index 2231751..5d8d07a 100644 --- a/server/configuration/nginx.conf +++ b/server/configuration/nginx.conf @@ -13,7 +13,7 @@ http{ } upstream pushprox{ - server pushprox:8080; + server pushprox:8080; } upstream grafana{ @@ -46,9 +46,14 @@ http{ server { listen 8080; + proxy_read_timeout 3600; + proxy_connect_timeout 3600; + proxy_send_timeout 3600; + location / { proxy_pass http://pushprox; } + } # Prometheus UI server configuration diff --git a/server/configuration/prometheus.yaml b/server/configuration/prometheus.yaml index d772f52..3ccc857 100644 --- a/server/configuration/prometheus.yaml +++ b/server/configuration/prometheus.yaml @@ -10,4 +10,8 @@ scrape_configs: - job_name: "android phones" proxy_url: "http://pushprox:8080" #TODO add mobile phones here + static_configs: + - targets: [ + "test.example.com" + ]