2017-07-10Home
Recently at work, we need to collect the metrics of users accessing HDFS, e.g. how many times a user has read a file, to help users adjust their storage policies (For more on background, please refer to HDFS-7343 and Smart Storage Management). Unfortunately, that is not available in the existing (Hadoop 2.7.3) metrics which means we have to hack NameNode (who knows it when users access HDFS files) ourselves. Fortunately, we don't have to start from scratch since Hadoop already provides a pluggable metrics framework, Hadoop Metrics2,
The framework provides a variety of ways to implement metrics instrumentation easily via the simple MetricsSource interface or the even simpler and more concise and declarative metrics annotations. The consumers of metrics just need to implement the simple MetricsSink interface. Producers register the metrics sources with a metrics system, while consumers register the sinks. A default metrics system is provided to marshal metrics from sources to sinks based on (per source/sink) configuration options...
Specifically for our requirement, we need to
MetricsSource inside NameNode that record users accessing file.MetricsSink that write the metrics somewhere (e.g. HDFS).The default metrics system is a singleton so that we have to add our MetricsSource into the existing NameNodeMetrics.
Here is the big picture of the metrics flow. The blue parts are already provided while the red parts should be implemented.
{:height="200px" width="650px"}
As said in the doc, there are two ways to write a MetricsSource. The simpler and more limited one is @Metrics annotation.
@Metrics annotationFor example, the NameNodeMetrics, where@Metric is used to indict a metrics source.
@Metrics(name="NameNodeActivity", about="NameNode metrics", context="dfs")
public class NameNodeMetrics {
...
@Metric MutableCounterLong createFileOps;
@Metric MutableCounterLong filesCreated;
@Metric MutableCounterLong filesAppended;
FileAccessMetrics fileAccessMetrics;
...
}
The limitation is the class must have at least one @Metric field, whose class has to extend MutableMetric. Other than that, we are free to add any MetricsSource, for instance, the FileAccessMetrics we are going to implement.
MetricsSourcepublic class FileAccessMetrics implements MetricsSource {
public static final String NAME = "FileAccessMetrics";
public static final String DESC = "FileAccessMetrics";
public static final String CONTEXT_VALUE ="file_access";
private List<Info> infos = new LinkedList<>();
public static FileAccessMetrics create(MetricsSystem ms) {
return ms.register(NAME, DESC, new FileAccessMetrics());
}
public synchronized void addMetrics(String path, String user, long time) {
infos.add(new Info(path, user, time));
}
@Override
public void getMetrics(MetricsCollector collector, boolean all) {
for (Info info: infos) {
MetricsRecordBuilder rb = collector.addRecord(info).setContext(CONTEXT_VALUE);
rb.addGauge(info, 1);
}
infos.clear();
}
Distilling it,
NameNodeMetrics will create this FileAccessMetrics.DFSClient opens a read call, NameNode will addMetrics(Info(path, user, time) to the list.MetricsSystem will periodically getMetrics from the list and put onto its internal queue.MetricsRecordBuilder expect a numerical value so we do a small trick by storing the Info into the record name and setting the value to 1.CONTEXT_VALUE will be used later to identify the record for write.Periodically, MetricsSystem will poll its internal queue
and putMetrics to MetricsSink, which can write it out as follows.
public class FileAccessMetrics implements MetricsSource {
...
public static class Writer implements MetricsSink, Closeable {
...
@Override
public void putMetrics(MetricsRecord record) {
...
for (AbstractMetric metric : record.metrics()) {
currentOutStream.print(metric.name() + ":" + metric.value());
}
...
}
...
}
...
}
Note that Hadoop 3.x already packs a bunch of useful sinks
Put the following configurations into either hadoop-metrics2-namenode.properties or hadoop-metrics2.properties.
namenode.sink.file_access.class=org.apache.hadoop.hdfs.server.namenode.metrics.FileAccessMetrics$Writer
namenode.sink.file_access.context=file_access
namenode is the prefix with which NameNodeMetrics initialize the metrics system.hadoop-metrics2-[prefix].properties and fall back to hadoop-metrics2.properties if not found.FileAccessMetrics$CONTEXT_VALUE such that MetricsSystem are able to filter out other NameNodeMetrics sources and only send FileAccessMetrics to our sink.This post describes the architecture and usage of Hadoop metrics2 through an example to instrument user accessing HDFS files. However, I cannot cover all the features since I haven't tried them out myself so please refer to the official documentation.