[io.metrics] initial contribution (#9890)

Signed-off-by: Robert Bach <openhab@mortalsilence.net>
This commit is contained in:
pravussum
2021-04-11 19:56:46 +02:00
committed by GitHub
parent 53d168991c
commit fc81909596
17 changed files with 5227 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.io.metrics-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-misc-metrics" description="Metrics Service" version="${project.version}">
<feature>openhab-runtime-base</feature>
<feature>openhab-core-model-item</feature>
<feature>openhab-core-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.io.metrics/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,39 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.io.metrics;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The {@link MetricsConfiguration} class holds the configuration for the metrics service
*
* @author Robert Bach - Initial contribution
*/
@NonNullByDefault
public class MetricsConfiguration {
public boolean influxMetricsEnabled = false;
public String influxURL = "http://localhost:8086";
public String influxDB = "openhab";
public @Nullable String influxPassword = null;
public @Nullable String influxUsername = null;
public Integer influxUpdateIntervalInSeconds = 300;
@Override
public String toString() {
return "MetricsConfiguration{" + "influxMetricsEnabled=" + influxMetricsEnabled + ", influxURL='" + influxURL
+ '\'' + ", influxDB='" + influxDB + '\'' + ", influxPassword='" + influxPassword + '\''
+ ", influxUsername='" + influxUsername + '\'' + ", influxUpdateIntervalInSeconds="
+ influxUpdateIntervalInSeconds + '}';
}
}

View File

@@ -0,0 +1,68 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.io.metrics;
import java.util.Objects;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.micrometer.core.instrument.composite.CompositeMeterRegistry;
/**
* {@link MetricsExporter} provides the interface for components exporting metrics to push based monitoring systems.
*
* @author Robert Bach - Initial contribution
*/
@NonNullByDefault
public abstract class MetricsExporter {
private final Logger logger = LoggerFactory.getLogger(MetricsExporter.class);
private boolean active = false;
protected @Nullable CompositeMeterRegistry meterRegistry = null;
protected @Nullable MetricsConfiguration config = null;
protected abstract void start(CompositeMeterRegistry meterRegistry, MetricsConfiguration metricsConfiguration);
protected abstract void shutdown();
protected abstract boolean isEnabled(MetricsConfiguration config);
public void updateExporterState(@Nullable MetricsConfiguration config) {
this.config = config;
if (config != null && isEnabled(config) && meterRegistry != null) {
if (!active) {
logger.debug("Activating exporter {} ", this.getClass().getSimpleName());
active = true;
start(Objects.requireNonNull(meterRegistry), config);
} else {
logger.trace("Exporter {} already active.", this.getClass().getSimpleName());
}
} else {
if (active) {
logger.debug("Shutting down exporter {} ", this.getClass().getSimpleName());
shutdown();
active = false;
} else {
logger.trace("Exporter {} already shut down.", this.getClass().getSimpleName());
}
}
}
public void setMeterRegistry(@Nullable CompositeMeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
updateExporterState(config);
}
}

View File

@@ -0,0 +1,83 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.io.metrics;
import java.util.Objects;
import javax.annotation.security.RolesAllowed;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.auth.Role;
import org.openhab.core.io.monitor.MeterRegistryProvider;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants;
import org.osgi.service.jaxrs.whiteboard.propertytypes.JSONRequired;
import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsApplicationSelect;
import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.micrometer.core.instrument.composite.CompositeMeterRegistry;
import io.micrometer.prometheus.PrometheusConfig;
import io.micrometer.prometheus.PrometheusMeterRegistry;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
/**
* * The {@link MetricsRestController} class implements the REST endpoints for all pull based monitoring systems.
*
* @author Robert Bach - Initial contribution
*/
@Component(immediate = true, service = MetricsRestController.class)
@JaxrsResource
@JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + MetricsService.METRICS_APP_NAME + ")")
@Path("")
@JSONRequired
@RolesAllowed({ Role.USER, Role.ADMIN })
@Tag(name = MetricsRestController.PATH_METRICS)
@NonNullByDefault
public class MetricsRestController {
private final Logger logger = LoggerFactory.getLogger(MetricsRestController.class);
public static final String PATH_METRICS = "metrics";
private @Nullable CompositeMeterRegistry meterRegistry = null;
private final PrometheusMeterRegistry prometheusMeterRegistry = new PrometheusMeterRegistry(
PrometheusConfig.DEFAULT);
@GET
@Path("/prometheus")
@Produces(MediaType.TEXT_PLAIN)
@Operation(operationId = "getPrometheusMetrics", summary = "Gets openHAB system and core metrics in a Prometheus compatible format.", responses = {
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = String.class))) })
public String getPrometheusMetrics() {
return prometheusMeterRegistry.scrape();
}
@Reference
public void setMeterRegistryProvider(MeterRegistryProvider meterRegistryProvider) {
if (meterRegistry != null) {
Objects.requireNonNull(meterRegistry).remove(prometheusMeterRegistry);
}
meterRegistry = meterRegistryProvider.getOHMeterRegistry();
Objects.requireNonNull(meterRegistry).add(prometheusMeterRegistry);
logger.debug("Core metrics registry retrieved and Prometheus registry added successfully.");
}
}

