All Downloads are FREE. Search and download functionalities are using the official Maven repository.

gobblin.metrics.MetricContext Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2014-2016 LinkedIn Corp. All rights reserved.
 *
 * 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.
 */

package gobblin.metrics;

import lombok.Getter;

import java.io.Closeable;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.codahale.metrics.Counter;
import com.codahale.metrics.MetricFilter;
import com.codahale.metrics.Gauge;
import com.codahale.metrics.Histogram;
import com.codahale.metrics.Meter;
import com.codahale.metrics.Metric;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.MetricSet;
import com.codahale.metrics.Timer;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.base.Throwables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.io.Closer;
import com.google.common.util.concurrent.MoreExecutors;

import gobblin.metrics.context.NameConflictException;
import gobblin.metrics.context.ReportableContext;
import gobblin.metrics.notification.EventNotification;
import gobblin.metrics.notification.Notification;
import gobblin.util.ExecutorsUtils;


/**
 * This class models a {@link MetricSet} that optionally has a list of {@link Tag}s
 * and a set of {@link com.codahale.metrics.ScheduledReporter}s associated with it. The
 * {@link Tag}s associated with a {@link MetricContext} are used to construct the
 * common metric name prefix of registered {@link com.codahale.metrics.Metric}s.
 *
 * 

* {@link MetricContext}s can form a hierarchy and any {@link MetricContext} can create * children {@link MetricContext}s. A child {@link MetricContext} inherit all the * {@link Tag}s associated with its parent, in additional to the {@link Tag}s * of itself. {@link Tag}s inherited from its parent will appear in front of those * of itself when constructing the metric name prefix. *

* * @author Yinan Li */ public class MetricContext extends MetricRegistry implements ReportableContext, Closeable { protected final Closer closer; public static final String METRIC_CONTEXT_ID_TAG_NAME = "metricContextID"; public static final String METRIC_CONTEXT_NAME_TAG_NAME = "metricContextName"; @Getter private final InnerMetricContext innerMetricContext; private static final Logger LOG = LoggerFactory.getLogger(MetricContext.class); public static final String GOBBLIN_METRICS_NOTIFICATIONS_TIMER_NAME = "gobblin.metrics.notifications.timer"; // Targets for notifications. private final Map> notificationTargets; private final ContextAwareTimer notificationTimer; private Optional executorServiceOptional; // This set exists so that metrics that have no hard references in code don't get GCed while the MetricContext // is alive. private final Set contextAwareMetricsSet; protected MetricContext(String name, MetricContext parent, List> tags, boolean isRoot) throws NameConflictException { Preconditions.checkArgument(!Strings.isNullOrEmpty(name)); this.closer = Closer.create(); try { this.innerMetricContext = this.closer.register(new InnerMetricContext(this, name, parent, tags)); } catch(ExecutionException ee) { throw Throwables.propagate(ee); } this.contextAwareMetricsSet = Sets.newConcurrentHashSet(); this.notificationTargets = Maps.newConcurrentMap(); this.executorServiceOptional = Optional.absent(); this.notificationTimer = new ContextAwareTimer(this, GOBBLIN_METRICS_NOTIFICATIONS_TIMER_NAME); register(this.notificationTimer); if (!isRoot) { RootMetricContext.get().addMetricContext(this); } } private synchronized ExecutorService getExecutorService() { if(!this.executorServiceOptional.isPresent()) { this.executorServiceOptional = Optional.of(MoreExecutors.getExitingExecutorService( (ThreadPoolExecutor) Executors.newCachedThreadPool(ExecutorsUtils.newThreadFactory(Optional.of(LOG), Optional.of("MetricContext-" + getName() + "-%d"))), 5, TimeUnit.MINUTES)); } return this.executorServiceOptional.get(); } /** * Get the name of this {@link MetricContext}. * * @return the name of this {@link MetricContext} */ public String getName() { return this.innerMetricContext.getName(); } /** * Get the parent {@link MetricContext} of this {@link MetricContext} wrapped in an * {@link com.google.common.base.Optional}, which may be absent if it has not parent * {@link MetricContext}. * * @return the parent {@link MetricContext} of this {@link MetricContext} wrapped in an * {@link com.google.common.base.Optional} */ public Optional getParent() { return this.innerMetricContext.getParent(); } /** * Get a view of the child {@link gobblin.metrics.MetricContext}s as a {@link com.google.common.collect.ImmutableMap}. * @return {@link com.google.common.collect.ImmutableMap} of * child {@link gobblin.metrics.MetricContext}s keyed by their names. */ public Map getChildContextsAsMap() { return this.innerMetricContext.getChildContextsAsMap(); } /** * See {@link com.codahale.metrics.MetricRegistry#getNames()}. * *

* This method will return fully-qualified metric names if the {@link MetricContext} is configured * to report fully-qualified metric names. *

*/ @Override public SortedSet getNames() { return this.innerMetricContext.getNames(); } /** * Submit {@link gobblin.metrics.GobblinTrackingEvent} to all notification listeners attached to this or any * ancestor {@link gobblin.metrics.MetricContext}s. The argument for this method is mutated by the method, so it * should not be reused by the caller. * * @param nonReusableEvent {@link GobblinTrackingEvent} to submit. This object will be mutated by the method, * so it should not be reused by the caller. */ public void submitEvent(GobblinTrackingEvent nonReusableEvent) { nonReusableEvent.setTimestamp(System.currentTimeMillis()); // Inject metric context tags into event metadata. Map originalMetadata = nonReusableEvent.getMetadata(); Map tags = getTagMap(); Map newMetadata = Maps.newHashMap(); for(Map.Entry entry : tags.entrySet()) { newMetadata.put(entry.getKey(), entry.getValue().toString()); } newMetadata.putAll(originalMetadata); nonReusableEvent.setMetadata(newMetadata); EventNotification notification = new EventNotification(nonReusableEvent); sendNotification(notification); } /** * See {@link com.codahale.metrics.MetricRegistry#getMetrics()}. * *

* This method will return fully-qualified metric names if the {@link MetricContext} is configured * to report fully-qualified metric names. *

*/ @Override public Map getMetrics() { return this.innerMetricContext.getMetrics(); } /** * See {@link com.codahale.metrics.MetricRegistry#getGauges()}. * *

* This method will return fully-qualified metric names if the {@link MetricContext} is configured * to report fully-qualified metric names. *

*/ @Override public SortedMap getGauges() { return this.innerMetricContext.getGauges(MetricFilter.ALL); } /** * See {@link com.codahale.metrics.MetricRegistry#getGauges(com.codahale.metrics.MetricFilter)}. * *

* This method will return fully-qualified metric names if the {@link MetricContext} is configured * to report fully-qualified metric names. *

*/ @Override public SortedMap getGauges(MetricFilter filter) { return this.innerMetricContext.getGauges(filter); } /** * See {@link com.codahale.metrics.MetricRegistry#getCounters()}. * *

* This method will return fully-qualified metric names if the {@link MetricContext} is configured * to report fully-qualified metric names. *

*/ @Override public SortedMap getCounters() { return this.innerMetricContext.getCounters(MetricFilter.ALL); } /** * See {@link com.codahale.metrics.MetricRegistry#getCounters(com.codahale.metrics.MetricFilter)}. * *

* This method will return fully-qualified metric names if the {@link MetricContext} is configured * to report fully-qualified metric names. *

*/ @Override public SortedMap getCounters(MetricFilter filter) { return this.innerMetricContext.getCounters(filter); } /** * See {@link com.codahale.metrics.MetricRegistry#getHistograms()}. * *

* This method will return fully-qualified metric names if the {@link MetricContext} is configured * to report fully-qualified metric names. *

*/ @Override public SortedMap getHistograms() { return this.innerMetricContext.getHistograms(MetricFilter.ALL); } /** * See {@link com.codahale.metrics.MetricRegistry#getHistograms(com.codahale.metrics.MetricFilter)}. * *

* This method will return fully-qualified metric names if the {@link MetricContext} is configured * to report fully-qualified metric names. *

*/ @Override public SortedMap getHistograms(MetricFilter filter) { return this.innerMetricContext.getHistograms(filter); } /** * See {@link com.codahale.metrics.MetricRegistry#getMeters()}. * *

* This method will return fully-qualified metric names if the {@link MetricContext} is configured * to report fully-qualified metric names. *

*/ @Override public SortedMap getMeters() { return this.innerMetricContext.getMeters(MetricFilter.ALL); } /** * See {@link com.codahale.metrics.MetricRegistry#getMeters(com.codahale.metrics.MetricFilter)}. * *

* This method will return fully-qualified metric names if the {@link MetricContext} is configured * to report fully-qualified metric names. *

*/ @Override public SortedMap getMeters(MetricFilter filter) { return this.innerMetricContext.getMeters(filter); } /** * See {@link com.codahale.metrics.MetricRegistry#getTimers()}. * *

* This method will return fully-qualified metric names if the {@link MetricContext} is configured * to report fully-qualified metric names. *

*/ @Override public SortedMap getTimers() { return this.innerMetricContext.getTimers(MetricFilter.ALL); } /** * See {@link com.codahale.metrics.MetricRegistry#getTimers(com.codahale.metrics.MetricFilter)}. * *

* This method will return fully-qualified metric names if the {@link MetricContext} is configured * to report fully-qualified metric names. *

*/ @Override public SortedMap getTimers(MetricFilter filter) { return this.innerMetricContext.getTimers(filter); } /** * This is equivalent to {@link #contextAwareCounter(String)}. */ @Override public Counter counter(String name) { return contextAwareCounter(name); } /** * This is equivalent to {@link #contextAwareMeter(String)}. */ @Override public Meter meter(String name) { return contextAwareMeter(name); } /** * This is equivalent to {@link #contextAwareHistogram(String)}. */ @Override public Histogram histogram(String name) { return contextAwareHistogram(name); } /** * This is equivalent to {@link #contextAwareTimer(String)}. */ @Override public Timer timer(String name) { return contextAwareTimer(name); } /** * Register a given metric under a given name. * *

* This method does not support registering {@link com.codahale.metrics.MetricSet}s. * See{@link #registerAll(com.codahale.metrics.MetricSet)}. *

* *

* This method will not register a metric with the same name in the parent context (if it exists). *

*/ @Override public synchronized T register(String name, T metric) throws IllegalArgumentException { if(!(metric instanceof ContextAwareMetric)) { throw new UnsupportedOperationException("Can only register ContextAwareMetrics."); } return this.innerMetricContext.register(name, metric); } /** * Register a {@link gobblin.metrics.ContextAwareMetric} under its own name. */ public T register(T metric) throws IllegalArgumentException { return register(metric.getName(), metric); } /** * Get a {@link ContextAwareCounter} with a given name. * * @param name name of the {@link ContextAwareCounter} * @return the {@link ContextAwareCounter} with the given name */ public ContextAwareCounter contextAwareCounter(String name) { return contextAwareCounter(name, ContextAwareMetricFactory.DEFAULT_CONTEXT_AWARE_COUNTER_FACTORY); } /** * Get a {@link ContextAwareCounter} with a given name. * * @param name name of the {@link ContextAwareCounter} * @param factory a {@link ContextAwareMetricFactory} for building {@link ContextAwareCounter}s * @return the {@link ContextAwareCounter} with the given name */ public ContextAwareCounter contextAwareCounter(String name, ContextAwareMetricFactory factory) { return this.innerMetricContext.getOrCreate(name, factory); } /** * Get a {@link ContextAwareMeter} with a given name. * * @param name name of the {@link ContextAwareMeter} * @return the {@link ContextAwareMeter} with the given name */ public ContextAwareMeter contextAwareMeter(String name) { return contextAwareMeter(name, ContextAwareMetricFactory.DEFAULT_CONTEXT_AWARE_METER_FACTORY); } /** * Get a {@link ContextAwareMeter} with a given name. * * @param name name of the {@link ContextAwareMeter} * @param factory a {@link ContextAwareMetricFactory} for building {@link ContextAwareMeter}s * @return the {@link ContextAwareMeter} with the given name */ public ContextAwareMeter contextAwareMeter(String name, ContextAwareMetricFactory factory) { return this.innerMetricContext.getOrCreate(name, factory); } /** * Get a {@link ContextAwareHistogram} with a given name. * * @param name name of the {@link ContextAwareHistogram} * @return the {@link ContextAwareHistogram} with the given name */ public ContextAwareHistogram contextAwareHistogram(String name) { return contextAwareHistogram(name, ContextAwareMetricFactory.DEFAULT_CONTEXT_AWARE_HISTOGRAM_FACTORY); } /** * Get a {@link ContextAwareHistogram} with a given name. * * @param name name of the {@link ContextAwareHistogram} * @param factory a {@link ContextAwareMetricFactory} for building {@link ContextAwareHistogram}s * @return the {@link ContextAwareHistogram} with the given name */ public ContextAwareHistogram contextAwareHistogram(String name, ContextAwareMetricFactory factory) { return this.innerMetricContext.getOrCreate(name, factory); } /** * Get a {@link ContextAwareTimer} with a given name. * * @param name name of the {@link ContextAwareTimer} * @return the {@link ContextAwareTimer} with the given name */ public ContextAwareTimer contextAwareTimer(String name) { return contextAwareTimer(name, ContextAwareMetricFactory.DEFAULT_CONTEXT_AWARE_TIMER_FACTORY); } /** * Get a {@link ContextAwareTimer} with a given name. * * @param name name of the {@link ContextAwareTimer} * @param factory a {@link ContextAwareMetricFactory} for building {@link ContextAwareTimer}s * @return the {@link ContextAwareTimer} with the given name */ public ContextAwareTimer contextAwareTimer(String name, ContextAwareMetricFactory factory) { return this.innerMetricContext.getOrCreate(name, factory); } /** * Create a new {@link ContextAwareGauge} wrapping a given {@link com.codahale.metrics.Gauge}. * * @param name name of the {@link ContextAwareGauge} * @param gauge the {@link com.codahale.metrics.Gauge} to be wrapped by the {@link ContextAwareGauge} * @param the type of the {@link ContextAwareGauge}'s value * @return a new {@link ContextAwareGauge} */ public ContextAwareGauge newContextAwareGauge(String name, Gauge gauge) { return new ContextAwareGauge(this, name, gauge); } /** * Remove a metric with a given name. * *

* This method will remove the metric with the given name from this {@link MetricContext} * as well as metrics with the same name from every child {@link MetricContext}s. *

* * @param name name of the metric to be removed * @return whether or not the metric has been removed */ @Override public synchronized boolean remove(String name) { return this.innerMetricContext.remove(name); } @Override public void removeMatching(MetricFilter filter) { this.innerMetricContext.removeMatching(filter); } public List> getTags() { return this.innerMetricContext.getTags(); } public Map getTagMap() { return this.innerMetricContext.getTagMap(); } @Override public void close() throws IOException { this.closer.close(); } /** * Get a new {@link MetricContext.Builder} for building child {@link MetricContext}s. * * @param name name of the child {@link MetricContext} to be built * @return a new {@link MetricContext.Builder} for building child {@link MetricContext}s */ public Builder childBuilder(String name) { return builder(name).hasParent(this); } /** * Get a new {@link MetricContext.Builder}. * * @param name name of the {@link MetricContext} to be built * @return a new {@link MetricContext.Builder} */ public static Builder builder(String name) { return new Builder(name); } /** * Add a target for {@link gobblin.metrics.notification.Notification}s. * @param target A {@link com.google.common.base.Function} that will be run every time * there is a new {@link gobblin.metrics.notification.Notification} in this context. * @return a key for this notification target. Can be used to remove the notification target later. */ public UUID addNotificationTarget(Function target) { UUID uuid = UUID.randomUUID(); if(this.notificationTargets.containsKey(uuid)) { throw new RuntimeException("Failed to create notification target."); } this.notificationTargets.put(uuid, target); return uuid; } /** * Remove notification target identified by the given key. * @param key key for the notification target to remove. */ public void removeNotificationTarget(UUID key) { this.notificationTargets.remove(key); } /** * Send a notification to all targets of this context and to the parent of this context. * @param notification {@link gobblin.metrics.notification.Notification} to send. */ public void sendNotification(final Notification notification) { ContextAwareTimer.Context timer = this.notificationTimer.time(); if(!this.notificationTargets.isEmpty()) { for (final Map.Entry> entry : this.notificationTargets.entrySet()) { try { entry.getValue().apply(notification); } catch (RuntimeException exception) { LOG.warn("RuntimeException when running notification target. Skipping.", exception); } } } if(getParent().isPresent()) { getParent().get().sendNotification(notification); } timer.stop(); } void addChildContext(String childContextName, MetricContext childContext) throws NameConflictException, ExecutionException { this.innerMetricContext.addChildContext(childContextName, childContext); } void addToMetrics(ContextAwareMetric metric) { this.contextAwareMetricsSet.add(metric); } void removeFromMetrics(ContextAwareMetric metric) { this.contextAwareMetricsSet.remove(metric); } @VisibleForTesting void clearNotificationTargets() { this.notificationTargets.clear(); } /** * A builder class for {@link MetricContext}. */ public static class Builder { private String name; private MetricContext parent = null; private final List> tags = Lists.newArrayList(); public Builder(String name) { this.name = name; } /** * Set the parent {@link MetricContext} of this {@link MetricContext} instance. * *

* This method is intentionally made private and is only called in {@link MetricContext#childBuilder(String)} * so users will not mistakenly call this method twice if they use {@link MetricContext#childBuilder(String)}. *

* @param parent the parent {@link MetricContext} * @return {@code this} */ private Builder hasParent(MetricContext parent) { this.parent = parent; // Inherit parent context's tags this.tags.addAll(parent.getTags()); return this; } /** * Add a single {@link Tag}. * * @param tag the {@link Tag} to add * @return {@code this} */ public Builder addTag(Tag tag) { this.tags.add(tag); return this; } /** * Add a collection of {@link Tag}s. * * @param tags the collection of {@link Tag}s to add * @return {@code this} */ public Builder addTags(Collection> tags) { this.tags.addAll(tags); return this; } /** * Builder a new {@link MetricContext}. * *

* See {@link Taggable#metricNamePrefix(boolean)} for the semantic of {@code includeTagKeys}. *

* *

* Note this builder may change the name of the built {@link MetricContext} if the parent context already has a child with * that name. If this is unacceptable, use {@link #buildStrict} instead. *

* * @return the newly built {@link MetricContext} */ public MetricContext build() { try { return buildStrict(); } catch (NameConflictException nce) { String uuid = UUID.randomUUID().toString(); LOG.warn("MetricContext with specified name already exists, appending UUID to the given name: " + uuid); this.name = this.name + "_" + uuid; try { return buildStrict(); } catch (NameConflictException nce2) { throw Throwables.propagate(nce2); } } } /** * Builder a new {@link MetricContext}. * *

* See {@link Taggable#metricNamePrefix(boolean)} for the semantic of {@code includeTagKeys}. *

* * @return the newly built {@link MetricContext} * @throws NameConflictException if the parent {@link MetricContext} already has a child with this name. */ public MetricContext buildStrict() throws NameConflictException { if(this.parent == null) { this.parent = RootMetricContext.get(); } return new MetricContext(this.name, this.parent, this.tags, false); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy