001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019package org.apache.hadoop.metrics2.util;
020
021import java.util.Collection;
022import java.util.LinkedHashMap;
023import java.util.Map;
024import java.util.Set;
025
026import org.apache.commons.logging.Log;
027import org.apache.commons.logging.LogFactory;
028import org.apache.hadoop.classification.InterfaceAudience;
029import org.apache.hadoop.classification.InterfaceStability;
030import org.apache.hadoop.metrics2.AbstractMetric;
031import org.apache.hadoop.metrics2.MetricsRecord;
032import org.apache.hadoop.metrics2.MetricsTag;
033
034import com.google.common.base.Objects;
035import com.google.common.collect.Maps;
036
037/**
038 * A metrics cache for sinks that don't support sparse updates.
039 */
040@InterfaceAudience.Public
041@InterfaceStability.Evolving
042public class MetricsCache {
043  static final Log LOG = LogFactory.getLog(MetricsCache.class);
044  static final int MAX_RECS_PER_NAME_DEFAULT = 1000;
045
046  private final Map<String, RecordCache> map = Maps.newHashMap();
047  private final int maxRecsPerName;
048
049  class RecordCache
050      extends LinkedHashMap<Collection<MetricsTag>, Record> {
051    private static final long serialVersionUID = 1L;
052    private boolean gotOverflow = false;
053
054    @Override
055    protected boolean removeEldestEntry(Map.Entry<Collection<MetricsTag>,
056                                                  Record> eldest) {
057      boolean overflow = size() > maxRecsPerName;
058      if (overflow && !gotOverflow) {
059        LOG.warn("Metrics cache overflow at "+ size() +" for "+ eldest);
060        gotOverflow = true;
061      }
062      return overflow;
063    }
064  }
065
066  /**
067   * Cached record
068   */
069  public static class Record {
070    final Map<String, String> tags = Maps.newHashMap();
071    final Map<String, AbstractMetric> metrics = Maps.newHashMap();
072
073    /**
074     * Lookup a tag value
075     * @param key name of the tag
076     * @return the tag value
077     */
078    public String getTag(String key) {
079      return tags.get(key);
080    }
081
082    /**
083     * Lookup a metric value
084     * @param key name of the metric
085     * @return the metric value
086     */
087    public Number getMetric(String key) {
088      AbstractMetric metric = metrics.get(key);
089      return metric != null ? metric.value() : null;
090    }
091
092    /**
093     * Lookup a metric instance
094     * @param key name of the metric
095     * @return the metric instance
096     */
097    public AbstractMetric getMetricInstance(String key) {
098      return metrics.get(key);
099    }
100
101    /**
102     * @return the entry set of the tags of the record
103     */
104    public Set<Map.Entry<String, String>> tags() {
105      return tags.entrySet();
106    }
107
108    /**
109     * @deprecated use metricsEntrySet() instead
110     * @return entry set of metrics
111     */
112    @Deprecated
113    public Set<Map.Entry<String, Number>> metrics() {
114      Map<String, Number> map = new LinkedHashMap<String, Number>(
115          metrics.size());
116      for (Map.Entry<String, AbstractMetric> mapEntry : metrics.entrySet()) {
117        map.put(mapEntry.getKey(), mapEntry.getValue().value());
118      }
119      return map.entrySet();
120    }
121
122    /**
123     * @return entry set of metrics
124     */
125    public Set<Map.Entry<String, AbstractMetric>> metricsEntrySet() {
126      return metrics.entrySet();
127    }
128
129    @Override public String toString() {
130      return Objects.toStringHelper(this)
131          .add("tags", tags).add("metrics", metrics)
132          .toString();
133    }
134  }
135
136  public MetricsCache() {
137    this(MAX_RECS_PER_NAME_DEFAULT);
138  }
139
140  /**
141   * Construct a metrics cache
142   * @param maxRecsPerName  limit of the number records per record name
143   */
144  public MetricsCache(int maxRecsPerName) {
145    this.maxRecsPerName = maxRecsPerName;
146  }
147
148  /**
149   * Update the cache and return the current cached record
150   * @param mr the update record
151   * @param includingTags cache tag values (for later lookup by name) if true
152   * @return the updated cache record
153   */
154  public Record update(MetricsRecord mr, boolean includingTags) {
155    String name = mr.name();
156    RecordCache recordCache = map.get(name);
157    if (recordCache == null) {
158      recordCache = new RecordCache();
159      map.put(name, recordCache);
160    }
161    Collection<MetricsTag> tags = mr.tags();
162    Record record = recordCache.get(tags);
163    if (record == null) {
164      record = new Record();
165      recordCache.put(tags, record);
166    }
167    for (AbstractMetric m : mr.metrics()) {
168      record.metrics.put(m.name(), m);
169    }
170    if (includingTags) {
171      // mostly for some sinks that include tags as part of a dense schema
172      for (MetricsTag t : mr.tags()) {
173        record.tags.put(t.name(), t.value());
174      }
175    }
176    return record;
177  }
178
179  /**
180   * Update the cache and return the current cache record
181   * @param mr the update record
182   * @return the updated cache record
183   */
184  public Record update(MetricsRecord mr) {
185    return update(mr, false);
186  }
187
188  /**
189   * Get the cached record
190   * @param name of the record
191   * @param tags of the record
192   * @return the cached record or null
193   */
194  public Record get(String name, Collection<MetricsTag> tags) {
195    RecordCache rc = map.get(name);
196    if (rc == null) return null;
197    return rc.get(tags);
198  }
199}