Skip to content

Commit

Permalink
Merge pull request #625 from lenguyenthanh/prometheus-exporter
Browse files Browse the repository at this point in the history
Improve Prometheus exporter
  • Loading branch information
ornicar authored Dec 1, 2024
2 parents 9dc34d5 + 84a8db9 commit 1643ba1
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 1 deletion.
2 changes: 1 addition & 1 deletion src/main/scala/Monitor.scala
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ final class Monitor(
logger.info(s"lila-ws 3.0 netty kamon=$useKamon")
logger.info(s"Java version: $version, memory: ${memory}MB")

if useKamon then kamon.Kamon.init()
if useKamon then kamon.Kamon.initWithoutAttaching()

scheduler.scheduleWithFixedDelay(5.seconds, 1949.millis) { () => periodicMetrics() }

Expand Down
92 changes: 92 additions & 0 deletions src/main/scala/util/PrometheusExporter.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package lila.ws

import com.typesafe.config.Config
import kamon.Kamon
import kamon.metric.*
import kamon.module.{ MetricReporter, Module, ModuleFactory }
import kamon.prometheus.*

import java.time.Duration

// copy paste with some minor changes/scalafmtAll from https://github.com/kamon-io/Kamon/blob/master/reporters/kamon-prometheus/src/main/scala/kamon/prometheus/PrometheusReporter.scala
// unused http server has been removed
class PrometheusReporter(configPath: String = DefaultConfigPath, initialConfig: Config = Kamon.config())
extends MetricReporter
with ScrapeSource:

import PrometheusReporter.readSettings
import kamon.prometheus.PrometheusSettings.environmentTags

private val stalePeriod = Duration.ofSeconds(2 * 24 * 60 * 60 + 1) // 2 days + 1 second
private val _snapshotAccumulator =
PeriodSnapshot.accumulator(stalePeriod, Duration.ZERO, stalePeriod)

@volatile private var _preparedScrapeData: String =
"# The kamon-prometheus module didn't receive any data just yet.\n"

@volatile private var _config = initialConfig
@volatile private var _reporterSettings = readSettings(initialConfig.getConfig(configPath))

override def stop(): Unit =
// Removes a reference to the last reporter to avoid leaking instances.
//
// It might not be safe to assume that **this** object is the last created instance, but in practice
// users never have more than one instance running. If they do, they can handle access to their instances
// by themselves.
PrometheusReporter._lastCreatedInstance = None

override def reconfigure(newConfig: Config): Unit =
_reporterSettings = readSettings(newConfig.getConfig(configPath))
_config = newConfig

override def reportPeriodSnapshot(snapshot: PeriodSnapshot): Unit =
_snapshotAccumulator.add(snapshot)
val currentData = _snapshotAccumulator.peek()
val scrapeDataBuilder =
new ScrapeDataBuilder(_reporterSettings.generic, environmentTags(_reporterSettings.generic))

scrapeDataBuilder.appendCounters(currentData.counters)
scrapeDataBuilder.appendGauges(currentData.gauges)
scrapeDataBuilder.appendDistributionMetricsAsGauges(
snapshot.rangeSamplers ++ snapshot.histograms ++ snapshot.timers
)
scrapeDataBuilder.appendHistograms(currentData.histograms)
scrapeDataBuilder.appendHistograms(currentData.timers)
scrapeDataBuilder.appendHistograms(currentData.rangeSamplers)
_preparedScrapeData = scrapeDataBuilder.build()

def scrapeData(): String =
_preparedScrapeData

object PrometheusReporter:

final val DefaultConfigPath = "kamon.prometheus"

/** We keep a reference to the last created Prometheus Reporter instance so that users can easily access it
* if they want to publish the scrape data through their own HTTP server.
*/
@volatile private var _lastCreatedInstance: Option[PrometheusReporter] = None

/** Returns the latest Prometheus scrape data created by the latest PrometheusReporter instance created
* automatically by Kamon. If you are creating more than one PrometheusReporter instance you might prefer
* to keep references to those instances programmatically and calling `.scrapeData()` directly on them
* instead of using this function.
*/
def latestScrapeData(): Option[String] =
_lastCreatedInstance.map(_.scrapeData())

class Factory extends ModuleFactory:
override def create(settings: ModuleFactory.Settings): Module =
val reporter = new PrometheusReporter(DefaultConfigPath, settings.config)
_lastCreatedInstance = Some(reporter)
reporter

def create(): PrometheusReporter =
new PrometheusReporter()

case class Settings(generic: PrometheusSettings.Generic)

def readSettings(prometheusConfig: Config): PrometheusReporter.Settings =
PrometheusReporter.Settings(
generic = PrometheusSettings.readSettings(prometheusConfig)
)

0 comments on commit 1643ba1

Please sign in to comment.