mirror of
https://github.com/mii443/prometheus-android-exporter.git
synced 2025-09-02 23:29:23 +00:00
wip
This commit is contained in:
@ -2,12 +2,14 @@ package com.birdthedeveloper.prometheus.android.prometheus.android.exporter.work
|
|||||||
|
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlin.math.min
|
||||||
import kotlin.math.pow
|
import kotlin.math.pow
|
||||||
|
|
||||||
class ExponentialBackoff {
|
class ExponentialBackoff {
|
||||||
companion object {
|
companion object {
|
||||||
private const val multiplier: Double = 2.0
|
private const val multiplier: Double = 2.0
|
||||||
private const val initialDelay = 3.0 // seconds
|
private const val initialDelay = 3.0 // seconds
|
||||||
|
private const val maxDelay = 61.0 // seconds
|
||||||
|
|
||||||
suspend fun runWithBackoff(function: suspend () -> Unit, onException: () -> Unit) {
|
suspend fun runWithBackoff(function: suspend () -> Unit, onException: () -> Unit) {
|
||||||
var successfull: Boolean = false
|
var successfull: Boolean = false
|
||||||
@ -34,6 +36,7 @@ class ExponentialBackoff {
|
|||||||
// calculate new delay
|
// calculate new delay
|
||||||
currentExpIndex++
|
currentExpIndex++
|
||||||
currentDelay = initialDelay + multiplier.pow(currentExpIndex)
|
currentDelay = initialDelay + multiplier.pow(currentExpIndex)
|
||||||
|
currentDelay = min(currentDelay, maxDelay)
|
||||||
}
|
}
|
||||||
|
|
||||||
delay(currentDelay.toLong())
|
delay(currentDelay.toLong())
|
||||||
|
@ -12,8 +12,11 @@ import com.birdthedeveloper.prometheus.android.prometheus.android.exporter.R
|
|||||||
import com.birdthedeveloper.prometheus.android.prometheus.android.exporter.compose.PromConfiguration
|
import com.birdthedeveloper.prometheus.android.prometheus.android.exporter.compose.PromConfiguration
|
||||||
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.DelicateCoroutinesApi
|
||||||
import kotlinx.coroutines.coroutineScope
|
import kotlinx.coroutines.coroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.newSingleThreadContext
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.StringWriter
|
import java.io.StringWriter
|
||||||
|
|
||||||
private const val TAG = "Worker"
|
private const val TAG = "Worker"
|
||||||
@ -45,19 +48,23 @@ class PromWorker(
|
|||||||
androidCustomExporter = AndroidCustomExporter(metricsEngine).register(collectorRegistry)
|
androidCustomExporter = AndroidCustomExporter(metricsEngine).register(collectorRegistry)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun countSuccessfulScrape(){
|
private suspend fun countSuccessfulScrape(){
|
||||||
remoteWriteSender?.countSuccessfulScrape()
|
remoteWriteSender?.countSuccessfulScrape()
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun startServices(config: PromConfiguration) {
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
|
private suspend fun startServicesInOneThread(config: PromConfiguration){
|
||||||
|
val threadContext = newSingleThreadContext("PromWorkerThreadContext")
|
||||||
|
|
||||||
var deferred = coroutineScope {
|
var deferred = coroutineScope {
|
||||||
|
withContext(threadContext){
|
||||||
|
|
||||||
if (config.remoteWriteEnabled) {
|
if (config.remoteWriteEnabled) {
|
||||||
remoteWriteSender = RemoteWriteSender(
|
remoteWriteSender = RemoteWriteSender(
|
||||||
RemoteWriteConfiguration(
|
RemoteWriteConfiguration(
|
||||||
config.remoteWriteScrapeInterval,
|
config.remoteWriteScrapeInterval,
|
||||||
config.remoteWriteEndpoint,
|
config.remoteWriteEndpoint,
|
||||||
::performScrape,
|
collectorRegistry,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
launch {
|
launch {
|
||||||
@ -91,9 +98,7 @@ class PromWorker(
|
|||||||
pushProxClient.start()
|
pushProxClient.start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,7 +109,7 @@ class PromWorker(
|
|||||||
//setForeground(createForegroundInfo())
|
//setForeground(createForegroundInfo())
|
||||||
|
|
||||||
initializeWork(inputConfiguration)
|
initializeWork(inputConfiguration)
|
||||||
startServices(inputConfiguration)
|
startServicesInOneThread(inputConfiguration)
|
||||||
|
|
||||||
return Result.success()
|
return Result.success()
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ private const val TAG = "PROMETHEUS_SERVER"
|
|||||||
data class PrometheusServerConfig(
|
data class PrometheusServerConfig(
|
||||||
val port: Int,
|
val port: Int,
|
||||||
val performScrape: suspend () -> String,
|
val performScrape: suspend () -> String,
|
||||||
val countSuccessfulScrape: () -> Unit,
|
val countSuccessfulScrape: suspend () -> Unit,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ data class PushProxConfig(
|
|||||||
val pushProxFqdn: String,
|
val pushProxFqdn: String,
|
||||||
val registry: CollectorRegistry,
|
val registry: CollectorRegistry,
|
||||||
val performScrape: () -> String,
|
val performScrape: () -> String,
|
||||||
val countSuccessfulScrape: () -> Unit,
|
val countSuccessfulScrape: suspend () -> Unit,
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -8,58 +8,54 @@ import io.ktor.client.request.headers
|
|||||||
import io.ktor.client.request.post
|
import io.ktor.client.request.post
|
||||||
import io.ktor.client.request.setBody
|
import io.ktor.client.request.setBody
|
||||||
import io.ktor.http.HttpHeaders
|
import io.ktor.http.HttpHeaders
|
||||||
|
import io.prometheus.client.CollectorRegistry
|
||||||
|
import kotlinx.coroutines.NonCancellable
|
||||||
|
import kotlinx.coroutines.channels.Channel
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import org.iq80.snappy.Snappy
|
import org.iq80.snappy.Snappy
|
||||||
import remote.write.RemoteWrite
|
import remote.write.RemoteWrite
|
||||||
import remote.write.RemoteWrite.Label
|
import remote.write.RemoteWrite.Label
|
||||||
import remote.write.RemoteWrite.TimeSeries
|
import remote.write.RemoteWrite.TimeSeries
|
||||||
import remote.write.RemoteWrite.WriteRequest
|
import remote.write.RemoteWrite.WriteRequest
|
||||||
import java.util.Timer
|
|
||||||
import kotlin.concurrent.schedule
|
|
||||||
|
|
||||||
private const val TAG: String = "REMOTE_WRITE_SENDER"
|
private const val TAG: String = "REMOTE_WRITE_SENDER"
|
||||||
|
|
||||||
private class LastTimeMutex{
|
|
||||||
private val mutex = Mutex()
|
|
||||||
private var lastTime : Long = 0
|
|
||||||
suspend fun setLastTime(time : Long){
|
|
||||||
mutex.withLock {
|
|
||||||
lastTime = time
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fun getLastTime() : Long { return lastTime }
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum class RemoteWriteSenderState {
|
private enum class RemoteWriteSenderState {
|
||||||
REMOTE_WRITE,
|
REMOTE_WRITE,
|
||||||
PUSHPROX_OR_PROMETHEUS_SERVER,
|
PUSHPROX_OR_PROMETHEUS_SERVER,
|
||||||
}
|
}
|
||||||
|
|
||||||
private class RemoteWriteSenderStateMutex {
|
private class LastTimeRingBuffer {
|
||||||
private val mutex = Mutex()
|
//TODO implement this with ring array
|
||||||
private var remoteWriteSenderState = RemoteWriteSenderState.PUSHPROX_OR_PROMETHEUS_SERVER
|
|
||||||
suspend fun setRemoteWriteSenderState(state : RemoteWriteSenderState){
|
private fun checkScrapeDidNotHappenInTime() : Boolean {
|
||||||
mutex.withLock {
|
return lastTimeMutex.getLastTime() < System.currentTimeMillis() - 3 * config.scrape_interval
|
||||||
remoteWriteSenderState = state
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getRemoteWriteSenderState() : RemoteWriteSenderState {
|
private fun checkScrapeDidNotHappenHysteresis() : Boolean {
|
||||||
return remoteWriteSenderState
|
return false //TODO implement this with ring buffer in lastTimeMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class RemoteWriteConfiguration(
|
data class RemoteWriteConfiguration(
|
||||||
val scrape_interval: Int,
|
val scrape_interval: Int,
|
||||||
val remote_write_endpoint: String,
|
val remote_write_endpoint: String,
|
||||||
val performScrape: () -> String, //TODO this class needs it structured in objects
|
val collectorRegistry: CollectorRegistry,
|
||||||
)
|
)
|
||||||
|
|
||||||
//TODO implement this thing
|
|
||||||
class RemoteWriteSender(private val config: RemoteWriteConfiguration) {
|
class RemoteWriteSender(private val config: RemoteWriteConfiguration) {
|
||||||
private val lastTimeMutex = LastTimeMutex()
|
// TODO ring buffer for last time
|
||||||
|
// TODO last time into it's own object with boolean functions
|
||||||
|
// private val lastTimeMutex = LastTimeMutex()
|
||||||
|
private var alreadyStoredSampleLength : Int = 0
|
||||||
|
private val storage : RemoteWriteSenderStorage = RemoteWriteSenderMemoryStorage()
|
||||||
|
private val scrapesAreBeingSentMutex = Mutex()
|
||||||
|
|
||||||
private fun getRequestBody(): ByteArray {
|
private fun getRequestBody(): ByteArray {
|
||||||
val label1: Label = Label.newBuilder()
|
val label1: Label = Label.newBuilder()
|
||||||
@ -104,25 +100,112 @@ class RemoteWriteSender(private val config: RemoteWriteConfiguration) {
|
|||||||
return request.toByteArray()
|
return request.toByteArray()
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun start(){
|
private fun performScrape() : MetricsScrape {
|
||||||
|
|
||||||
|
|
||||||
|
for ( item in config.collectorRegistry.metricFamilySamples() ){
|
||||||
|
val name : String = item.name
|
||||||
|
for (sample in item.samples){
|
||||||
|
val timestamp : Long = sample.timestampMs
|
||||||
|
|
||||||
|
val labelValueIterator : Iterator<String> = sample.labelValues.iterator()
|
||||||
|
for(labelName in sample.labelNames){
|
||||||
|
val protobufLabel : Label = Label.newBuilder()
|
||||||
|
.setName(labelName)
|
||||||
|
.setValue(labelValueIterator.next())
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
val sampleValue : Double = sample.value
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO channel je k hovnu
|
||||||
|
//TODO v remotewriteseender storage musi byt mutex
|
||||||
|
|
||||||
|
|
||||||
|
private suspend fun scraper(channel : Channel<Unit>){
|
||||||
|
val checkDelay = 1000L
|
||||||
|
while (true){
|
||||||
|
if (checkScrapeDidNotHappenInTime()){
|
||||||
|
delay(config.scrape_interval * 1000L)
|
||||||
|
storage.writeScrapedSample(performScrape())
|
||||||
|
|
||||||
|
while(checkScrapeDidNotHappenHysteresis()){
|
||||||
|
delay(config.scrape_interval * 1000L)
|
||||||
|
storage.writeScrapedSample(performScrape())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delay(checkDelay)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sending metric scrapes to remote write endpoint will not be parallel
|
||||||
|
private suspend fun sendAll(){
|
||||||
|
scrapesAreBeingSentMutex.withLock {
|
||||||
|
// Take all metric samples and send them in batches of (max_samples_per_send)
|
||||||
|
// one by one batch
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun senderManager(channel : Channel<Unit>){
|
||||||
|
val alreadyStoredMetricScrapes : Int = storage.getLength()
|
||||||
|
|
||||||
runBlocking {
|
runBlocking {
|
||||||
//TODO from here
|
if (alreadyStoredMetricScrapes > 0){
|
||||||
|
launch { // fire and forget
|
||||||
|
sendAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
channel.receive()
|
||||||
|
|
||||||
|
|
||||||
|
// suspended on channel.receive
|
||||||
|
|
||||||
|
// when there are enough to send:
|
||||||
|
// start a sender
|
||||||
|
|
||||||
|
// send with these conditions:
|
||||||
|
//
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun countSuccessfulScrape(){
|
suspend fun start(){
|
||||||
Log.v(TAG, "countSuccesful scrape")
|
val channel = Channel<Unit>()
|
||||||
//TODO implement this "last time" and mutex
|
try {
|
||||||
|
runBlocking {
|
||||||
|
launch {
|
||||||
|
scraper(channel)
|
||||||
|
}
|
||||||
|
launch {
|
||||||
|
senderManager(channel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
withContext(NonCancellable){
|
||||||
|
channel.close()
|
||||||
|
Log.v(TAG, "Canceling Remote Write Sender")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
scheduleCheckScrapesHappened()
|
suspend fun countSuccessfulScrape(){
|
||||||
|
Log.v(TAG, "Counting successful scrape")
|
||||||
|
lastTimeMutex.setLastTime(System.currentTimeMillis())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun encodeWithSnappy(data: ByteArray): ByteArray {
|
private fun encodeWithSnappy(data: ByteArray): ByteArray {
|
||||||
return Snappy.compress(data)
|
return Snappy.compress(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun sendTestRequest() {
|
private suspend fun sendTestRequest() {
|
||||||
Log.v(TAG, "sending to prometheus now")
|
Log.v(TAG, "sending to prometheus now")
|
||||||
val client = HttpClient()
|
val client = HttpClient()
|
||||||
val response = client.post(config.remote_write_endpoint) {
|
val response = client.post(config.remote_write_endpoint) {
|
||||||
@ -141,4 +224,3 @@ class RemoteWriteSender(private val config: RemoteWriteConfiguration) {
|
|||||||
client.close()
|
client.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,16 +1,29 @@
|
|||||||
package com.birdthedeveloper.prometheus.android.prometheus.android.exporter.worker
|
package com.birdthedeveloper.prometheus.android.prometheus.android.exporter.worker
|
||||||
|
|
||||||
typealias MetricsScrape = String
|
import java.sql.Timestamp
|
||||||
|
import java.util.LinkedList
|
||||||
|
import java.util.Queue
|
||||||
|
|
||||||
// define the contract for Remote Write Sender storage
|
//TODO toto je na houby cele, musi byt structured misto byte array
|
||||||
|
class MetricsScrape(
|
||||||
|
val compressedMetrics : ByteArray,
|
||||||
|
val timestamp: Long,
|
||||||
|
)
|
||||||
|
|
||||||
|
// No need for locks as all operations are run on a single thread, defined in PromWorker
|
||||||
|
// This class defines contract for RemoteWriteSender storage
|
||||||
abstract class RemoteWriteSenderStorage {
|
abstract class RemoteWriteSenderStorage {
|
||||||
abstract fun writeScrapedSample(metricsScrape: MetricsScrape)
|
abstract fun writeScrapedSample(metricsScrape: MetricsScrape)
|
||||||
abstract fun getNumberOfScrapedSamples(number: Int): List<MetricsScrape>
|
abstract fun getNumberOfScrapedSamples(number: Int): List<MetricsScrape>
|
||||||
abstract fun removeNumberOfScrapedSamples(number: Int)
|
abstract fun removeNumberOfScrapedSamples(number: Int)
|
||||||
abstract fun isEmpty(): Boolean
|
abstract fun isEmpty(): Boolean
|
||||||
|
abstract fun getLength(): Int
|
||||||
}
|
}
|
||||||
|
|
||||||
class RemoteWriteSenderMemoryStorage : RemoteWriteSenderStorage() {
|
class RemoteWriteSenderMemoryStorage : RemoteWriteSenderStorage() {
|
||||||
|
// writeRequests are stored in protobuf already compressed
|
||||||
|
private val data : Queue<MetricsScrape> = LinkedList<MetricsScrape>()
|
||||||
|
|
||||||
override fun getNumberOfScrapedSamples(number: Int): List<MetricsScrape> {
|
override fun getNumberOfScrapedSamples(number: Int): List<MetricsScrape> {
|
||||||
TODO("Not yet implemented")
|
TODO("Not yet implemented")
|
||||||
}
|
}
|
||||||
@ -26,6 +39,10 @@ class RemoteWriteSenderMemoryStorage : RemoteWriteSenderStorage() {
|
|||||||
override fun isEmpty(): Boolean {
|
override fun isEmpty(): Boolean {
|
||||||
TODO("Not yet implemented")
|
TODO("Not yet implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getLength(): Int {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class RemoteWriteSenterDatabaseStorage : RemoteWriteSenderStorage() {
|
class RemoteWriteSenterDatabaseStorage : RemoteWriteSenderStorage() {
|
||||||
@ -44,4 +61,8 @@ class RemoteWriteSenterDatabaseStorage : RemoteWriteSenderStorage() {
|
|||||||
override fun isEmpty(): Boolean {
|
override fun isEmpty(): Boolean {
|
||||||
TODO("Not yet implemented")
|
TODO("Not yet implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getLength(): Int {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
}
|
}
|
@ -32,3 +32,6 @@ remote_write:
|
|||||||
# data type: string, no default value provided
|
# data type: string, no default value provided
|
||||||
# example: http://localhost:9090/
|
# example: http://localhost:9090/
|
||||||
remote_write_endpoint:
|
remote_write_endpoint:
|
||||||
|
|
||||||
|
# default value is 5 scrapes
|
||||||
|
max_samples_per_send: 5
|
||||||
|
Reference in New Issue
Block a user