View File

@@ -0,0 +1,117 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.io.metrics;
import java.util.Dictionary;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import javax.ws.rs.core.Application;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.config.core.ConfigurableService;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.io.monitor.MeterRegistryProvider;
import org.openhab.io.metrics.exporters.InfluxMetricsExporter;
import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Modified;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants;
import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.micrometer.core.instrument.composite.CompositeMeterRegistry;
/**
* The {@link MetricsService} class implements the central component controlling the different metric endpoints/syncers.
*
* @author Robert Bach - Initial contribution
*/
@NonNullByDefault
@Component(immediate = true, service = MetricsService.class)
@ConfigurableService(category = "io", label = "Metrics service", description_uri = "io:metrics")
public class MetricsService {
public static final String METRICS_APP_NAME = "Metrics";
public static final String ROOT = "/metrics";
private final Logger logger = LoggerFactory.getLogger(MetricsService.class);
private @Nullable ServiceRegistration<Application> restService = null;
private @Nullable MetricsConfiguration config;
@Reference
protected @NonNullByDefault({}) MetricsRestController metrics;
private Set<MetricsExporter> metricsExporters = new HashSet<>();
private @Nullable CompositeMeterRegistry meterRegistry = null;
@Activate
protected void activate(Map<@Nullable String, @Nullable Object> configuration) {
MetricsRestApplication app = new MetricsRestApplication();
BundleContext context = FrameworkUtil.getBundle(getClass()).getBundleContext();
restService = context.registerService(Application.class, app, getServiceProperties());
logger.info("Metrics service available under {}.", ROOT);
metricsExporters.add(new InfluxMetricsExporter());
updateConfig(configuration);
updateMeterRegistry();
}
@Modified
protected synchronized void modified(Map<@Nullable String, @Nullable Object> configuration) {
updateConfig(configuration);
}
@Deactivate
protected void deactivate() {
if (restService != null) {
Objects.requireNonNull(restService).unregister();
}
}
Dictionary<@Nullable String, @Nullable String> getServiceProperties() {
Dictionary<@Nullable String, @Nullable String> dict = new Hashtable<>();
dict.put(JaxrsWhiteboardConstants.JAX_RS_APPLICATION_BASE, ROOT);
return dict;
}
@JaxrsName(METRICS_APP_NAME)
private class MetricsRestApplication extends Application {
@NonNullByDefault({})
@Override
public Set<Object> getSingletons() {
return Set.of(metrics);
}
}
@Reference
public void setMeterRegistryProvider(MeterRegistryProvider meterRegistryProvider) {
meterRegistry = meterRegistryProvider.getOHMeterRegistry();
updateMeterRegistry();
}
private void updateConfig(@Nullable Map<@Nullable String, @Nullable Object> configuration) {
this.config = new Configuration(configuration).as(MetricsConfiguration.class);
logger.debug("Configuration: {}", this.config);
this.metricsExporters.forEach(e -> e.updateExporterState(config));
}
private void updateMeterRegistry() {
this.metricsExporters.forEach(e -> e.setMeterRegistry(meterRegistry));
}
}

