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 }