/*
* 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.reporter;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.concurrent.TimeUnit;
import lombok.Getter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.codahale.metrics.Counter;
import com.codahale.metrics.Gauge;
import com.codahale.metrics.Histogram;
import com.codahale.metrics.Meter;
import com.codahale.metrics.Timer;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.common.io.Closer;
import com.typesafe.config.Config;
import gobblin.metrics.MetricContext;
import gobblin.metrics.Tag;
/**
* Scheduled reporter with metrics reporting configuration.
*
* <p>
* Concrete reporters need to subclass this class and implement {@link ScheduledReporter#report(SortedMap , SortedMap, SortedMap, SortedMap, SortedMap, Map)}
* that emits the metrics in the desired (textual/serialized) form.
* </p>
*
* @author Lorand Bendig
*
*/
public abstract class ConfiguredScheduledReporter extends ScheduledReporter {
private static final Logger LOGGER = LoggerFactory.getLogger(ConfiguredScheduledReporter.class);
private static final String FINAL_TAG_KEY = "finalReport";
@Getter
private final TimeUnit rateUnit;
@Getter
private final TimeUnit durationUnit;
private final double rateFactor;
private final double durationFactor;
protected final ImmutableMap<String, String> tags;
protected final Closer closer;
protected final String metricContextName;
protected static final Joiner JOINER = Joiner.on('.').skipNulls();
public ConfiguredScheduledReporter(Builder<?> builder, Config config) {
super(builder.name, config);
this.rateUnit = builder.rateUnit;
this.durationUnit = builder.durationUnit;
this.rateFactor = builder.rateUnit.toSeconds(1);
this.durationFactor = 1.0 / builder.durationUnit.toNanos(1);
this.tags = ImmutableMap.copyOf(builder.tags);
this.closer = Closer.create();
this.metricContextName = builder.metricContextName;
}
/**
* Builder for {@link ConfiguredScheduledReporter}. Defaults to no filter, reporting rates in seconds and times in
* milliseconds.
*/
public static abstract class Builder<T extends Builder<T>> {
protected String name;
protected TimeUnit rateUnit;
protected TimeUnit durationUnit;
protected Map<String, String> tags;
protected String metricContextName;
protected Builder() {
this.name = "ConfiguredScheduledReporter";
this.rateUnit = TimeUnit.SECONDS;
this.durationUnit = TimeUnit.MILLISECONDS;
this.tags = Maps.newHashMap();
}
protected abstract T self();
/**
* Set the name of the reporter
*
* @param name name of the metric reporter
* @return {@code this}
*/
public T name(String name) {
this.name = name;
return self();
}
/**
* Convert rates to the given time unit.
*
* @param rateUnit a unit of time
* @return {@code this}
*/
public T convertRatesTo(TimeUnit rateUnit) {
this.rateUnit = rateUnit;
return self();
}
/**
* Convert durations to the given time unit.
*
* @param durationUnit a unit of time
* @return {@code this}
*/
public T convertDurationsTo(TimeUnit durationUnit) {
this.durationUnit = durationUnit;
return self();
}
/**
* Add tags
* @param tags additional {@link gobblin.metrics.Tag}s for the reporter.
* @return {@code this}
*/
public T withTags(Map<String, String> tags) {
this.tags.putAll(tags);
return self();
}
/**
* Add tags.
* @param tags List of {@link gobblin.metrics.Tag}
* @return {@code this}
*/
public T withTags(List<Tag<?>> tags) {
for(Tag<?> tag : tags) {
this.tags.put(tag.getKey(), tag.getValue().toString());
}
return self();
}
/**
* Add tag.
* @param key tag key
* @param value tag value
* @return {@code this}
*/
public T withTag(String key, String value) {
this.tags.put(key, value);
return self();
}
/**
* Add the name of the base metrics context as prefix to the metric keys
*
* @param metricContextName name of the metrics context
* @return {@code this}
*/
public T withMetricContextName(String metricContextName) {
this.metricContextName = metricContextName;
return self();
}
}
@Override
protected void report(SortedMap<String, Gauge> gauges, SortedMap<String, Counter> counters,
SortedMap<String, Histogram> histograms, SortedMap<String, Meter> meters, SortedMap<String, Timer> timers,
Map<String, Object> tags, boolean isFinal) {
if (isFinal) {
report(gauges, counters, histograms, meters, timers,
ImmutableMap.<String, Object>builder().putAll(tags).put(FINAL_TAG_KEY, Boolean.TRUE).build());
} else {
report(gauges, counters, histograms, meters, timers, tags);
}
}
protected double convertDuration(double duration) {
return duration * this.durationFactor;
}
protected double convertRate(double rate) {
return rate * this.rateFactor;
}
/**
* Constructs the prefix of metric key to be emitted.
* Enriches {@link ConfiguredScheduledReporter#metricContextName} with the current task id and fork id to
* be able to identify the emitted metric by its origin
*
* @param tags
* @return Prefix of the metric key
*/
protected String getMetricNamePrefix(Map<String, Object> tags){
String currentContextName = (String) tags.get(MetricContext.METRIC_CONTEXT_NAME_TAG_NAME);
if (metricContextName == null || (currentContextName.indexOf(metricContextName) > -1)) {
return currentContextName;
}
return JOINER.join(metricContextName, tags.get("taskId"), tags.get("forkBranchName"), tags.get("class"));
}
@Override
public void close() throws IOException {
try {
this.closer.close();
} catch(Exception e) {
LOGGER.warn("Exception when closing ConfiguredScheduledReporter", e);
} finally {
super.close();
}
}
}