View File

@@ -0,0 +1,99 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.io.metrics.exporters;
import java.time.Duration;
import java.util.Objects;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.io.metrics.MetricsConfiguration;
import org.openhab.io.metrics.MetricsExporter;
import io.micrometer.core.instrument.Clock;
import io.micrometer.core.instrument.composite.CompositeMeterRegistry;
import io.micrometer.influx.InfluxConfig;
import io.micrometer.influx.InfluxMeterRegistry;
/**
* The {@link InfluxMetricsExporter} class implements a MetricsExporter for InfluxDB
*
* @author Robert Bach - Initial contribution
*/
@NonNullByDefault
public class InfluxMetricsExporter extends MetricsExporter {
private @Nullable InfluxMeterRegistry influxMeterRegistry = null;
private @Nullable CompositeMeterRegistry meterRegistry = null;
@Override
public void start(CompositeMeterRegistry meterRegistry, MetricsConfiguration metricsConfiguration) {
influxMeterRegistry = new InfluxMeterRegistry(getInfluxConfig(metricsConfiguration), Clock.SYSTEM);
meterRegistry.add(influxMeterRegistry);
}
@Override
public void shutdown() {
if (influxMeterRegistry != null) {
Objects.requireNonNull(influxMeterRegistry).stop();
}
if (meterRegistry != null) {
Objects.requireNonNull(meterRegistry).remove(influxMeterRegistry);
meterRegistry = null;
}
influxMeterRegistry = null;
}
private InfluxConfig getInfluxConfig(MetricsConfiguration metricsConfiguration) {
return new InfluxConfig() {
@Override
public Duration step() {
return Duration.ofSeconds(metricsConfiguration.influxUpdateIntervalInSeconds);
}
@Override
public String uri() {
return metricsConfiguration.influxURL;
}
@Override
public String db() {
return metricsConfiguration.influxDB;
}
@Override
@Nullable
public String userName() {
return metricsConfiguration.influxUsername;
}
@Override
@Nullable
public String password() {
return metricsConfiguration.influxPassword;
}
@Override
@io.micrometer.core.lang.Nullable
@Nullable
public String get(@Nullable String k) {
return null; // accept the rest of the defaults
}
};
}
@Override
protected boolean isEnabled(MetricsConfiguration config) {
return config.influxMetricsEnabled;
}
}

View File

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<config-description:config-descriptions
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0
https://openhab.org/schemas/config-description-1.0.0.xsd">
<config-description uri="io:metrics">
<parameter name="influxMetricsEnabled" type="boolean">
<label>Influx Metrics</label>
<description>Enable the Influx (www.influxdata.com) Metrics. Further Configuration of the InfluxDB Instance
Necessary.</description>
<default>false</default>
</parameter>
<parameter name="influxURL" type="text">
<label>InfluxDB URL</label>
<description>The URL of the InfluxDB Instance. Defaults to http://localhost:8086</description>
<default>http://localhost:8086</default>
</parameter>
<parameter name="influxDB" type="text">
<label>InfluxDB Database Name</label>
<description>The Name of the Database to Use. Defaults to "openhab".</description>
<default>openhab</default>
</parameter>
<parameter name="influxUsername" type="text">
<label>InfluxDB User Name</label>
<description>The InfluxDB User Name (No Default).</description>
</parameter>
<parameter name="influxPassword" type="text">
<label>InfluxDB Password</label>
<description>The InfluxDB Password (No Default).</description>
<context>password</context>
</parameter>
<parameter name="influxUpdateIntervalInSeconds" type="integer" unit="s" min="1">
<label>InfluxDB Update Interval in Seconds</label>
<description>Controls How Often Metrics Are Exported to InfluxDB (in Seconds). Defaults to 300</description>
<default>300</default>
</parameter>
</config-description>
</config-description:config-descriptions>