Skip to content

Commit

Permalink
Begin adjusting MetricsFeature; add formatting for Prometheus and JSON
Browse files Browse the repository at this point in the history
  • Loading branch information
tjquinno committed Aug 18, 2023
1 parent 45744b4 commit 77d9f73
Show file tree
Hide file tree
Showing 19 changed files with 1,939 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,22 @@ public interface MeterRegistry extends Wrapper {
*/
Iterable<Meter> meters(Iterable<String> scopeSelection);

/**
* Returns the scopes (if any) as represented by tags on meter IDs.
*
* @return scopes across all registered meters
*/
Iterable<String> scopes();

/**
* Returns whether the specified meter is enabled or not, based on whether the meter registry as a whole is enabled and also
* whether the config settings for filtering include and exclude indicate the specific meter is enabled.
*
* @param meterId meter ID to check
* @return true if the meter (and its meter registry) are enabled; false otherwise
*/
boolean isMeterEnabled(Meter.Id meterId);

/**
* Returns the default {@link io.helidon.metrics.api.Clock} in use by the registry.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright (c) 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.helidon.metrics.api;

import java.util.Optional;

/**
* Formatter of a {@link io.helidon.metrics.api.MeterRegistry} according to a given media type.
*/
public interface MeterRegistryFormatter {

/**
* Formats the meter registry's data.
*
* @return formatted output
*/
Optional<?> format();

/**
* Formats the meter registry's metadata.
*
* @return formatted metadata output
*/
Optional<?> formatMetadata();
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,27 @@
public interface MetricsFactory {

/**
* Returns an implementation which has the highest weight of the factories available at runtime.
* Returns the most-recently created implementation or, if none, a new one from a highest-weight provider available at
* runtime and using the {@value MetricsConfig.Builder#METRICS_CONFIG_KEY} section from the
* {@link io.helidon.common.config.GlobalConfig}.
*
* @return highest-weight metrics factory
* @return current or new metrics factory
*/
static MetricsFactory getInstance() {
return MetricsFactoryManager.getInstance();
}

/**
* Returns a new instance from a highest-weight provider available at runtime using the provided
* {@link io.helidon.metrics.api.MetricsConfig}.
*
* @param metricsConfig metrics config
* @return new metrics factory
*/
static MetricsFactory getInstance(MetricsConfig metricsConfig) {
return MetricsFactoryManager.getInstance(metricsConfig);
}

/**
* Returns the global meter registry.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,39 +15,120 @@
*/
package io.helidon.metrics.api;

import java.util.Objects;
import java.util.ServiceLoader;
import java.util.concurrent.Callable;
import java.util.concurrent.locks.ReentrantLock;

import io.helidon.common.HelidonServiceLoader;
import io.helidon.common.LazyValue;
import io.helidon.common.config.GlobalConfig;
import io.helidon.metrics.spi.MetricsFactoryProvider;

/**
* Locates and makes available a highest-weight implementation of {@link io.helidon.metrics.spi.MetricsFactoryProvider},
* using a default no-op implementation if no other is available.
* Provides {@link io.helidon.metrics.api.spi.MetricFactory} instances using a highest-weight implementation of
* {@link io.helidon.metrics.spi.MetricsFactoryProvider}, defaulting to a no-op implementation if no other is available.
* <p>
* The {@link #getInstance()} and {@link #getInstance(MetricsConfig)} methods update and use the most-recently used
* {@link io.helidon.metrics.api.MetricsConfig} (which could be derived from {@link io.helidon.common.config.GlobalConfig})
* and the most-recently created {@link io.helidon.metrics.api.MetricsFactory}.
* </p>
* <p>
* The {@link #create(MetricsConfig)} method neither reads nor updates the most-recently used config and factory.
* </p>
*/
class MetricsFactoryManager {

private static final System.Logger LOGGER = System.getLogger(MetricsFactoryManager.class.getName());

/**
* Instance of the highest-weight implementation of {@link io.helidon.metrics.spi.MetricsFactoryProvider}.
*/
private static final LazyValue<MetricsFactoryProvider> METRICS_FACTORY_PROVIDER =
LazyValue.create(() -> HelidonServiceLoader.builder(ServiceLoader.load(MetricsFactoryProvider.class))
.addService(NoOpMetricsFactoryProvider.create(), Double.MIN_VALUE)
.build()
.iterator()
.next());

private static final LazyValue<MetricsFactory> METRICS_FACTORY =
LazyValue.create(() -> METRICS_FACTORY_PROVIDER.get().create(
MetricsConfig.builder()
.config(GlobalConfig.config().get(MetricsConfig.METRICS_CONFIG_KEY))
.build()));
LazyValue.create(() -> {
MetricsFactoryProvider result = HelidonServiceLoader.builder(ServiceLoader.load(MetricsFactoryProvider.class))
.addService(NoOpMetricsFactoryProvider.create(), Double.MIN_VALUE)
.build()
.iterator()
.next();
LOGGER.log(System.Logger.Level.DEBUG, "Loaded metrics factory provider: {0}",
result.getClass().getName());
return result;
});

/**
* The {@link io.helidon.metrics.api.MetricsFactory} most recently created via either {@link #getInstance} method.
*/
private static MetricsFactory metricsFactory;

/**
* The {@link io.helidon.metrics.api.MetricsConfig} used to create {@link #metricsFactory}.
*/
private static MetricsConfig metricsConfig;

private static final ReentrantLock LOCK = new ReentrantLock();

/**
* Creates a new {@link io.helidon.metrics.api.MetricsFactory} according to the specified
* {@link io.helidon.metrics.api.MetricsConfig}, saving the config as the current config and the new factory as the current
* factory.
*
* @param metricsConfig metrics config
* @return new metrics factory
*/
static MetricsFactory getInstance(MetricsConfig metricsConfig) {
return access(() -> {
MetricsFactoryManager.metricsConfig = metricsConfig;
MetricsFactoryManager.metricsFactory = METRICS_FACTORY_PROVIDER.get().create(metricsConfig);
return MetricsFactoryManager.metricsFactory;
});
}

/**
* Returns the current {@link io.helidon.metrics.api.MetricsFactory}, creating one if needed using the global configuration
* and saving the {@link io.helidon.metrics.api.MetricsConfig} from the global config as the current config and the new
* factory as the current factory.
*
* @return current metrics factory
*/
static MetricsFactory getInstance() {
return METRICS_FACTORY.get();
return access(() -> metricsFactory = Objects.requireNonNullElseGet(metricsFactory,
() -> METRICS_FACTORY_PROVIDER.get()
.create(ensureMetricsConfig())));
}

/**
* Creates a new {@link io.helidon.metrics.api.MetricsFactory} using the specified
* {@link io.helidon.metrics.api.MetricsConfig} with no side effects: neither the config nor the new factory replace
* the current values stored in this manager.
*
* @param metricsConfig metrics config to use in creating the factory
* @return new metrics factory
*/
static MetricsFactory create(MetricsConfig metricsConfig) {
return METRICS_FACTORY_PROVIDER.get().create(metricsConfig);
}

private static MetricsConfig ensureMetricsConfig() {
metricsConfig = Objects.requireNonNullElseGet(metricsConfig,
() -> MetricsConfig
.create(GlobalConfig.config()
.get(MetricsConfig.METRICS_CONFIG_KEY)));
return metricsConfig;
}

private static <T> T access(Callable<T> c) {
LOCK.lock();
try {
return c.call();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
LOCK.unlock();
}
}

private MetricsFactoryManager() {
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,16 @@ public boolean isDeleted(Meter meter) {
return false;
}

@Override
public boolean isMeterEnabled(Meter.Id meterId) {
return true;
}

@Override
public Iterable<String> scopes() {
return Set.of();
}

@Override
public <B extends Meter.Builder<B, M>, M extends Meter> M getOrCreate(B builder) {
NoOpMeter.Builder<?, ?> b = (NoOpMeter.Builder<?, ?>) builder;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright (c) 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.helidon.metrics.spi;

import java.util.Optional;

import io.helidon.common.media.type.MediaType;
import io.helidon.metrics.api.MeterRegistry;
import io.helidon.metrics.api.MeterRegistryFormatter;

/**
* Behavior for providers of meter registry formatters, which (if then can) furnish a formatter given a
* {@link io.helidon.common.media.type.MediaType}.
*
* <p>
* We use a provider approach so code can obtain and run formatters that might depend heavily on particular implementations
* without the calling code having to share that heavy dependency.
* </p>
*/
public interface MeterRegistryFormatterProvider {

/**
* Returns, if possible, a {@link io.helidon.metrics.api.MeterRegistryFormatter} capable of preparing output according to
* the specified {@link io.helidon.common.media.type.MediaType}.
* @param mediaType media type of the desired output
* @param meterRegistry {@link io.helidon.metrics.api.MeterRegistry} from which to gather data
* @param scopeTagName tag name used to record scope
* @param scopeSelection scope names to format; empty means no scope-based restriction
* @param nameSelection meter names to format; empty means no name-based restriction
* @return compatible formatter; empty if none
*/
Optional<MeterRegistryFormatter> formatter(MediaType mediaType,
MeterRegistry meterRegistry,
String scopeTagName,
Iterable<String> scopeSelection,
Iterable<String> nameSelection);
}
4 changes: 4 additions & 0 deletions metrics/api/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@

requires io.helidon.builder.api;
requires static io.helidon.config.metadata;

// TODO remove next line once we no longer need the MP metrics API
requires transitive microprofile.metrics.api;

requires io.helidon.inject.configdriven.api;

exports io.helidon.metrics.api;
Expand All @@ -39,5 +42,6 @@
uses ExemplarService;
uses io.helidon.metrics.api.MetricsProgrammaticSettings;
uses io.helidon.metrics.spi.MetricsFactoryProvider;
uses io.helidon.metrics.spi.MeterRegistryFormatterProvider;
uses MetricsFactory;
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
* "m_" if the actual meter name starts with a digit or underscore and underscores replace special characters.
* </p>
*/
// TODO remove this class once we've converted to use the one in helidon-metrics-micrometer.
public class MicrometerPrometheusFormatter {
/**
* Mapping from supported media types to the corresponding Prometheus registry content types.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,17 @@ public Collection<? extends io.helidon.metrics.api.Meter> meters(Predicate<io.he
.toList();
}

@Override
public Iterable<String> scopes() {
return scopeMembership.keySet();
}

// TODO enhance after adding back the filtering config
@Override
public boolean isMeterEnabled(io.helidon.metrics.api.Meter.Id meterId) {
return metricsConfig.enabled();
}

@Override
public Clock clock() {
return clock;
Expand Down
Loading

0 comments on commit 77d9f73

Please sign in to comment.