PageRenderTime 52ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/trunk/flume-core/src/main/java/com/cloudera/flume/reporter/ganglia/GangliaSink.java

#
Java | 338 lines | 201 code | 36 blank | 101 comment | 24 complexity | 06cc2964e6109b1a0c5210d42cea0b21 MD5 | raw file
Possible License(s): Apache-2.0
  1. /**
  2. * Licensed to Cloudera, Inc. under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. Cloudera, Inc. licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. */
  18. /*
  19. * Licensed to the Apache Software Foundation (ASF) under one
  20. * or more contributor license agreements. See the NOTICE file
  21. * distributed with this work for additional information
  22. * regarding copyright ownership. The ASF licenses this file
  23. * to you under the Apache License, Version 2.0 (the
  24. * "License"); you may not use this file except in compliance
  25. * with the License. You may obtain a copy of the License at
  26. *
  27. * http://www.apache.org/licenses/LICENSE-2.0
  28. *
  29. * Unless required by applicable law or agreed to in writing, software
  30. * distributed under the License is distributed on an "AS IS" BASIS,
  31. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  32. * See the License for the specific language governing permissions and
  33. * limitations under the License.
  34. */
  35. package com.cloudera.flume.reporter.ganglia;
  36. import java.io.IOException;
  37. import java.net.DatagramPacket;
  38. import java.net.DatagramSocket;
  39. import java.net.SocketAddress;
  40. import java.net.SocketException;
  41. import java.util.HashMap;
  42. import java.util.List;
  43. import java.util.Map;
  44. import org.apache.hadoop.metrics.spi.Util;
  45. import org.slf4j.Logger;
  46. import org.slf4j.LoggerFactory;
  47. import com.cloudera.flume.conf.Context;
  48. import com.cloudera.flume.conf.FlumeConfiguration;
  49. import com.cloudera.flume.conf.SinkFactory.SinkBuilder;
  50. import com.cloudera.flume.core.Attributes;
  51. import com.cloudera.flume.core.Event;
  52. import com.cloudera.flume.core.EventSink;
  53. import com.cloudera.flume.core.Attributes.Type;
  54. import com.cloudera.util.NetUtils;
  55. import com.google.common.base.Preconditions;
  56. /**
  57. * This sink takes a specified list of ganglia gmond servers, a event attribute
  58. * (from a report/event), a units name, and a Flume attribute type, and emits
  59. * XDR formatted UDP packets so that the metric can be seen in Ganglia. This
  60. * specifically supports the ganglia >=3.1.x wire format.
  61. *
  62. * Originally based on o.a.h.metrics.ganglia.GangliaContext31 / HADOOP-4675
  63. *
  64. * XDR spec http://www.faqs.org/rfcs/rfc1832.html
  65. *
  66. * This is not thread safe.
  67. */
  68. public class GangliaSink extends EventSink.Base {
  69. private static final String DEFAULT_GROUP = "flume";
  70. private static final int DEFAULT_TMAX = 60;
  71. private static final int DEFAULT_DMAX = 0;
  72. private static final int DEFAULT_PORT = 8649;
  73. private static final int BUFFER_SIZE = 1500; // as per libgmond.c
  74. private static final Logger LOG = LoggerFactory.getLogger(GangliaSink.class);
  75. private final String servers;
  76. private static final Map<Type, String> typeTable = new HashMap<Type, String>() {
  77. private static final long serialVersionUID = 1L;
  78. {
  79. put(Type.STRING, "string");
  80. put(Type.INT, "int32");
  81. put(Type.LONG, "float"); // Conversion happens here, originally from
  82. // hadoop source
  83. put(Type.DOUBLE, "double");
  84. }
  85. };
  86. private byte[] buffer = new byte[BUFFER_SIZE];
  87. private int offset;
  88. private List<? extends SocketAddress> metricsServers;
  89. private DatagramSocket datagramSocket;
  90. final private String attr; // turns into the metric name.
  91. final private String units;
  92. final private Type type;
  93. /** Creates a new instance of GangliaContext */
  94. public GangliaSink(String gangliaSvrs, String attr, String units, Type t) {
  95. this.servers = gangliaSvrs;
  96. this.attr = attr;
  97. this.units = units;
  98. this.type = t;
  99. }
  100. /**
  101. * This currently only outputs one metric from a report, and relies on the
  102. * sink constructor to give details about the typing information.
  103. *
  104. * This may become moot if typing information is provided by flume's data.
  105. */
  106. @Override
  107. public void append(Event e) throws IOException, InterruptedException {
  108. String value;
  109. switch (type) {
  110. case LONG: {
  111. Long l = Attributes.readLong(e, attr);
  112. if (l == null) {
  113. // attribute not present, drop
  114. return;
  115. }
  116. value = l.toString();
  117. break;
  118. }
  119. case INT: {
  120. Integer i = Attributes.readInt(e, attr);
  121. if (i == null) {
  122. // attribute not present, drop
  123. return;
  124. }
  125. value = i.toString();
  126. break;
  127. }
  128. case STRING: {
  129. String s = Attributes.readString(e, attr);
  130. if (s == null) {
  131. // attribute not present, drop
  132. return;
  133. }
  134. value = s;
  135. break;
  136. }
  137. case DOUBLE: {
  138. Double d = Attributes.readDouble(e, attr);
  139. if (d == null) {
  140. // attribute not present,drop
  141. return;
  142. }
  143. value = d.toString();
  144. break;
  145. }
  146. default:
  147. return;
  148. }
  149. emitMetric(attr, typeTable.get(type), value, units);
  150. super.append(e);
  151. }
  152. @Override
  153. public void open() throws IOException, InterruptedException {
  154. // TODO (jon) need to worry about SecurityException and other
  155. // RuntimeExceptions.
  156. // From o.a.h.metrics.spi.Util
  157. // This can throw IllegalArgumentException or SecurityException
  158. metricsServers = Util.parse(servers, DEFAULT_PORT);
  159. try {
  160. datagramSocket = new DatagramSocket();
  161. } catch (SocketException se) {
  162. LOG.warn("problem with ganglia socket", se);
  163. }
  164. }
  165. /**
  166. * Not thread safe
  167. */
  168. @Override
  169. public void close() throws IOException, InterruptedException {
  170. if (datagramSocket == null) {
  171. LOG.warn("Double close");
  172. return;
  173. }
  174. datagramSocket.close();
  175. datagramSocket = null;
  176. }
  177. // //////////////////////////////////////////////////////////////////
  178. // This is ganglia >= 3.1.x wire format. Basically ripped out of
  179. // HADOOP-4675
  180. /**
  181. * This takes a metric and then send it off to the listening ganglia gmond
  182. * ports via udp.
  183. *
  184. * This is not thread safe.
  185. */
  186. private void emitMetric(String name, String type, String value, String units)
  187. throws IOException {
  188. int slope = 3; // see gmetric.c
  189. int tmax = DEFAULT_TMAX;
  190. int dmax = DEFAULT_DMAX;
  191. String hostName = NetUtils.localhost();
  192. // First we send out a metadata message
  193. xdr_int(128); // metric_id = metadata_msg
  194. xdr_string(hostName); // hostname
  195. xdr_string(name); // metric name
  196. xdr_int(0); // spoof = False
  197. xdr_string(type); // metric type
  198. xdr_string(name); // metric name
  199. xdr_string(units); // units
  200. xdr_int(slope); // slope
  201. xdr_int(tmax); // tmax, the maximum time between metrics
  202. xdr_int(dmax); // dmax, the maximum data value
  203. xdr_int(1); // ???
  204. /*
  205. * Num of the entries in extra_value field for Ganglia 3.1.x
  206. */
  207. xdr_string("GROUP"); /* Group attribute */
  208. xdr_string(DEFAULT_GROUP); /* Group value */
  209. // send to each ganglia metrics listener/server
  210. for (SocketAddress socketAddress : metricsServers) {
  211. DatagramPacket packet = new DatagramPacket(buffer, offset, socketAddress);
  212. datagramSocket.send(packet);
  213. }
  214. // Now we send out a message with the actual value.
  215. // Technically, we only need to send out the metadata message once for
  216. // each metric, but I don't want to have to record which metrics we did and
  217. // did not send.
  218. offset = 0;
  219. xdr_int(133); // we are sending a string value
  220. xdr_string(hostName); // hostName
  221. xdr_string(name); // metric name
  222. xdr_int(0); // spoof = False
  223. xdr_string("%s"); // format field
  224. xdr_string(value); // metric value
  225. for (SocketAddress socketAddress : metricsServers) {
  226. DatagramPacket packet = new DatagramPacket(buffer, offset, socketAddress);
  227. datagramSocket.send(packet);
  228. }
  229. }
  230. /**
  231. * Puts a string into the buffer by first writing the size of the string as an
  232. * int, followed by the bytes of the string, padded if necessary to a multiple
  233. * of 4.
  234. */
  235. private void xdr_string(String s) {
  236. byte[] bytes = s.getBytes();
  237. int len = bytes.length;
  238. xdr_int(len);
  239. System.arraycopy(bytes, 0, buffer, offset, len);
  240. offset += len;
  241. pad();
  242. }
  243. /**
  244. * Pads the buffer with zero bytes up to the nearest multiple of 4.
  245. *
  246. * Not thread safe
  247. */
  248. private void pad() {
  249. int newOffset = ((offset + 3) / 4) * 4;
  250. while (offset < newOffset) {
  251. buffer[offset++] = 0;
  252. }
  253. }
  254. /**
  255. * Puts an integer into the buffer as 4 bytes, big-endian.
  256. *
  257. * Not thread safe
  258. */
  259. private void xdr_int(int i) {
  260. buffer[offset++] = (byte) ((i >> 24) & 0xff);
  261. buffer[offset++] = (byte) ((i >> 16) & 0xff);
  262. buffer[offset++] = (byte) ((i >> 8) & 0xff);
  263. buffer[offset++] = (byte) (i & 0xff);
  264. }
  265. // end of rip
  266. // //////////////////////////////////////////////////////////////////
  267. public static SinkBuilder builder() {
  268. return new SinkBuilder() {
  269. @Override
  270. public EventSink build(Context context, String... argv) {
  271. Preconditions.checkArgument(argv.length >= 3 && argv.length <= 4,
  272. "usage: ganglia(\"attr\", \"units\",\"type\"[, \"gmondservers\"])");
  273. String ganglias = FlumeConfiguration.get().getGangliaServers(); // default
  274. String attr = argv[0];
  275. String units = argv[1];
  276. String type = argv[2];
  277. Type t = null;
  278. // These strings are flume types, not xdr types
  279. if (type.equals("int")) {
  280. t = Type.INT;
  281. } else if (type.equals("long")) {
  282. t = Type.LONG;
  283. } else if (type.equals("double")) {
  284. t = Type.DOUBLE;
  285. } else if (type.equals("string")) {
  286. t = Type.STRING;
  287. } else {
  288. throw new IllegalArgumentException(
  289. "Illegal ganglia xdr type: "
  290. + type
  291. + " != int|long|double|string\n"
  292. + "usage: ganglia(\"attr\", \"units\",\"type\"[, \"gmondservers\"]");
  293. }
  294. if (argv.length >= 4) {
  295. ganglias = argv[3];
  296. }
  297. EventSink snk = new GangliaSink(ganglias, attr, units, t);
  298. return snk;
  299. }
  300. };
  301. }
  302. }