/* * Copyright 2012 Nodeable Inc * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.streamreduce.storm.spouts; import backtype.storm.spout.SpoutOutputCollector; import backtype.storm.task.TopologyContext; import backtype.storm.topology.OutputFieldsDeclarer; import backtype.storm.topology.base.BaseRichSpout; import backtype.storm.tuple.Fields; import backtype.storm.tuple.Values; import com.streamreduce.analytics.MetricName; import com.streamreduce.core.metric.MetricModeType; import org.apache.log4j.Logger; import org.mortbay.jetty.Request; import org.mortbay.jetty.Server; import org.mortbay.jetty.handler.AbstractHandler; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.*; import java.util.concurrent.ConcurrentLinkedQueue; /** * Extension of {@link BaseRichSpout} that embeds a Jetty server to accept * commands injected by HTTP. These commands are spread through the topology * and used for debugging and monitoring. * * Here's some examples that all do the same thing, they cause the "debug.clear" to reset * the stream in JuggaloaderTimeBase. * curl -i http://localhost:8194/?metricType=debug.clear --data "metricType=debug.clear" * curl -i http://localhost:8194/?metricType=debug.clear * curl -i http://localhost:8194/ --data "metricType=debug.clear" * curl -i http://localhost:8194/?metricType=debug.clear --data "metricType=debug.clearx" * # all above work, but this doesn't. the qs overrides the postdata * curl -i http://localhost:8194/?metricType=debug.clearx --data "metricType=debug.clear" * * # send values, check state * curl -i "http://localhost:8194/?metricName=TEST_STREAM.foo&metricValue=8.0" * curl -i "http://localhost:8194/?metricName=TEST_STREAM.foo&metricType=debug.state" * curl -i "http://localhost:8194/?metricName=TEST_STREAM.foo&metricType=debug.clear" * curl -i "http://localhost:8194/?metricName=metricType=debug.numstates" * curl -i "http://localhost:8194/?metricName=metricType=debug.clearall" * * # trigger anomaly: * curl -i "http://localhost:8194/?metricName=TEST_STREAM.foo&metricType=debug.set&name=n&value=100" * curl -i "http://localhost:8194/?metricName=TEST_STREAM.foo&metricType=debug.set&name=mean&value=45.6" * curl -i "http://localhost:8194/?metricName=TEST_STREAM.foo&metricType=debug.set&name=stddev&value=1.6" * curl -i "http://localhost:8194/?metricName=TEST_STREAM.foo&metricType=debug.state" * curl -i "http://localhost:8194/?metricName=TEST_STREAM.foo&metricType=debug.state&metricValue=500.0" # send a sample * * Commands supported: * debug.clear - clears the state of the specified stream from the bolt * debug.clearall - clears all states from the bolt * debug.state - dumps the state (mean, stddev, etc) of the specified stream * debug.numstates - dumps the number of states in memory for the bolt * debug.set - sets name ("mean", "stddev", "min", "max", "n") for the stream to number given by value * * Other commands need to be added to Juggaloader to be sent from here. */ public class JuggaloaderCommandSpout extends BaseRichSpout { private static final Logger logger = Logger.getLogger(JuggaloaderCommandSpout.class); static final ResourceBundle topologyProps = ResourceBundle.getBundle("juggaloader-topology"); private static final int JL_HTTP_COMMAND_PORT = Integer.parseInt(topologyProps.getString("juggaloader.commandspout.port"));; private static int seq = 0; private static ConcurrentLinkedQueue queue; private SpoutOutputCollector collector; public JuggaloaderCommandSpout() {} /** * {@inheritDoc} */ @Override public void open(Map map, TopologyContext topologyContext, SpoutOutputCollector spoutOutputCollector) { collector = spoutOutputCollector; } /** * {@inheritDoc} */ @Override public void nextTuple() { if (queue == null || queue.isEmpty()) { try { Thread.sleep(10); } catch (Exception e) { logger.error("Exception during sleep", e); } } else { Map<String, String> params = (Map<String, String>)queue.remove(); Map<String, String> nullCriteria = new HashMap<>(); collector.emit( new Values( params.containsKey("metricAccount") ? params.get("metricAccount") : "global", params.containsKey("metricName") ? params.get("metricName") : MetricName.TEST_STREAM.toString(), params.containsKey("metricType") ? params.get("metricType") : MetricModeType.ABSOLUTE.toString(), params.containsKey("metricTimestamp") ? Long.parseLong(params.get("metricTimestamp")) : System.currentTimeMillis(), params.containsKey("metricValue") ? Float.parseFloat(params.get("metricValue")) : 0.0f, nullCriteria, params ) ); // ack(entry); } } /** * {@inheritDoc} */ @Override public void declareOutputFields(OutputFieldsDeclarer outputFieldsDeclarer) { // added the default stream because storm complained outputFieldsDeclarer.declare( new Fields( "metricAccount", // The account the metric's value should be credited/stored in "metricName", // The metric's name "metricType", // The metric's type "metricTimestamp", // The metric's timestamp "metricValue", // The metric's value "metricCriteria", // key/value pairs used for querying and uniquing of the metric entry "metaData" // Metadata used downstream for generating nodebellys ) ); } /** * This is synchronized and statically called so only 1 worker * per node running this spout will create the Server. */ @SuppressWarnings("unused") //Presently not used because we may be running inside of an external jetty instance. public static synchronized void startEmbeddedHttpServer() { try { // Only the first thread that gets here will create the server and queue. if(queue != null) { return; } queue = new ConcurrentLinkedQueue<Map<String, String>>(); Server server = new Server(JL_HTTP_COMMAND_PORT); server.setHandler(new AbstractHandler() { @Override public void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) throws IOException, ServletException { Map<String, String> params = new HashMap<>(); Enumeration names = request.getParameterNames(); while(names.hasMoreElements()) { String key = (String) names.nextElement(); params.put(key, (String) request.getParameter(key)); } queue.add(params); response.setContentType("text/html;charset=utf-8"); response.setStatus(HttpServletResponse.SC_OK); response.getWriter().println(JuggaloaderCommandSpout.class.getName() + "." + Thread.currentThread().getId() + ", v0.1, " + seq); ((Request) request).setHandled(true); seq += 1; } }); server.start(); } catch (Exception e) { throw new RuntimeException(e); } } }