/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 gobblin.metrics;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import com.google.common.base.Optional;
import com.google.common.base.Throwables;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.Lists;
import gobblin.configuration.ConfigurationKeys;
import gobblin.configuration.State;
/**
* Registry that stores instances of {@link GobblinMetrics} identified by an arbitrary string id. The static method
* {@link #getInstance()} provides a static instance of this this class that should be considered the global registry of
* metrics.
*
* <p>
* An application could also instantiate one or more registries to, for example, separate instances of
* {@link GobblinMetrics} into different scopes.
* </p>
*/
public class GobblinMetricsRegistry {
private static final GobblinMetricsRegistry GLOBAL_INSTANCE = new GobblinMetricsRegistry();
private final Cache<String, GobblinMetrics> metricsCache = CacheBuilder.newBuilder().softValues().build();
private GobblinMetricsRegistry() {
// Do nothing
}
/**
* Associate a {@link GobblinMetrics} instance with a given ID if the ID is
* not already associated with a {@link GobblinMetrics} instance.
*
* @param id the given {@link GobblinMetrics} ID
* @param gobblinMetrics the {@link GobblinMetrics} instance to be associated with the given ID
* @return the previous {@link GobblinMetrics} instance associated with the ID or {@code null}
* if there's no previous {@link GobblinMetrics} instance associated with the ID
*/
public GobblinMetrics putIfAbsent(String id, GobblinMetrics gobblinMetrics) {
return this.metricsCache.asMap().putIfAbsent(id, gobblinMetrics);
}
/**
* Get the {@link GobblinMetrics} instance associated with a given ID.
*
* @param id the given {@link GobblinMetrics} ID
* @return the {@link GobblinMetrics} instance associated with the ID, wrapped in an {@link Optional} or
* {@link Optional#absent()} if no {@link GobblinMetrics} instance for the given ID is found
*/
public Optional<GobblinMetrics> get(String id) {
return Optional.fromNullable(this.metricsCache.getIfPresent(id));
}
/**
* Get the {@link GobblinMetrics} instance associated with a given ID. If the ID is not found this method returns the
* {@link GobblinMetrics} returned by the given {@link Callable}, and creates a mapping between the specified ID
* and the {@link GobblinMetrics} instance returned by the {@link Callable}.
*
* @param id the given {@link GobblinMetrics} ID
* @param valueLoader a {@link Callable} that returns a {@link GobblinMetrics}, the {@link Callable} is only invoked
* if the given id is not found
*
* @return a {@link GobblinMetrics} instance associated with the id
*/
public GobblinMetrics getOrDefault(String id, Callable<? extends GobblinMetrics> valueLoader) {
try {
return this.metricsCache.get(id, valueLoader);
} catch (ExecutionException ee) {
throw Throwables.propagate(ee);
}
}
/**
* Remove the {@link GobblinMetrics} instance with a given ID.
*
* @param id the given {@link GobblinMetrics} ID
* @return removed {@link GobblinMetrics} instance or {@code null} if no
* {@link GobblinMetrics} instance for the given ID is found
*/
public GobblinMetrics remove(String id) {
return this.metricsCache.asMap().remove(id);
}
/**
* Get an instance of {@link GobblinMetricsRegistry}.
*
* @return an instance of {@link GobblinMetricsRegistry}
*/
public static GobblinMetricsRegistry getInstance() {
return GLOBAL_INSTANCE;
}
/**
* <p>
* Creates {@link gobblin.metrics.MetricContext}. Tries to read the name of the parent context
* from key "metrics.context.name" at state, and tries to get the parent context by name from
* the {@link gobblin.metrics.MetricContext} registry (the parent context must be registered).
* </p>
*
* <p>
* Automatically adds two tags to the inner context:
* <ul>
* <li> component: attempts to determine which component type within gobblin-api generated this instance. </li>
* <li> class: the specific class of the object that generated this instance of Instrumented </li>
* </ul>
* </p>
*
*/
public MetricContext getMetricContext(State state, Class<?> klazz, List<Tag<?>> tags) {
int randomId = new Random().nextInt(Integer.MAX_VALUE);
List<Tag<?>> generatedTags = Lists.newArrayList();
if (!klazz.isAnonymousClass()) {
generatedTags.add(new Tag<>("class", klazz.getCanonicalName()));
}
Optional<GobblinMetrics> gobblinMetrics = state.contains(ConfigurationKeys.METRIC_CONTEXT_NAME_KEY)
? GobblinMetricsRegistry.getInstance().get(state.getProp(ConfigurationKeys.METRIC_CONTEXT_NAME_KEY))
: Optional.<GobblinMetrics> absent();
MetricContext.Builder builder = gobblinMetrics.isPresent()
? gobblinMetrics.get().getMetricContext().childBuilder(klazz.getCanonicalName() + "." + randomId)
: MetricContext.builder(klazz.getCanonicalName() + "." + randomId);
return builder.addTags(generatedTags).addTags(tags).build();
}
}