From 0a2b7f4bf0dde31c82482fbaf5153c22c84ada69 Mon Sep 17 00:00:00 2001 From: Ivan Topolnjak Date: Mon, 4 Feb 2019 19:15:43 +0100 Subject: cleanup the exposition of status data and ensure the module registry provides enough info --- .../main/scala/kamon/module/ModuleRegistry.scala | 463 +++++++++++++++++++++ 1 file changed, 463 insertions(+) create mode 100644 kamon-core/src/main/scala/kamon/module/ModuleRegistry.scala (limited to 'kamon-core/src/main/scala/kamon/module/ModuleRegistry.scala') diff --git a/kamon-core/src/main/scala/kamon/module/ModuleRegistry.scala b/kamon-core/src/main/scala/kamon/module/ModuleRegistry.scala new file mode 100644 index 00000000..5520c602 --- /dev/null +++ b/kamon-core/src/main/scala/kamon/module/ModuleRegistry.scala @@ -0,0 +1,463 @@ +package kamon +package module + +import java.time.{Duration, Instant} +import java.util.concurrent.{CountDownLatch, Executors, ScheduledFuture, TimeUnit} +import java.util.concurrent.atomic.AtomicReference + +import com.typesafe.config.Config +import kamon.metric.{MetricsSnapshotGenerator, PeriodSnapshot} +import kamon.module.Module.Registration +import kamon.status.Status +import kamon.trace.Tracer.SpanBuffer +import kamon.util.Clock +import org.slf4j.LoggerFactory + +import scala.collection.JavaConverters.asScalaBufferConverter +import scala.concurrent.{ExecutionContext, ExecutionContextExecutorService, Future, Promise} +import scala.util.Try +import scala.util.control.NonFatal + + + +/** + * Controls the lifecycle of all available modules. + */ +class ModuleRegistry(classLoading: ClassLoading, configuration: Configuration, clock: Clock, snapshotGenerator: MetricsSnapshotGenerator, spanBuffer: SpanBuffer) { + private val _logger = LoggerFactory.getLogger(classOf[ModuleRegistry]) + private val _metricsTickerExecutor = Executors.newScheduledThreadPool(1, threadFactory("kamon-metrics-ticker", daemon = true)) + private val _spansTickerExecutor = Executors.newScheduledThreadPool(1, threadFactory("kamon-spans-ticker", daemon = true)) + + private val _metricsTickerSchedule = new AtomicReference[ScheduledFuture[_]]() + private val _spansTickerSchedule = new AtomicReference[ScheduledFuture[_]]() + + private var _registrySettings = readRegistrySettings(configuration.config()) + private var _registeredModules: Map[String, Entry[Module]] = Map.empty + private var _metricReporterModules: Map[String, Entry[MetricReporter]] = Map.empty + private var _spanReporterModules: Map[String, Entry[SpanReporter]] = Map.empty + + // Start ticking as soon as the registry is created. + scheduleMetricsTicker() + scheduleSpansTicker() + + + /** + * Registers a module that has already been instantiated by the user. The start callback will be executed as part + * of the registration process. If a module with the specified name already exists the registration will fail. If + * the registered module is a MetricReporter and/or SpanReporter it will also be configured to receive the metrics + * and spans data upon every tick. + * + * @param name Desired module name. + * @param module Module instance. + * @return A registration that can be used to stop the module at any time. + */ + def register(name: String, module: Module): Registration = synchronized { + if(_registeredModules.get(name).isEmpty) { + val inferredSettings = Module.Settings( + name, + module.getClass.getName, + module.getClass, + inferModuleKind(module.getClass), + true + ) + + val moduleEntry = createEntry(inferredSettings, true, module) + startModule(moduleEntry) + registration(moduleEntry) + + } else { + _logger.warn(s"Cannot register module [$name], a module with that name already exists.") + noopRegistration(name) + } + } + + /** + * Reads all available modules from the config and either starts, stops or reconfigures them in order to match the + * configured modules state. + */ + def load(config: Config): Unit = synchronized { + val configuredModules = readModuleSettings(config) + val automaticallyRegisteredModules = _registeredModules.filterNot { case (_, module) => module.programmaticallyAdded } + + // Start, reconfigure and stop modules that are still present but disabled. + configuredModules.foreach { moduleSettings => + automaticallyRegisteredModules.get(moduleSettings.name).fold { + // The module does not exist in the registry, the only possible action is starting it, if enabled. + if(moduleSettings.enabled) { + createModule(moduleSettings).foreach(entry => startModule(entry)) + } + + } { existentModuleSettings => + // When a module already exists it can either need to be stopped, or to be reconfigured. + if(moduleSettings.enabled) { + reconfigureModule(existentModuleSettings, config) + } else { + stopModule(existentModuleSettings) + } + } + } + + // Remove all modules that no longer exist in the configuration. + val missingModules = automaticallyRegisteredModules.filterKeys(moduleName => configuredModules.find(_.name == moduleName).isEmpty) + missingModules.foreach { + case (_, entry) => stopModule(entry) + } + } + + /** + * Schedules the reconfigure hook on all registered modules and applies the latest configuration settings to the + * registry. + */ + def reconfigure(newConfig: Config): Unit = synchronized { + _registrySettings = readRegistrySettings(configuration.config()) + _registeredModules.values.foreach(entry => reconfigureModule(entry, newConfig)) + scheduleMetricsTicker() + scheduleSpansTicker() + } + + /** + * Stops all registered modules. As part of the stop process, all modules get a last chance to report metrics and + * spans available until the call to stop. + */ + def stop(): Future[Unit] = synchronized { + implicit val cleanupExecutor = ExecutionContext.Implicits.global + scheduleMetricsTicker(once = true) + scheduleSpansTicker(once = true) + + val stopSignals = _registeredModules.values.map(stopModule) + val latch = new CountDownLatch(stopSignals.size) + stopSignals.foreach(f => f.onComplete(_ => latch.countDown())) + + // There is a global 30 seconds limit to shutdown after which all executors will shut down. + val stopCompletionFuture = Future(latch.await(30, TimeUnit.SECONDS)) + stopCompletionFuture.onComplete(_ => { + _metricsTickerExecutor.shutdown() + _spansTickerExecutor.shutdown() + }) + + stopCompletionFuture.map(_ => ()) + } + + + /** + * (Re)Schedules the metrics ticker that periodically takes snapshots from the metric registry and sends them to + * all available metric reporting modules. If a ticker was already scheduled then that scheduled job will be + * cancelled and scheduled again. + */ + private def scheduleMetricsTicker(once: Boolean = false): Unit = { + val currentMetricsTicker = _metricsTickerSchedule.get() + if(currentMetricsTicker != null) + currentMetricsTicker.cancel(false) + + _metricsTickerSchedule.set { + val interval = _registrySettings.metricTickInterval.toMillis + val initialDelay = if(_registrySettings.optimisticMetricTickAlignment) { + val now = clock.instant() + val nextTick = Clock.nextTick(now, _registrySettings.metricTickInterval) + Duration.between(now, nextTick).toMillis + } else _registrySettings.metricTickInterval.toMillis + + val ticker = new Runnable { + var lastInstant = Instant.now(clock) + + override def run(): Unit = try { + val currentInstant = Instant.now(clock) + val periodSnapshot = PeriodSnapshot( + from = lastInstant, + to = currentInstant, + metrics = snapshotGenerator.snapshot()) + + metricReporterModules().foreach { entry => + Future { + Try(entry.module.reportPeriodSnapshot(periodSnapshot)).failed.foreach { error => + _logger.error(s"Reporter [${entry.name}] failed to process a metrics tick.", error) + } + }(entry.executionContext) + } + + lastInstant = currentInstant + } catch { + case NonFatal(t) => _logger.error("Failed to run a metrics tick", t) + } + } + + if(once) + _metricsTickerExecutor.schedule(ticker, 0L, TimeUnit.MILLISECONDS) + else + _metricsTickerExecutor.scheduleAtFixedRate(ticker, initialDelay, interval, TimeUnit.MILLISECONDS) + } + } + + /** + * (Re)Schedules the spans ticker that periodically takes the spans accumulated by the tracer and flushes them to + * all available span reporting modules. If a ticker was already scheduled then that scheduled job will be + * cancelled and scheduled again. + */ + private def scheduleSpansTicker(once: Boolean = false): Unit = { + val currentSpansTicker = _spansTickerSchedule.get() + if(currentSpansTicker != null) + currentSpansTicker.cancel(false) + + _spansTickerSchedule.set { + val interval = _registrySettings.traceTickInterval.toMillis + + val ticker = new Runnable { + override def run(): Unit = try { + val spanBatch = spanBuffer.flush() + + spanReporterModules().foreach { entry => + Future { + Try(entry.module.reportSpans(spanBatch)).failed.foreach { error => + _logger.error(s"Reporter [${entry.name}] failed to process a spans tick.", error) + } + }(entry.executionContext) + } + + } catch { + case NonFatal(t) => _logger.error("Failed to run a spans tick", t) + } + } + + if(once) + _spansTickerExecutor.schedule(ticker, 0L, TimeUnit.MILLISECONDS) + else + _spansTickerExecutor.scheduleAtFixedRate(ticker, interval, interval, TimeUnit.MILLISECONDS) + } + } + + private def metricReporterModules(): Iterable[Entry[MetricReporter]] = synchronized { + _metricReporterModules.values + } + + private def spanReporterModules(): Iterable[Entry[SpanReporter]] = synchronized { + _spanReporterModules.values + } + + private def readModuleSettings(config: Config): Seq[Module.Settings] = { + val moduleConfigs = config.getConfig("kamon.modules").configurations + val moduleSettings = moduleConfigs.map { + case (moduleName, moduleConfig) => + val moduleSettings = Try { + Module.Settings( + moduleName, + moduleConfig.getString("description"), + classLoading.resolveClass[Module](moduleConfig.getString("class")).get, + parseModuleKind(moduleConfig.getString("kind")), + moduleConfig.getBoolean("enabled") + ) + }.map(ms => { + val inferredModuleKind = inferModuleKind(ms.clazz) + assert(inferredModuleKind == ms.kind, + s"Module [${ms.name}] is configured as [${ms.kind}] but the actual type does not comply to the expected interface.") + ms + }) + + + moduleSettings.failed.foreach { t => + _logger.warn(s"Failed to read configuration for module [$moduleName]", t) + + if(moduleConfig.hasPath("requires-aspectj") || moduleConfig.hasPath("auto-start") || moduleConfig.hasPath("extension-class")) { + _logger.warn(s"Module [$moduleName] contains legacy configuration settings, please ensure that no legacy configuration") + } + } + + moduleSettings + + } filter(_.isSuccess) map(_.get) toSeq + + + // Load all modules that might have been configured using the legacy "kamon.reporters" setting from <1.2.0 + // versions. This little hack should be removed by the time we release 2.0. + // + if(config.hasPath("kamon.reporters")) { + val legacyModuleSettings = config.getStringList("kamon.reporters").asScala + .map(moduleClass => { + val moduleSettings = Try { + val moduleClazz = classLoading.resolveClass[Module](moduleClass).get + val inferredModuleKind = inferModuleKind(moduleClazz) + val name = moduleClazz.getName() + val description = "Module detected from the legacy kamon.reporters configuration." + + Module.Settings(name, description, moduleClazz, inferredModuleKind, true) + } + + moduleSettings.failed.foreach(t => _logger.error(s"Failed to load legacy reporter module [${moduleClass}]", t)) + moduleSettings + }) + .filter(_.isSuccess) + .map(_.get) + + + val (repeatedLegacyModules, uniqueLegacyModules) = legacyModuleSettings + .partition(lm => moduleSettings.find(_.clazz.getName == lm.clazz.getName).nonEmpty) + + repeatedLegacyModules.foreach(m => + _logger.warn(s"Module [${m.name}] is configured twice, please remove it from the deprecated kamon.reporters setting.")) + + uniqueLegacyModules.foreach(m => + _logger.warn(s"Module [${m.name}] is configured in the deprecated kamon.reporters setting, please consider moving it to kamon.modules.")) + + moduleSettings ++ uniqueLegacyModules + + } else moduleSettings + } + + /** + * Creates a module from the provided settings. + */ + private def createModule(settings: Module.Settings): Option[Entry[Module]] = { + val moduleInstance = classLoading.createInstance[Module](settings.clazz, Nil) + val moduleEntry = moduleInstance.map(instance => createEntry(settings, false, instance)) + + moduleEntry.failed.foreach(t => _logger.warn(s"Failed to create instance of module [${settings.name}]", t)) + moduleEntry.toOption + } + + private def createEntry(settings: Module.Settings, programmaticallyAdded: Boolean, instance: Module): Entry[Module] = { + val executor = Executors.newSingleThreadExecutor(threadFactory(settings.name)) + Entry(settings.name, ExecutionContext.fromExecutorService(executor), programmaticallyAdded, settings, instance) + } + + private def inferModuleKind(clazz: Class[_ <: Module]): Module.Kind = { + if(classOf[CombinedReporter].isAssignableFrom(clazz)) + Module.Kind.Combined + else if(classOf[MetricReporter].isAssignableFrom(clazz)) + Module.Kind.Metric + else if(classOf[SpanReporter].isAssignableFrom(clazz)) + Module.Kind.Span + else + Module.Kind.Plain + } + + + /** + * Returns the current status of this module registry. + */ + private[kamon] def status(): Status.ModuleRegistry = { + val automaticallyAddedModules = readModuleSettings(configuration.config()).map(moduleSettings => { + _registeredModules.get(moduleSettings.name) + .map(moduleEntry => + // The module is on the classpath and started. + Status.Module(moduleEntry.name, moduleEntry.settings.description, moduleEntry.settings.kind, false, true) + + ).getOrElse( + // The module is on the classpath but has not been started. + Status.Module(moduleSettings.name, moduleSettings.description, moduleSettings.kind, false, false) + ) + }) + + val programmaticallyAddedModules = _registeredModules + .filter { case (_, entry) => entry.programmaticallyAdded } + .map { case (name, entry) => Status.Module(name, entry.settings.description, entry.settings.kind, true, true) } + + val allModules = automaticallyAddedModules ++ programmaticallyAddedModules + Status.ModuleRegistry(allModules) + } + + + /** + * Registers a module and schedules execution of its start procedure. + */ + private def startModule(entry: Entry[Module]): Unit = { + registerModule(entry) + + // Schedule the start hook on the module + entry.executionContext.execute(new Runnable { + override def run(): Unit = + Try(entry.module.start()) + .failed.foreach(t => _logger.warn(s"Failure occurred while starting module [${entry.name}]", t)) + }) + } + + private def registerModule(entry: Entry[Module]): Unit = { + _registeredModules = _registeredModules + (entry.name -> entry) + if(entry.module.isInstanceOf[MetricReporter]) + _metricReporterModules = _metricReporterModules + (entry.name -> entry.asInstanceOf[Entry[MetricReporter]]) + if(entry.module.isInstanceOf[SpanReporter]) + _spanReporterModules = _spanReporterModules + (entry.name -> entry.asInstanceOf[Entry[SpanReporter]]) + + } + + /** + * Removes the module from the registry and schedules a call to the stop lifecycle hook on the module's execution + * context. The returned future completes when the module finishes its stop procedure. + */ + private def stopModule(entry: Entry[Module]): Future[Unit] = synchronized { + val cleanupExecutor = ExecutionContext.Implicits.global + + // Remove the module from all registries + _registeredModules = _registeredModules - entry.name + if(entry.module.isInstanceOf[MetricReporter]) + _metricReporterModules = _metricReporterModules - entry.name + if(entry.module.isInstanceOf[SpanReporter]) + _spanReporterModules = _spanReporterModules - entry.name + + + // Schedule a call to stop on the module + val stopPromise = Promise[Unit]() + entry.executionContext.execute(new Runnable { + override def run(): Unit = + stopPromise.complete { + val stopResult = Try(entry.module.stop()) + stopResult.failed.foreach(t => _logger.warn(s"Failure occurred while stopping module [${entry.name}]", t)) + stopResult + } + + }) + + stopPromise.future.onComplete(_ => entry.executionContext.shutdown())(cleanupExecutor) + stopPromise.future + } + + /** + * Schedules a call to reconfigure on the module's execution context. + */ + private def reconfigureModule(entry: Entry[Module], config: Config): Unit = { + entry.executionContext.execute(new Runnable { + override def run(): Unit = + Try(entry.module.reconfigure(config)) + .failed.foreach(t => _logger.warn(s"Failure occurred while reconfiguring module [${entry.name}]", t)) + }) + } + + private def noopRegistration(moduleName: String): Registration = new Registration { + override def cancel(): Unit = + _logger.warn(s"Cannot cancel registration on module [$moduleName] because the module was not added properly") + } + + private def registration(entry: Entry[Module]): Registration = new Registration { + override def cancel(): Unit = stopModule(entry) + } + + private def parseModuleKind(kind: String): Module.Kind = kind.toLowerCase match { + case "combined" => Module.Kind.Combined + case "metric" => Module.Kind.Metric + case "span" => Module.Kind.Span + case "plain" => Module.Kind.Plain + } + + private def readRegistrySettings(config: Config): Settings = + Settings( + metricTickInterval = config.getDuration("kamon.metric.tick-interval"), + optimisticMetricTickAlignment = config.getBoolean("kamon.metric.optimistic-tick-alignment"), + traceTickInterval = config.getDuration("kamon.trace.tick-interval"), + traceReporterQueueSize = config.getInt("kamon.trace.reporter-queue-size") + ) + + private case class Settings( + metricTickInterval: Duration, + optimisticMetricTickAlignment: Boolean, + traceTickInterval: Duration, + traceReporterQueueSize: Int + ) + + + private case class Entry[T <: Module]( + name: String, + executionContext: ExecutionContextExecutorService, + programmaticallyAdded: Boolean, + settings: Module.Settings, + module: T + ) +} + -- cgit v1.2.3 From 77d0fa78a8dba17e710a0caedbd91272218bfcee Mon Sep 17 00:00:00 2001 From: Ivan Topolnjak Date: Wed, 6 Feb 2019 15:45:09 +0100 Subject: better component structure, included a Kamon APM suggestion --- .../main/scala/kamon/module/ModuleRegistry.scala | 9 +- .../main/scala/kamon/status/JsonMarshalling.scala | 1 + .../src/main/scala/kamon/status/Status.scala | 1 + kamon-status/package-lock.json | 6 ++ kamon-status/package.json | 1 + kamon-status/src/api/StatusApi.ts | 1 + kamon-status/src/components/Card.vue | 4 +- kamon-status/src/components/EnvironmentCard.vue | 66 +++++++++++++ kamon-status/src/components/MetricList.vue | 2 +- kamon-status/src/components/ModuleCard.vue | 91 ++++++++++++++++++ kamon-status/src/components/ModuleList.vue | 90 ++++++++++++++++++ kamon-status/src/components/ModuleStatus.vue | 76 --------------- kamon-status/src/components/StatusCard.vue | 71 ++++++++++++++ kamon-status/src/main.ts | 1 + kamon-status/src/styles/main.scss | 5 + kamon-status/src/views/Overview.vue | 102 +++++---------------- 16 files changed, 366 insertions(+), 161 deletions(-) create mode 100644 kamon-status/src/components/EnvironmentCard.vue create mode 100644 kamon-status/src/components/ModuleCard.vue create mode 100644 kamon-status/src/components/ModuleList.vue delete mode 100644 kamon-status/src/components/ModuleStatus.vue create mode 100644 kamon-status/src/components/StatusCard.vue (limited to 'kamon-core/src/main/scala/kamon/module/ModuleRegistry.scala') diff --git a/kamon-core/src/main/scala/kamon/module/ModuleRegistry.scala b/kamon-core/src/main/scala/kamon/module/ModuleRegistry.scala index 5520c602..18921b03 100644 --- a/kamon-core/src/main/scala/kamon/module/ModuleRegistry.scala +++ b/kamon-core/src/main/scala/kamon/module/ModuleRegistry.scala @@ -338,17 +338,20 @@ class ModuleRegistry(classLoading: ClassLoading, configuration: Configuration, c _registeredModules.get(moduleSettings.name) .map(moduleEntry => // The module is on the classpath and started. - Status.Module(moduleEntry.name, moduleEntry.settings.description, moduleEntry.settings.kind, false, true) + Status.Module(moduleEntry.name, moduleEntry.settings.description, moduleEntry.settings.clazz.getCanonicalName, + moduleEntry.settings.kind, false, true) ).getOrElse( // The module is on the classpath but has not been started. - Status.Module(moduleSettings.name, moduleSettings.description, moduleSettings.kind, false, false) + Status.Module(moduleSettings.name, moduleSettings.description, moduleSettings.clazz.getCanonicalName, + moduleSettings.kind, false, false) ) }) val programmaticallyAddedModules = _registeredModules .filter { case (_, entry) => entry.programmaticallyAdded } - .map { case (name, entry) => Status.Module(name, entry.settings.description, entry.settings.kind, true, true) } + .map { case (name, entry) => Status.Module(name, entry.settings.description, entry.settings.clazz.getCanonicalName, + entry.settings.kind, true, true) } val allModules = automaticallyAddedModules ++ programmaticallyAddedModules Status.ModuleRegistry(allModules) diff --git a/kamon-core/src/main/scala/kamon/status/JsonMarshalling.scala b/kamon-core/src/main/scala/kamon/status/JsonMarshalling.scala index 370ab467..a84d016a 100644 --- a/kamon-core/src/main/scala/kamon/status/JsonMarshalling.scala +++ b/kamon-core/src/main/scala/kamon/status/JsonMarshalling.scala @@ -37,6 +37,7 @@ object JsonMarshalling { array.`object`() .value("name", m.name) .value("description", m.description) + .value("clazz", m.clazz) .value("kind", moduleKindString(m.kind)) .value("isProgrammaticallyRegistered", m.isProgrammaticallyRegistered) .value("isStarted", m.isStarted) diff --git a/kamon-core/src/main/scala/kamon/status/Status.scala b/kamon-core/src/main/scala/kamon/status/Status.scala index 2a52b95f..1ba5defd 100644 --- a/kamon-core/src/main/scala/kamon/status/Status.scala +++ b/kamon-core/src/main/scala/kamon/status/Status.scala @@ -70,6 +70,7 @@ object Status { case class Module( name: String, description: String, + clazz: String, kind: ModuleKind, isProgrammaticallyRegistered: Boolean, isStarted: Boolean diff --git a/kamon-status/package-lock.json b/kamon-status/package-lock.json index fdcb7690..d49faf54 100644 --- a/kamon-status/package-lock.json +++ b/kamon-status/package-lock.json @@ -4,6 +4,12 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@fortawesome/fontawesome-free": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-5.7.1.tgz", + "integrity": "sha512-gukWJ7Mwf0WXQbkcwcm5zi8+H8aT5MMnphf5hpydOw898H1ibgm2cyejHgk6Km/FTvrPp5ppUHLrlFwt0QxsQw==", + "dev": true + }, "@intervolga/optimize-cssnano-plugin": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@intervolga/optimize-cssnano-plugin/-/optimize-cssnano-plugin-1.0.6.tgz", diff --git a/kamon-status/package.json b/kamon-status/package.json index ae317311..0d622f35 100644 --- a/kamon-status/package.json +++ b/kamon-status/package.json @@ -17,6 +17,7 @@ "vue-router": "^3.0.1" }, "devDependencies": { + "@fortawesome/fontawesome-free": "^5.7.1", "@vue/cli-plugin-typescript": "^3.3.0", "@vue/cli-service": "^3.3.0", "node-sass": "^4.9.0", diff --git a/kamon-status/src/api/StatusApi.ts b/kamon-status/src/api/StatusApi.ts index 25d525d2..3add6161 100644 --- a/kamon-status/src/api/StatusApi.ts +++ b/kamon-status/src/api/StatusApi.ts @@ -24,6 +24,7 @@ export enum ModuleKind { export interface Module { name: string description: string + clazz: string kind: ModuleKind isProgrammaticallyRegistered: boolean isStarted: boolean diff --git a/kamon-status/src/components/Card.vue b/kamon-status/src/components/Card.vue index 745a1caf..d77596da 100644 --- a/kamon-status/src/components/Card.vue +++ b/kamon-status/src/components/Card.vue @@ -1,5 +1,5 @@ @@ -13,7 +13,7 @@ } hr { - margin: 1px; + margin: 0px; border-color: #f3f3f3; } diff --git a/kamon-status/src/components/EnvironmentCard.vue b/kamon-status/src/components/EnvironmentCard.vue new file mode 100644 index 00000000..97e79d28 --- /dev/null +++ b/kamon-status/src/components/EnvironmentCard.vue @@ -0,0 +1,66 @@ + + + \ No newline at end of file diff --git a/kamon-status/src/components/MetricList.vue b/kamon-status/src/components/MetricList.vue index b9b6a92a..60f7499a 100644 --- a/kamon-status/src/components/MetricList.vue +++ b/kamon-status/src/components/MetricList.vue @@ -10,7 +10,7 @@
-
+
{{ metric.type }}
{{ metric.name }}
diff --git a/kamon-status/src/components/ModuleCard.vue b/kamon-status/src/components/ModuleCard.vue new file mode 100644 index 00000000..ea3a0c68 --- /dev/null +++ b/kamon-status/src/components/ModuleCard.vue @@ -0,0 +1,91 @@ + + + + + diff --git a/kamon-status/src/components/ModuleList.vue b/kamon-status/src/components/ModuleList.vue new file mode 100644 index 00000000..746ccf16 --- /dev/null +++ b/kamon-status/src/components/ModuleList.vue @@ -0,0 +1,90 @@ + + + + + diff --git a/kamon-status/src/components/ModuleStatus.vue b/kamon-status/src/components/ModuleStatus.vue deleted file mode 100644 index fff3373e..00000000 --- a/kamon-status/src/components/ModuleStatus.vue +++ /dev/null @@ -1,76 +0,0 @@ - - - - - diff --git a/kamon-status/src/components/StatusCard.vue b/kamon-status/src/components/StatusCard.vue new file mode 100644 index 00000000..193744df --- /dev/null +++ b/kamon-status/src/components/StatusCard.vue @@ -0,0 +1,71 @@ + + + + diff --git a/kamon-status/src/main.ts b/kamon-status/src/main.ts index 02a4b217..a0a1ac2c 100644 --- a/kamon-status/src/main.ts +++ b/kamon-status/src/main.ts @@ -2,6 +2,7 @@ import Vue from 'vue' import App from './App.vue' import router from './router' import 'bootstrap/dist/css/bootstrap.min.css' +import '@fortawesome/fontawesome-free/css/all.min.css' import './styles/main.scss' Vue.config.productionTip = false diff --git a/kamon-status/src/styles/main.scss b/kamon-status/src/styles/main.scss index 271da8af..54e77dfb 100644 --- a/kamon-status/src/styles/main.scss +++ b/kamon-status/src/styles/main.scss @@ -32,3 +32,8 @@ h1, h2, h3 { overflow-wrap: anywhere; color: #676767; } + +a, a:hover, a:active { + color: inherit; + text-decoration: none; +} \ No newline at end of file diff --git a/kamon-status/src/views/Overview.vue b/kamon-status/src/views/Overview.vue index 75b39eb0..830f5110 100644 --- a/kamon-status/src/views/Overview.vue +++ b/kamon-status/src/views/Overview.vue @@ -5,69 +5,18 @@

Status

- -
-
-
Instrumentation
-
{{instrumentationStatusMessage}}
-
-
-
Reporters
-
{{ activeReporters.length }} Started
-
-
-
Metrics
-
{{metricsStatusMessage}}
-
-
-
+

Environment

- -
-
-
Service
-
{{ service }}
-
-
-
Host
-
{{ host }}
-
-
-
instance
-
{{instance}}
-
-
-
tags
-
- - {{ tag }}={{ environmentTags[tag] }} - -
-
-
None
-
-
-
-
+
-
-

Reporters

-
-
- -
- -
-

Modules

-
-
- +
+
@@ -83,22 +32,25 @@ + + diff --git a/kamon-status/src/components/InstrumentationModuleStatusCard.vue b/kamon-status/src/components/InstrumentationModuleStatusCard.vue new file mode 100644 index 00000000..6fb1206f --- /dev/null +++ b/kamon-status/src/components/InstrumentationModuleStatusCard.vue @@ -0,0 +1,40 @@ + + + + + diff --git a/kamon-status/src/components/ModuleCard.vue b/kamon-status/src/components/ModuleCard.vue deleted file mode 100644 index ea3a0c68..00000000 --- a/kamon-status/src/components/ModuleCard.vue +++ /dev/null @@ -1,91 +0,0 @@ - - - - - diff --git a/kamon-status/src/components/ModuleList.vue b/kamon-status/src/components/ModuleList.vue index 746ccf16..eddc05d0 100644 --- a/kamon-status/src/components/ModuleList.vue +++ b/kamon-status/src/components/ModuleList.vue @@ -4,20 +4,19 @@

Reporters

- +
-

Modules

- +
@@ -25,12 +24,12 @@ - - + \ No newline at end of file diff --git a/kamon-status/src/components/ModuleStatusCard.vue b/kamon-status/src/components/ModuleStatusCard.vue new file mode 100644 index 00000000..825fce40 --- /dev/null +++ b/kamon-status/src/components/ModuleStatusCard.vue @@ -0,0 +1,52 @@ + + + + + diff --git a/kamon-status/src/components/OverviewCard.vue b/kamon-status/src/components/OverviewCard.vue new file mode 100644 index 00000000..a1805596 --- /dev/null +++ b/kamon-status/src/components/OverviewCard.vue @@ -0,0 +1,71 @@ + + + + diff --git a/kamon-status/src/components/StatusCard.vue b/kamon-status/src/components/StatusCard.vue index 193744df..7293940e 100644 --- a/kamon-status/src/components/StatusCard.vue +++ b/kamon-status/src/components/StatusCard.vue @@ -1,71 +1,84 @@ - + + diff --git a/kamon-status/src/views/Overview.vue b/kamon-status/src/views/Overview.vue index 830f5110..ae79f0c6 100644 --- a/kamon-status/src/views/Overview.vue +++ b/kamon-status/src/views/Overview.vue @@ -2,10 +2,10 @@
-

Status

+

Overview

- +
@@ -22,9 +22,13 @@

Metrics

-
+
+
+ +
+
@@ -33,16 +37,18 @@ import { Component, Vue } from 'vue-property-decorator' import {Option, none, some} from 'ts-option' import ModuleList from '../components/ModuleList.vue' +import InstrumentationModuleList from '../components/InstrumentationModuleList.vue' import MetricList from '../components/MetricList.vue' import EnvironmentCard from '../components/EnvironmentCard.vue' -import StatusCard from '../components/StatusCard.vue' +import OverviewCard from '../components/OverviewCard.vue' import {StatusApi, Settings, ModuleRegistry, ModuleKind, MetricRegistry, Module, Metric, - Instrumentation, Environment} from '../api/StatusApi' + Instrumentation, Environment, InstrumentationModule} from '../api/StatusApi' @Component({ components: { - 'status-card': StatusCard, + 'overview-card': OverviewCard, 'module-list': ModuleList, + 'instrumentation-module-list': InstrumentationModuleList, 'metric-list': MetricList, 'environment-card': EnvironmentCard }, @@ -93,6 +99,12 @@ export default class Overview extends Vue { .getOrElse([]) } + get instrumentationModules(): InstrumentationModule[] { + return this.instrumentation + .map(i => i.modules) + .getOrElse([]) + } + get environment(): Option { return this.settings.map(s => s.environment) } @@ -113,7 +125,7 @@ export default class Overview extends Vue { } private isStarted(module: Module): boolean { - return module.isStarted + return module.started } } -- cgit v1.2.3 From 7152517f2586a5b40726365a756087ddddc099ca Mon Sep 17 00:00:00 2001 From: Ivan Topolnjak Date: Mon, 11 Feb 2019 23:16:54 +0100 Subject: self-review changes and use a thread pool for the embedded status page server --- .../src/test/scala/kamon/KamonLifecycleSpec.scala | 2 +- kamon-core/src/main/resources/reference.conf | 8 +- kamon-core/src/main/scala/kamon/Kamon.scala | 66 +------- kamon-core/src/main/scala/kamon/StatusPage.scala | 22 ++- .../main/scala/kamon/module/ModuleRegistry.scala | 38 +++-- .../main/scala/kamon/status/JsonMarshalling.scala | 8 +- .../src/main/scala/kamon/status/Status.scala | 15 +- .../main/scala/kamon/status/StatusPageServer.scala | 83 +++++++--- kamon-status/src/api/StatusApi.ts | 6 +- kamon-status/src/assets/logo.svg | 171 ++++----------------- kamon-status/src/components/ModuleList.vue | 2 +- kamon-status/src/components/ModuleStatusCard.vue | 2 +- kamon-status/src/components/OverviewCard.vue | 2 +- kamon-status/src/views/Overview.vue | 4 +- kamon-status/vue.config.js | 2 +- 15 files changed, 161 insertions(+), 270 deletions(-) (limited to 'kamon-core/src/main/scala/kamon/module/ModuleRegistry.scala') diff --git a/kamon-core-tests/src/test/scala/kamon/KamonLifecycleSpec.scala b/kamon-core-tests/src/test/scala/kamon/KamonLifecycleSpec.scala index 9ee07694..c0f5b50e 100644 --- a/kamon-core-tests/src/test/scala/kamon/KamonLifecycleSpec.scala +++ b/kamon-core-tests/src/test/scala/kamon/KamonLifecycleSpec.scala @@ -60,7 +60,7 @@ object KamonWithRunningReporter extends App { object KamonWithTemporaryReporter extends App { Kamon.registerModule("dummy metric reporter", new DummyMetricReporter()) - Kamon.registerModule("dummy span repoter", new DummySpanReporter()) + Kamon.registerModule("dummy span reporter", new DummySpanReporter()) Thread.sleep(5000) Kamon.stopAllReporters() diff --git a/kamon-core/src/main/resources/reference.conf b/kamon-core/src/main/resources/reference.conf index 37491c79..7a5576bc 100644 --- a/kamon-core/src/main/resources/reference.conf +++ b/kamon-core/src/main/resources/reference.conf @@ -27,14 +27,14 @@ kamon { status { - # When enabled, Kamon will start an embedded web server to publish the status mini-site that contains basic - # status and debugging information. + # When enabled Kamon will start an embedded web server to publish the status page mini-site, which contains basic + # system information that can be used for debugging and troubleshooting issues with Kamon. enabled = true - # Controls the hostname and port in which the status mini-site HTTP server will be listening. + # Controls the hostname and port on which the status page embedded server will be listening. listen { hostname = "0.0.0.0" - port = 9912 + port = 5266 } } diff --git a/kamon-core/src/main/scala/kamon/Kamon.scala b/kamon-core/src/main/scala/kamon/Kamon.scala index 284c7553..cfeea19e 100644 --- a/kamon-core/src/main/scala/kamon/Kamon.scala +++ b/kamon-core/src/main/scala/kamon/Kamon.scala @@ -15,10 +15,6 @@ package kamon -import com.typesafe.config.{Config, ConfigFactory, ConfigRenderOptions} -import kamon.metric.PeriodSnapshot -import kamon.trace.Span - object Kamon extends ClassLoading with Configuration with Utilities @@ -36,64 +32,6 @@ object Kamon extends ClassLoading _environment onReconfigure(newConfig => { - _environment = Environment.fromConfig(config) - }) -} - - -object QuickTest extends App { - val manualConfig = - """ - |kamon.modules { - | kamon-zipkin { - | enabled = false - | description = "Module that sends data to particular places" - | kind = metric - | class = kamon.MyCustomMetricDude - | } - |} - | - |kamon.environment.tags { - | one = test - |} - """.stripMargin - - val newConfig = ConfigFactory.parseString(manualConfig).withFallback(Kamon.config()) - Kamon.reconfigure(newConfig) - - - - Kamon.loadModules() - Kamon.registerModule("my-module", new kamon.module.MetricReporter { - override def reportPeriodSnapshot(snapshot: PeriodSnapshot): Unit = {} - override def start(): Unit = {} - override def stop(): Unit = {} - override def reconfigure(newConfig: Config): Unit = {} - }) - - Kamon.registerModule("my-module-for-spans", new kamon.module.SpanReporter { - override def reportSpans(spans: Seq[Span.FinishedSpan]): Unit = {} - override def start(): Unit = {} - override def stop(): Unit = {} - override def reconfigure(newConfig: Config): Unit = {} + _environment = Environment.fromConfig(newConfig) }) - - - Kamon.histogram("test").refine("actor_class" -> "com.kamon.something.MyActor", "system" -> "HRMS").record(10) - Kamon.rangeSampler("test-rs").refine("actor_class" -> "com.kamon.something.MyActor", "system" -> "HRMS").increment(34) - Kamon.counter("test-counter").refine("tagcito" -> "value").increment(42) - - //println("JSON CONFIG: " + Kamon.config().root().render(ConfigRenderOptions.concise().setFormatted(true).setJson(true))) - - - Thread.sleep(100000000) - - -} - -class MyCustomMetricDude extends kamon.module.MetricReporter { - override def reportPeriodSnapshot(snapshot: PeriodSnapshot): Unit = {} - override def start(): Unit = {} - override def stop(): Unit = {} - override def reconfigure(newConfig: Config): Unit = {} -} +} \ No newline at end of file diff --git a/kamon-core/src/main/scala/kamon/StatusPage.scala b/kamon-core/src/main/scala/kamon/StatusPage.scala index e5c8ef52..1ac60c1b 100644 --- a/kamon-core/src/main/scala/kamon/StatusPage.scala +++ b/kamon-core/src/main/scala/kamon/StatusPage.scala @@ -1,9 +1,13 @@ package kamon import com.typesafe.config.Config -import kamon.status.{StatusPageServer, Status} +import kamon.status.{Status, StatusPageServer} +import org.slf4j.LoggerFactory + +import scala.util.{Failure, Success, Try} trait StatusPage { self: Configuration with ClassLoading with ModuleLoading with Metrics with Configuration => + private val _log = LoggerFactory.getLogger(classOf[StatusPage]) @volatile private var _statusPageServer: Option[StatusPageServer] = None private val _status = new Status(self._moduleRegistry, self._metricsRegistry, self) @@ -47,10 +51,20 @@ trait StatusPage { self: Configuration with ClassLoading with ModuleLoading with } private def startServer(hostname: String, port: Int, resourceLoader: ClassLoader): Unit = { - val server = new StatusPageServer(hostname, port, resourceLoader, _status) - server.start() + Try { + + val server = new StatusPageServer(hostname, port, resourceLoader, _status) + server.start() + server - _statusPageServer = Some(server) + } match { + case Success(server) => + _log.info(s"Status page started on http://$hostname:$port/") + _statusPageServer = Some(server) + + case Failure(t) => + _log.error("Failed to start the status page embedded server", t) + } } private def stopServer(): Unit = { diff --git a/kamon-core/src/main/scala/kamon/module/ModuleRegistry.scala b/kamon-core/src/main/scala/kamon/module/ModuleRegistry.scala index 81b94f29..d45cd80f 100644 --- a/kamon-core/src/main/scala/kamon/module/ModuleRegistry.scala +++ b/kamon-core/src/main/scala/kamon/module/ModuleRegistry.scala @@ -76,7 +76,7 @@ class ModuleRegistry(classLoading: ClassLoading, configuration: Configuration, c * configured modules state. */ def load(config: Config): Unit = synchronized { - val configuredModules = readModuleSettings(config) + val configuredModules = readModuleSettings(config, true) val automaticallyRegisteredModules = _registeredModules.filterNot { case (_, module) => module.programmaticallyAdded } // Start, reconfigure and stop modules that are still present but disabled. @@ -233,7 +233,7 @@ class ModuleRegistry(classLoading: ClassLoading, configuration: Configuration, c _spanReporterModules.values } - private def readModuleSettings(config: Config): Seq[Module.Settings] = { + private def readModuleSettings(config: Config, emitConfigurationWarnings: Boolean): Seq[Module.Settings] = { val moduleConfigs = config.getConfig("kamon.modules").configurations val moduleSettings = moduleConfigs.map { case (moduleName, moduleConfig) => @@ -253,11 +253,18 @@ class ModuleRegistry(classLoading: ClassLoading, configuration: Configuration, c }) - moduleSettings.failed.foreach { t => - _logger.warn(s"Failed to read configuration for module [$moduleName]", t) + if(emitConfigurationWarnings) { + moduleSettings.failed.foreach { t => + _logger.warn(s"Failed to read configuration for module [$moduleName]", t) - if(moduleConfig.hasPath("requires-aspectj") || moduleConfig.hasPath("auto-start") || moduleConfig.hasPath("extension-class")) { - _logger.warn(s"Module [$moduleName] contains legacy configuration settings, please ensure that no legacy configuration") + val hasLegacySettings = + moduleConfig.hasPath("requires-aspectj") || + moduleConfig.hasPath("auto-start") || + moduleConfig.hasPath("extension-class") + + if (hasLegacySettings) { + _logger.warn(s"Module [$moduleName] contains legacy configuration settings, please ensure that no legacy configuration") + } } } @@ -281,7 +288,10 @@ class ModuleRegistry(classLoading: ClassLoading, configuration: Configuration, c Module.Settings(name, description, moduleClazz, inferredModuleKind, true) } - moduleSettings.failed.foreach(t => _logger.error(s"Failed to load legacy reporter module [${moduleClass}]", t)) + if(emitConfigurationWarnings) { + moduleSettings.failed.foreach(t => _logger.error(s"Failed to load legacy reporter module [${moduleClass}]", t)) + } + moduleSettings }) .filter(_.isSuccess) @@ -291,11 +301,13 @@ class ModuleRegistry(classLoading: ClassLoading, configuration: Configuration, c val (repeatedLegacyModules, uniqueLegacyModules) = legacyModuleSettings .partition(lm => moduleSettings.find(_.clazz.getName == lm.clazz.getName).nonEmpty) - repeatedLegacyModules.foreach(m => - _logger.warn(s"Module [${m.name}] is configured twice, please remove it from the deprecated kamon.reporters setting.")) + if(emitConfigurationWarnings) { + repeatedLegacyModules.foreach(m => + _logger.warn(s"Module [${m.name}] is configured twice, please remove it from the deprecated kamon.reporters setting.")) - uniqueLegacyModules.foreach(m => - _logger.warn(s"Module [${m.name}] is configured in the deprecated kamon.reporters setting, please consider moving it to kamon.modules.")) + uniqueLegacyModules.foreach(m => + _logger.warn(s"Module [${m.name}] is configured in the deprecated kamon.reporters setting, please consider moving it to kamon.modules.")) + } moduleSettings ++ uniqueLegacyModules @@ -334,7 +346,7 @@ class ModuleRegistry(classLoading: ClassLoading, configuration: Configuration, c * Returns the current status of this module registry. */ private[kamon] def status(): Status.ModuleRegistry = { - val automaticallyAddedModules = readModuleSettings(configuration.config()).map(moduleSettings => { + val automaticallyAddedModules = readModuleSettings(configuration.config(), false).map(moduleSettings => { val isActive = _registeredModules.get(moduleSettings.name).nonEmpty Status.Module( @@ -342,7 +354,7 @@ class ModuleRegistry(classLoading: ClassLoading, configuration: Configuration, c moduleSettings.description, moduleSettings.clazz.getCanonicalName, moduleSettings.kind, - isProgrammaticallyRegistered = false, + programmaticallyRegistered = false, moduleSettings.enabled, isActive) }) diff --git a/kamon-core/src/main/scala/kamon/status/JsonMarshalling.scala b/kamon-core/src/main/scala/kamon/status/JsonMarshalling.scala index 2291648c..5ab0eb9f 100644 --- a/kamon-core/src/main/scala/kamon/status/JsonMarshalling.scala +++ b/kamon-core/src/main/scala/kamon/status/JsonMarshalling.scala @@ -39,9 +39,9 @@ object JsonMarshalling { .value("description", m.description) .value("clazz", m.clazz) .value("kind", moduleKindString(m.kind)) - .value("isProgrammaticallyRegistered", m.isProgrammaticallyRegistered) - .value("enabled", m.isEnabled) - .value("started", m.isStarted) + .value("programmaticallyRegistered", m.programmaticallyRegistered) + .value("enabled", m.enabled) + .value("started", m.started) .end() }) @@ -107,7 +107,7 @@ object JsonMarshalling { override def toJson(instance: Status.Instrumentation, builder: JavaStringBuilder): Unit = { val instrumentationObject = JsonWriter.on(builder) .`object`() - .value("isActive", instance.isIActive) + .value("active", instance.active) .`object`("modules") instance.modules.asScala.foreach { diff --git a/kamon-core/src/main/scala/kamon/status/Status.scala b/kamon-core/src/main/scala/kamon/status/Status.scala index 956e3594..ef5bb8eb 100644 --- a/kamon-core/src/main/scala/kamon/status/Status.scala +++ b/kamon-core/src/main/scala/kamon/status/Status.scala @@ -9,7 +9,8 @@ import kamon.module.Module.{Kind => ModuleKind} import java.util.{Collections, List => JavaList, Map => JavaMap} /** - * Exposes Kamon components' status information. This is meant to be used for informational and debugging purposes. + * Exposes Kamon components' status information. This is meant to be used for informational and debugging purposes and + * by no means should replace the use of reporters to extract information from Kamon. */ class Status(_moduleRegistry: ModuleRegistry, _metricRegistry: MetricRegistry, configuration: Configuration) { @@ -20,8 +21,8 @@ class Status(_moduleRegistry: ModuleRegistry, _metricRegistry: MetricRegistry, c Status.Settings(BuildInfo.version, Kamon.environment, configuration.config()) /** - * Status of the module registry. Describes what modules have been detected in the classpath and their current - * statuses. + * Status of the module registry. Describes what modules have been detected and registered, either from the classpath + * or programatically and their current status. */ def moduleRegistry(): Status.ModuleRegistry = _moduleRegistry.status() @@ -72,9 +73,9 @@ object Status { description: String, clazz: String, kind: ModuleKind, - isProgrammaticallyRegistered: Boolean, - isEnabled: Boolean, - isStarted: Boolean + programmaticallyRegistered: Boolean, + enabled: Boolean, + started: Boolean ) case class MetricRegistry( @@ -94,7 +95,7 @@ object Status { * outside Kamon. */ private[kamon] case class Instrumentation( - isIActive: Boolean, + active: Boolean, modules: JavaMap[String, String], errors: JavaMap[String, JavaList[Throwable]] ) diff --git a/kamon-core/src/main/scala/kamon/status/StatusPageServer.scala b/kamon-core/src/main/scala/kamon/status/StatusPageServer.scala index 2784b87a..b2c7ff74 100644 --- a/kamon-core/src/main/scala/kamon/status/StatusPageServer.scala +++ b/kamon-core/src/main/scala/kamon/status/StatusPageServer.scala @@ -1,15 +1,19 @@ package kamon.status +import java.io.InputStream + import fi.iki.elonen.NanoHTTPD -import fi.iki.elonen.NanoHTTPD.Response import fi.iki.elonen.NanoHTTPD.Response.{Status => StatusCode} +import java.util.Collections +import java.util.concurrent.{ExecutorService, Executors} + +import scala.collection.JavaConverters.asScalaBufferConverter class StatusPageServer(hostname: String, port: Int, resourceLoader: ClassLoader, status: Status) extends NanoHTTPD(hostname, port) { private val RootResourceDirectory = "status" - private val ResourceExtensionRegex = ".*\\.([a-zA-Z]*)".r - + private val ResourceExtensionRegex = ".*\\.([a-zA-Z0-9]*)".r override def serve(session: NanoHTTPD.IHTTPSession): NanoHTTPD.Response = { if(session.getMethod() == NanoHTTPD.Method.GET) { @@ -25,44 +29,85 @@ class StatusPageServer(hostname: String, port: Int, resourceLoader: ClassLoader, } } else { + // Serve resources from the status page folder. - val resource = if (session.getUri() == "/") "/index.html" else session.getUri() - val resourcePath = RootResourceDirectory + resource + val requestedResource = if (session.getUri() == "/") "/index.html" else session.getUri() + val resourcePath = RootResourceDirectory + requestedResource val resourceStream = resourceLoader.getResourceAsStream(resourcePath) - if (resourceStream == null) NotFound else { - NanoHTTPD.newChunkedResponse(StatusCode.OK, mimeType(resource), resourceStream) - } + if (resourceStream == null) NotFound else resource(requestedResource, resourceStream) } } else NotAllowed } + override def start(): Unit = { + setAsyncRunner(new ThreadPoolRunner(Executors.newFixedThreadPool(2))) + start(NanoHTTPD.SOCKET_READ_TIMEOUT, false) + } + private def mimeType(resource: String): String = { val ResourceExtensionRegex(resourceExtension) = resource resourceExtension match { - case "css" => "text/css" - case "js" => "application/javascript" - case "ico" => "image/x-icon" - case "svg" => "image/svg+xml" - case "html" => "text/html" - case _ => "text/plain" + case "css" => "text/css" + case "js" => "application/javascript" + case "ico" => "image/x-icon" + case "svg" => "image/svg+xml" + case "html" => "text/html" + case "woff2" => "font/woff2" + case _ => "text/plain" } } - private def json[T : JsonMarshalling](instance: T): Response = { + private def json[T](instance: T)(implicit marshalling: JsonMarshalling[T]) = { val builder = new java.lang.StringBuilder() - implicitly[JsonMarshalling[T]].toJson(instance, builder) - NanoHTTPD.newFixedLengthResponse(StatusCode.OK, "application/json", builder.toString()) + marshalling.toJson(instance, builder) + + val response = NanoHTTPD.newFixedLengthResponse(StatusCode.OK, "application/json", builder.toString()) + response.closeConnection(true) + response + } + + private def resource(name: String, stream: InputStream) = { + val response = NanoHTTPD.newChunkedResponse(StatusCode.OK, mimeType(name), stream) + response.closeConnection(true) + response } private val NotAllowed = NanoHTTPD.newFixedLengthResponse( StatusCode.METHOD_NOT_ALLOWED, NanoHTTPD.MIME_PLAINTEXT, - "Only GET requests are allowed.") + "Only GET requests are allowed." + ) private val NotFound = NanoHTTPD.newFixedLengthResponse( StatusCode.NOT_FOUND, NanoHTTPD.MIME_PLAINTEXT, - "The requested resource was not found.") + "The requested resource was not found." + ) + + // Closing the connections will ensure that the thread pool will not be exhausted by keep alive + // connections from the browsers. + NotAllowed.closeConnection(true) + NotFound.closeConnection(true) + + + /** + * AsyncRunner that uses a thread pool for handling requests rather than spawning a new thread for each request (as + * the default runner does). + */ + private class ThreadPoolRunner(executorService: ExecutorService) extends NanoHTTPD.AsyncRunner { + final private val _openRequests = Collections.synchronizedList(new java.util.LinkedList[NanoHTTPD#ClientHandler]()) + + override def closeAll(): Unit = + _openRequests.asScala.foreach(_.close()) + + override def closed(clientHandler: NanoHTTPD#ClientHandler): Unit = + _openRequests.remove(clientHandler) + + override def exec(clientHandler: NanoHTTPD#ClientHandler): Unit = { + executorService.submit(clientHandler) + _openRequests.add(clientHandler) + } + } } \ No newline at end of file diff --git a/kamon-status/src/api/StatusApi.ts b/kamon-status/src/api/StatusApi.ts index 231c5eca..ee596b8d 100644 --- a/kamon-status/src/api/StatusApi.ts +++ b/kamon-status/src/api/StatusApi.ts @@ -26,7 +26,7 @@ export interface Module { description: string clazz: string kind: ModuleKind - isProgrammaticallyRegistered: boolean + programmaticallyRegistered: boolean enabled: boolean started: boolean } @@ -56,7 +56,7 @@ export interface InstrumentationModule { } export interface Instrumentation { - isActive: boolean + active: boolean modules: InstrumentationModule[] errors: { [key: string]: string[]} } @@ -112,7 +112,7 @@ export class StatusApi { public static instrumentationStatus(): Promise { return axios.get('/status/instrumentation').then(response => { const instrumentation: Instrumentation = { - isActive: response.data.isActive as boolean, + active: response.data.active as boolean, modules: [], errors: {} } diff --git a/kamon-status/src/assets/logo.svg b/kamon-status/src/assets/logo.svg index 96b29425..d351c48a 100644 --- a/kamon-status/src/assets/logo.svg +++ b/kamon-status/src/assets/logo.svg @@ -1,145 +1,26 @@ - - - -image/svg+xml - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/kamon-status/src/components/ModuleList.vue b/kamon-status/src/components/ModuleList.vue index 6cef9e2d..ded523d7 100644 --- a/kamon-status/src/components/ModuleList.vue +++ b/kamon-status/src/components/ModuleList.vue @@ -42,7 +42,7 @@ export default class ModuleList extends Vue { name: 'Kamon APM', description: 'See your metrics and trace data for free with a Starter account.', kind: ModuleKind.Combined, - isProgrammaticallyRegistered: false, + programmaticallyRegistered: false, enabled: false, started: false, clazz: '' diff --git a/kamon-status/src/components/ModuleStatusCard.vue b/kamon-status/src/components/ModuleStatusCard.vue index d095d7b3..18e2b038 100644 --- a/kamon-status/src/components/ModuleStatusCard.vue +++ b/kamon-status/src/components/ModuleStatusCard.vue @@ -30,7 +30,7 @@ export default class ModuleStatusCard extends Vue { @Prop() private module!: Module get discoveryStatus(): string { - return this.module.isProgrammaticallyRegistered ? 'manual' : 'automatic' + return this.module.programmaticallyRegistered ? 'manual' : 'automatic' } get runStatus(): { message: string, color: string, icon: string } { diff --git a/kamon-status/src/components/OverviewCard.vue b/kamon-status/src/components/OverviewCard.vue index 8939d93d..6746d761 100644 --- a/kamon-status/src/components/OverviewCard.vue +++ b/kamon-status/src/components/OverviewCard.vue @@ -53,7 +53,7 @@ export default class OverviewCard extends Vue { } get instrumentationStatusMessage(): string { - return this.instrumentation.map(i => (i.isActive ? 'Active' : 'Disabled') as string).getOrElse('Unknown') + return this.instrumentation.map(i => (i.active ? 'Active' : 'Disabled') as string).getOrElse('Unknown') } get metricsStatusMessage(): string { diff --git a/kamon-status/src/views/Overview.vue b/kamon-status/src/views/Overview.vue index 7d200e81..424987c1 100644 --- a/kamon-status/src/views/Overview.vue +++ b/kamon-status/src/views/Overview.vue @@ -16,7 +16,7 @@

Metrics

-
+
@@ -74,7 +74,7 @@ export default class Overview extends Vue { } get instrumentationStatusMessage(): string { - return this.instrumentation.map(i => (i.isActive ? 'Active' : 'Disabled') as string).getOrElse('Unknown') + return this.instrumentation.map(i => (i.active ? 'Active' : 'Disabled') as string).getOrElse('Unknown') } get metricsStatusMessage(): string { diff --git a/kamon-status/vue.config.js b/kamon-status/vue.config.js index 9459cace..76a1773c 100644 --- a/kamon-status/vue.config.js +++ b/kamon-status/vue.config.js @@ -1,6 +1,6 @@ module.exports = { productionSourceMap: false, devServer: { - proxy: 'http://localhost:9912' + proxy: 'http://localhost:5266' } } \ No newline at end of file -- cgit v1.2.3