gobblin.metrics.InnerMetricContext Maven / Gradle / Ivy
Show all versions of gobblin-metrics Show documentation
/*
 * 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 java.io.Closeable;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
import com.codahale.metrics.Counter;
import com.codahale.metrics.Gauge;
import com.codahale.metrics.Histogram;
import com.codahale.metrics.Meter;
import com.codahale.metrics.MetricFilter;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.MetricSet;
import com.codahale.metrics.Timer;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Maps;
import com.google.common.io.Closer;
import lombok.Getter;
import gobblin.metrics.context.ContextWeakReference;
import gobblin.metrics.context.NameConflictException;
import gobblin.metrics.context.ReportableContext;
import gobblin.metrics.metric.InnerMetric;
/**
 * Contains list of {@link Metric}s, {@link Tag}s, as well as references to parent and child {@link MetricContext} for
 * a {@link MetricContext}. This object is only references by the corresponding {@link MetricContext} as well as the
 * {@link RootMetricContext}, and it is used to report metrics one last time after the corresponding {@link MetricContext}
 * has been GCed.
 */
public class InnerMetricContext extends MetricRegistry implements ReportableContext, Closeable {
  private final Closer closer;
  // Name of this context
  private final String name;
  // A map from simple names to context-aware metrics. All metrics registered with this context (using
  // their simple names) are also registered with the MetricRegistry using their fully-qualified names.
  private final ConcurrentMap contextAwareMetrics = Maps.newConcurrentMap();
  // This is used to work on tags associated with this context
  private final Tagged tagged;
  // Reference to the parent context wrapped in an Optional as there may be no parent context
  private final Optional parent;
  // A map from child context names to child contexts
  private final Cache children = CacheBuilder.newBuilder().weakValues().build();
  @Getter
  private final WeakReference metricContext;
  protected InnerMetricContext(MetricContext context, String name, MetricContext parent, List> tags)
      throws NameConflictException, ExecutionException {
    this.name = name;
    this.closer = Closer.create();
    this.parent = Optional.fromNullable(parent);
    if (this.parent.isPresent()) {
      this.parent.get().addChildContext(this.name, context);
      this.metricContext = new ContextWeakReference(context, this);
    } else {
      this.metricContext = new WeakReference<>(context);
    }
    this.tagged = new Tagged(tags);
    this.tagged.addTag(new Tag<>(MetricContext.METRIC_CONTEXT_ID_TAG_NAME, UUID.randomUUID().toString()));
    this.tagged.addTag(new Tag<>(MetricContext.METRIC_CONTEXT_NAME_TAG_NAME, name));
  }
  /**
   * Get the name of this {@link MetricContext}.
   *
   * @return the name of this {@link MetricContext}
   */
  @Override
  public String getName() {
    return this.name;
  }
  /**
   * 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}
   */
  @Override
  public Optional getParent() {
    return this.parent;
  }
  /**
   * Add a {@link MetricContext} as a child of this {@link MetricContext} if it is not currently a child.
   *
   * @param childContextName the name of the child {@link MetricContext}
   * @param childContext the child {@link MetricContext} to add
   */
  public synchronized void addChildContext(String childContextName, final MetricContext childContext)
      throws NameConflictException, ExecutionException {
    if (this.children.get(childContextName, new Callable() {
      @Override
      public MetricContext call() throws Exception {
        return childContext;
      }
    }) != childContext) {
      throw new NameConflictException("A child context with that name already exists.");
    }
  }
  /**
   * 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.
   */
  @Override
  public Map getChildContextsAsMap() {
    return ImmutableMap.copyOf(this.children.asMap());
  }
  /**
   * 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 getSimpleNames();
  }
  /**
   * 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 getSimplyNamedMetrics();
  }
  /**
   * 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 getSimplyNamedMetrics(Gauge.class, Optional.of(filter));
  }
  /**
   * 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 getSimplyNamedMetrics(Counter.class, Optional.of(filter));
  }
  /**
   * 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 getSimplyNamedMetrics(Histogram.class, Optional.of(filter));
  }
  /**
   * 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 getSimplyNamedMetrics(Meter.class, Optional.of(filter));
  }
  /**
   * 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 getSimplyNamedMetrics(Timer.class, Optional.of(filter));
  }
  /**
   * 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");
    }
    if (this.contextAwareMetrics.putIfAbsent(name, ((ContextAwareMetric) metric).getInnerMetric()) != null) {
      throw new IllegalArgumentException("A metric named " + name + " already exists");
    }
    MetricContext metricContext = this.metricContext.get();
    if (metricContext != null) {
      metricContext.addToMetrics((ContextAwareMetric) metric);
    }
    // Also register the metric with the MetricRegistry using its fully-qualified name
    return metric;
  }
  /**
   * Register a {@link gobblin.metrics.ContextAwareMetric} under its own name.
   */
  public  T register(T metric) throws IllegalArgumentException {
    return register(metric.getName(), metric);
  }
  @Override
  public void registerAll(MetricSet metrics) throws IllegalArgumentException {
    throw new UnsupportedOperationException();
  }
  /**
   * 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) {
    MetricContext metricContext = this.metricContext.get();
    if (metricContext != null) {
      metricContext.removeFromMetrics(this.contextAwareMetrics.get(name).getContextAwareMetric());
    }
    return this.contextAwareMetrics.remove(name) != null && removeChildrenMetrics(name);
  }
  @Override
  public void removeMatching(MetricFilter filter) {
    for (Map.Entry entry : this.contextAwareMetrics.entrySet()) {
      if (filter.matches(entry.getKey(), entry.getValue().getContextAwareMetric())) {
        remove(entry.getKey());
      }
    }
  }
  @Override
  public List> getTags() {
    return this.tagged.getTags();
  }
  @Override
  public Map getTagMap() {
    return this.tagged.getTagMap();
  }
  @Override
  public void close() throws IOException {
    this.closer.close();
  }
  private SortedSet getSimpleNames() {
    return ImmutableSortedSet.copyOf(this.contextAwareMetrics.keySet());
  }
  private Map getSimplyNamedMetrics() {
    return ImmutableMap. copyOf(this.contextAwareMetrics);
  }
  @SuppressWarnings("unchecked")
  private  SortedMap getSimplyNamedMetrics(Class mClass,
      Optional filter) {
    ImmutableSortedMap.Builder builder = ImmutableSortedMap.naturalOrder();
    for (Map.Entry entry : this.contextAwareMetrics.entrySet()) {
      if (mClass.isInstance(entry.getValue())) {
        if (filter.isPresent() && !filter.get().matches(entry.getKey(), entry.getValue().getContextAwareMetric())) {
          continue;
        }
        builder.put(entry.getKey(), (T) entry.getValue());
      }
    }
    return builder.build();
  }
  @SuppressWarnings("unchecked")
  protected synchronized  T getOrCreate(String name,
      ContextAwareMetricFactory factory) {
    InnerMetric metric = this.contextAwareMetrics.get(name);
    if (metric != null) {
      if (factory.isInstance(metric)) {
        return (T) metric.getContextAwareMetric();
      }
      throw new IllegalArgumentException(name + " is already used for a different type of metric");
    }
    T newMetric = factory.newMetric(this.metricContext.get(), name);
    this.register(name, newMetric);
    return newMetric;
  }
  private boolean removeChildrenMetrics(String name) {
    boolean removed = true;
    for (MetricContext child : getChildContextsAsMap().values()) {
      if (!child.remove(name)) {
        removed = false;
      }
    }
    return removed;
  }
  @Override
  public String toString() {
    StringBuilder stringBuilder = new StringBuilder();
    stringBuilder.append("InnerMetricContext Name: ");
    stringBuilder.append(this.name);
    if (this.getParent().isPresent()) {
      stringBuilder.append(", Parent Name: ");
      stringBuilder.append(this.getParent().get().getName());
    } else {
      stringBuilder.append(", No Parent Context");
    }
    stringBuilder.append(", Number of Children: ");
    stringBuilder.append(this.getChildContextsAsMap().size());
    stringBuilder.append(", Tags: ");
    stringBuilder.append(Joiner.on(", ").withKeyValueSeparator(" : ").useForNull("NULL").join(this.getTagMap()));
    return stringBuilder.toString();
  }
}