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 }