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    
019    package org.apache.hadoop.metrics2.sink.ganglia;
020    
021    import java.io.IOException;
022    import java.net.DatagramPacket;
023    import java.net.DatagramSocket;
024    import java.net.SocketAddress;
025    import java.net.SocketException;
026    import java.net.UnknownHostException;
027    import java.util.HashMap;
028    import java.util.List;
029    import java.util.Map;
030    
031    import org.apache.commons.configuration.SubsetConfiguration;
032    import org.apache.commons.logging.Log;
033    import org.apache.commons.logging.LogFactory;
034    import org.apache.hadoop.metrics2.MetricsSink;
035    import org.apache.hadoop.metrics2.util.Servers;
036    import 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     */
045    public 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    }