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.sink.ganglia;
020
021import java.io.IOException;
022import java.net.DatagramPacket;
023import java.net.DatagramSocket;
024import java.net.SocketAddress;
025import java.net.SocketException;
026import java.net.UnknownHostException;
027import java.util.HashMap;
028import java.util.List;
029import java.util.Map;
030
031import org.apache.commons.configuration.SubsetConfiguration;
032import org.apache.commons.logging.Log;
033import org.apache.commons.logging.LogFactory;
034import org.apache.hadoop.metrics2.MetricsSink;
035import org.apache.hadoop.metrics2.util.Servers;
036import org.apache.hadoop.net.DNS;
037
038/**
039 * This the base class for Ganglia sink classes using metrics2. Lot of the code
040 * has been derived from org.apache.hadoop.metrics.ganglia.GangliaContext.
041 * As per the documentation, sink implementations doesn't have to worry about
042 * thread safety. Hence the code wasn't written for thread safety and should
043 * be modified in case the above assumption changes in the future.
044 */
045public abstract class AbstractGangliaSink implements MetricsSink {
046
047  public final Log LOG = LogFactory.getLog(this.getClass());
048
049  /*
050   * Output of "gmetric --help" showing allowable values
051   * -t, --type=STRING
052   *     Either string|int8|uint8|int16|uint16|int32|uint32|float|double
053   * -u, --units=STRING Unit of measure for the value e.g. Kilobytes, Celcius
054   *     (default='')
055   * -s, --slope=STRING Either zero|positive|negative|both
056   *     (default='both')
057   * -x, --tmax=INT The maximum time in seconds between gmetric calls
058   *     (default='60')
059   */
060  public static final String DEFAULT_UNITS = "";
061  public static final int DEFAULT_TMAX = 60;
062  public static final int DEFAULT_DMAX = 0;
063  public static final GangliaSlope DEFAULT_SLOPE = GangliaSlope.both;
064  public static final int DEFAULT_PORT = 8649;
065  public static final String SERVERS_PROPERTY = "servers";
066  public static final int BUFFER_SIZE = 1500; // as per libgmond.c
067  public static final String SUPPORT_SPARSE_METRICS_PROPERTY = "supportsparse";
068  public static final boolean SUPPORT_SPARSE_METRICS_DEFAULT = false;
069  public static final String EQUAL = "=";
070
071  private String hostName = "UNKNOWN.example.com";
072  private DatagramSocket datagramSocket;
073  private List<? extends SocketAddress> metricsServers;
074  private byte[] buffer = new byte[BUFFER_SIZE];
075  private int offset;
076  private boolean supportSparseMetrics = SUPPORT_SPARSE_METRICS_DEFAULT;
077
078  /**
079   * Used for visiting Metrics
080   */
081  protected final GangliaMetricVisitor gangliaMetricVisitor =
082    new GangliaMetricVisitor();
083
084  private SubsetConfiguration conf;
085  private Map<String, GangliaConf> gangliaConfMap;
086  private GangliaConf DEFAULT_GANGLIA_CONF = new GangliaConf();
087
088  /**
089   * ganglia slope values which equal the ordinal
090   */
091  public enum GangliaSlope {
092    zero,       // 0
093    positive,   // 1
094    negative,   // 2
095    both        // 3
096  };
097
098  /**
099   * define enum for various type of conf
100   */
101  public enum GangliaConfType {
102    slope, units, dmax, tmax
103  };
104
105  /*
106   * (non-Javadoc)
107   *
108   * @see
109   * org.apache.hadoop.metrics2.MetricsPlugin#init(org.apache.commons.configuration
110   * .SubsetConfiguration)
111   */
112  public void init(SubsetConfiguration conf) {
113    LOG.debug("Initializing the GangliaSink for Ganglia metrics.");
114
115    this.conf = conf;
116
117    // Take the hostname from the DNS class.
118    if (conf.getString("slave.host.name") != null) {
119      hostName = conf.getString("slave.host.name");
120    } else {
121      try {
122        hostName = DNS.getDefaultHost(
123            conf.getString("dfs.datanode.dns.interface", "default"),
124            conf.getString("dfs.datanode.dns.nameserver", "default"));
125      } catch (UnknownHostException uhe) {
126        LOG.error(uhe);
127        hostName = "UNKNOWN.example.com";
128      }
129    }
130
131    // load the gannglia servers from properties
132    metricsServers = Servers.parse(conf.getString(SERVERS_PROPERTY),
133        DEFAULT_PORT);
134
135    // extract the Ganglia conf per metrics
136    gangliaConfMap = new HashMap<String, GangliaConf>();
137    loadGangliaConf(GangliaConfType.units);
138    loadGangliaConf(GangliaConfType.tmax);
139    loadGangliaConf(GangliaConfType.dmax);
140    loadGangliaConf(GangliaConfType.slope);
141
142    try {
143      datagramSocket = new DatagramSocket();
144    } catch (SocketException se) {
145      LOG.error(se);
146    }
147
148    // see if sparseMetrics is supported. Default is false
149    supportSparseMetrics = conf.getBoolean(SUPPORT_SPARSE_METRICS_PROPERTY,
150        SUPPORT_SPARSE_METRICS_DEFAULT);
151  }
152
153  /*
154   * (non-Javadoc)
155   *
156   * @see org.apache.hadoop.metrics2.MetricsSink#flush()
157   */
158  public void flush() {
159    // nothing to do as we are not buffering data
160  }
161
162  // Load the configurations for a conf type
163  private void loadGangliaConf(GangliaConfType gtype) {
164    String propertyarr[] = conf.getStringArray(gtype.name());
165    if (propertyarr != null && propertyarr.length > 0) {
166      for (String metricNValue : propertyarr) {
167        String metricNValueArr[] = metricNValue.split(EQUAL);
168        if (metricNValueArr.length != 2 || metricNValueArr[0].length() == 0) {
169          LOG.error("Invalid propertylist for " + gtype.name());
170        }
171
172        String metricName = metricNValueArr[0].trim();
173        String metricValue = metricNValueArr[1].trim();
174        GangliaConf gconf = gangliaConfMap.get(metricName);
175        if (gconf == null) {
176          gconf = new GangliaConf();
177          gangliaConfMap.put(metricName, gconf);
178        }
179
180        switch (gtype) {
181        case units:
182          gconf.setUnits(metricValue);
183          break;
184        case dmax:
185          gconf.setDmax(Integer.parseInt(metricValue));
186          break;
187        case tmax:
188          gconf.setTmax(Integer.parseInt(metricValue));
189          break;
190        case slope:
191          gconf.setSlope(GangliaSlope.valueOf(metricValue));
192          break;
193        }
194      }
195    }
196  }
197
198  /**
199   * Lookup GangliaConf from cache. If not found, return default values
200   *
201   * @param metricName
202   * @return looked up GangliaConf
203   */
204  protected GangliaConf getGangliaConfForMetric(String metricName) {
205    GangliaConf gconf = gangliaConfMap.get(metricName);
206
207    return gconf != null ? gconf : DEFAULT_GANGLIA_CONF;
208  }
209
210  /**
211   * @return the hostName
212   */
213  protected String getHostName() {
214    return hostName;
215  }
216
217  /**
218   * Puts a string into the buffer by first writing the size of the string as an
219   * int, followed by the bytes of the string, padded if necessary to a multiple
220   * of 4.
221   * @param s the string to be written to buffer at offset location
222   */
223  protected void xdr_string(String s) {
224    byte[] bytes = s.getBytes();
225    int len = bytes.length;
226    xdr_int(len);
227    System.arraycopy(bytes, 0, buffer, offset, len);
228    offset += len;
229    pad();
230  }
231
232  // Pads the buffer with zero bytes up to the nearest multiple of 4.
233  private void pad() {
234    int newOffset = ((offset + 3) / 4) * 4;
235    while (offset < newOffset) {
236      buffer[offset++] = 0;
237    }
238  }
239
240  /**
241   * Puts an integer into the buffer as 4 bytes, big-endian.
242   */
243  protected void xdr_int(int i) {
244    buffer[offset++] = (byte) ((i >> 24) & 0xff);
245    buffer[offset++] = (byte) ((i >> 16) & 0xff);
246    buffer[offset++] = (byte) ((i >> 8) & 0xff);
247    buffer[offset++] = (byte) (i & 0xff);
248  }
249
250  /**
251   * Sends Ganglia Metrics to the configured hosts
252   * @throws IOException
253   */
254  protected void emitToGangliaHosts() throws IOException {
255    try {
256      for (SocketAddress socketAddress : metricsServers) {
257        DatagramPacket packet =
258          new DatagramPacket(buffer, offset, socketAddress);
259        datagramSocket.send(packet);
260      }
261    } finally {
262      // reset the buffer for the next metric to be built
263      offset = 0;
264    }
265  }
266
267  /**
268   * Reset the buffer for the next metric to be built
269   */
270  void resetBuffer() {
271    offset = 0;
272  }
273
274  /**
275   * @return whether sparse metrics are supported
276   */
277  protected boolean isSupportSparseMetrics() {
278    return supportSparseMetrics;
279  }
280
281  /**
282   * Used only by unit test
283   * @param datagramSocket the datagramSocket to set.
284   */
285  void setDatagramSocket(DatagramSocket datagramSocket) {
286    this.datagramSocket = datagramSocket;
287  }
288}