package resa.metrics; import backtype.storm.Config; import backtype.storm.hooks.BaseTaskHook; import backtype.storm.hooks.info.SpoutAckInfo; import backtype.storm.hooks.info.SpoutFailInfo; import backtype.storm.metric.api.MultiCountMetric; import backtype.storm.spout.SpoutOutputCollector; import backtype.storm.task.TopologyContext; import backtype.storm.topology.IRichSpout; import backtype.storm.utils.Utils; import resa.topology.DelegatedSpout; import resa.util.ConfigUtil; import resa.util.ResaConfig; import resa.util.Sampler; import java.util.List; import java.util.Map; import java.util.stream.Stream; /** * A measurable spout implementation based on system hook * <p> * Created by ding on 14-4-8. */ public class MeasurableSpout extends DelegatedSpout { private class MeasurableMsgId { final String stream; final Object msgId; final long startTime; private MeasurableMsgId(String stream, Object msgId, long startTime) { this.stream = stream; this.msgId = msgId; this.startTime = startTime; } public boolean isSampled() { return startTime > 0; } } private class SpoutHook extends BaseTaskHook { @Override public void spoutAck(SpoutAckInfo info) { MeasurableMsgId streamMsgId = (MeasurableMsgId) info.messageId; if (streamMsgId != null && streamMsgId.isSampled()) { long cost = System.currentTimeMillis() - streamMsgId.startTime; completeMetric.addMetric(streamMsgId.stream, cost); if (cost > qos) { missMetric.addMetric(streamMsgId.stream, cost); } if (completeStatMetric != null) { completeStatMetric.add(streamMsgId.stream, cost); } } } @Override public void spoutFail(SpoutFailInfo info) { MeasurableMsgId streamMsgId = (MeasurableMsgId) info.messageId; if (streamMsgId != null && streamMsgId.isSampled()) { if (completeStatMetric != null) { completeStatMetric.fail(streamMsgId.stream); } } } } private transient CMVMetric completeMetric; private Sampler sampler; private transient MultiCountMetric emitMetric; private transient CMVMetric missMetric; private transient CompleteStatMetric completeStatMetric; private long lastMetricsSent; private long qos; public MeasurableSpout(IRichSpout delegate) { super(delegate); } private long getMetricsDuration() { long now = System.currentTimeMillis(); long duration = now - lastMetricsSent; lastMetricsSent = now; return duration; } @Override public void open(Map conf, TopologyContext context, SpoutOutputCollector collector) { int interval = Utils.getInt(conf.get(Config.TOPOLOGY_BUILTIN_METRICS_BUCKET_SIZE_SECS)); completeMetric = context.registerMetric(MetricNames.COMPLETE_LATENCY, new CMVMetric(), interval); // register miss metric qos = ConfigUtil.getLong(conf, "resa.metric.complete-latency.threshold.ms", Long.MAX_VALUE); missMetric = context.registerMetric(MetricNames.MISS_QOS, new CMVMetric(), interval); emitMetric = context.registerMetric(MetricNames.EMIT_COUNT, new MultiCountMetric(), interval); // register stat metric double[] xAxis = Stream.of(((String) conf.getOrDefault("resa.metric.complete-latency.stat.x-axis", "")) .split(",")).filter(s -> !s.isEmpty()).mapToDouble(Double::parseDouble).toArray(); completeStatMetric = xAxis.length > 0 ? context.registerMetric(MetricNames.LATENCY_STAT, new CompleteStatMetric(xAxis), interval) : null; // register duration metric lastMetricsSent = System.currentTimeMillis(); context.registerMetric(MetricNames.DURATION, this::getMetricsDuration, interval); sampler = new Sampler(ConfigUtil.getDouble(conf, ResaConfig.COMP_SAMPLE_RATE, 0.05)); context.addTaskHook(new SpoutHook()); super.open(conf, context, new SpoutOutputCollector(collector) { @Override public List<Integer> emit(String streamId, List<Object> tuple, Object messageId) { return super.emit(streamId, tuple, newStreamMessageId(streamId, messageId)); } @Override public void emitDirect(int taskId, String streamId, List<Object> tuple, Object messageId) { super.emitDirect(taskId, streamId, tuple, newStreamMessageId(streamId, messageId)); } private MeasurableMsgId newStreamMessageId(String stream, Object messageId) { long startTime; if (sampler.shoudSample()) { startTime = System.currentTimeMillis(); emitMetric.scope(stream).incr(); } else { startTime = -1; } return messageId == null ? null : new MeasurableMsgId(stream, messageId, startTime); } }); } private Object getUserMsgId(Object msgId) { return msgId != null ? ((MeasurableMsgId) msgId).msgId : msgId; } @Override public void ack(Object msgId) { super.ack(getUserMsgId(msgId)); } @Override public void fail(Object msgId) { super.fail(getUserMsgId(msgId)); } }