mirror of
https://github.com/mii443/prometheus-android-exporter.git
synced 2025-08-22 15:15:35 +00:00
doPush impl almost done
This commit is contained in:
22
README.md
22
README.md
@ -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
|
||||||
|
```
|
||||||
|
|
||||||
|
@ -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'
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
@ -1,2 +0,0 @@
|
|||||||
# Server configuration, intended use of this demo
|
|
||||||
#TODO demo , describe all files
|
|
@ -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:
|
||||||
|
@ -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
|
||||||
|
@ -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"
|
||||||
|
]
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user