package net.iponweb.disthene.reader.handler; import com.google.common.base.Stopwatch; import com.google.common.util.concurrent.SimpleTimeLimiter; import com.google.common.util.concurrent.TimeLimiter; import com.google.common.util.concurrent.UncheckedTimeoutException; import io.netty.handler.codec.http.*; import net.iponweb.disthene.reader.beans.TimeSeries; import net.iponweb.disthene.reader.config.ReaderConfiguration; import net.iponweb.disthene.reader.exceptions.*; import net.iponweb.disthene.reader.format.ResponseFormatter; import net.iponweb.disthene.reader.graphite.Target; import net.iponweb.disthene.reader.graphite.TargetVisitor; import net.iponweb.disthene.reader.graphite.evaluation.EvaluationContext; import net.iponweb.disthene.reader.graphite.evaluation.TargetEvaluator; import net.iponweb.disthene.reader.graphite.grammar.GraphiteLexer; import net.iponweb.disthene.reader.graphite.grammar.GraphiteParser; import net.iponweb.disthene.reader.graphite.utils.ValueFormatter; import net.iponweb.disthene.reader.handler.parameters.RenderParameters; import net.iponweb.disthene.reader.service.metric.MetricService; import net.iponweb.disthene.reader.service.stats.StatsService; import net.iponweb.disthene.reader.service.throttling.ThrottlingService; import org.antlr.v4.runtime.ANTLRInputStream; import org.antlr.v4.runtime.CommonTokenStream; import org.antlr.v4.runtime.misc.ParseCancellationException; import org.antlr.v4.runtime.tree.ParseTree; import org.apache.log4j.Logger; import java.util.ArrayList; import java.util.List; import java.util.concurrent.*; /** * @author Andrei Ivanov */ public class RenderHandler implements DistheneReaderHandler { final static Logger logger = Logger.getLogger(RenderHandler.class); private TargetEvaluator evaluator; private StatsService statsService; private ThrottlingService throttlingService; private ReaderConfiguration readerConfiguration; private static final ExecutorService executor = Executors.newCachedThreadPool(); private TimeLimiter timeLimiter = new SimpleTimeLimiter(executor); public RenderHandler(MetricService metricService, StatsService statsService, ThrottlingService throttlingService, ReaderConfiguration readerConfiguration) { this.evaluator = new TargetEvaluator(metricService); this.statsService = statsService; this.throttlingService = throttlingService; this.readerConfiguration = readerConfiguration; } @Override public FullHttpResponse handle(final HttpRequest request) throws ParameterParsingException, ExecutionException, InterruptedException, EvaluationException, LogarithmicScaleNotAllowed { final RenderParameters parameters = RenderParameters.parse(request); logger.debug("Got request: " + parameters); Stopwatch timer = Stopwatch.createStarted(); double throttled = throttlingService.throttle(parameters.getTenant()); statsService.incRenderRequests(parameters.getTenant()); if (throttled > 0) { statsService.incThrottleTime(parameters.getTenant(), throttled); } final List<Target> targets = new ArrayList<>(); EvaluationContext context = new EvaluationContext( readerConfiguration.isHumanReadableNumbers() ? ValueFormatter.getInstance(parameters.getFormat()) : ValueFormatter.getInstance(ValueFormatter.ValueFormatterType.MACHINE) ); // Let's parse the targets for(String targetString : parameters.getTargets()) { GraphiteLexer lexer = new GraphiteLexer(new ANTLRInputStream(targetString)); CommonTokenStream tokens = new CommonTokenStream(lexer); GraphiteParser parser = new GraphiteParser(tokens); ParseTree tree = parser.expression(); try { targets.add(new TargetVisitor(parameters.getTenant(), parameters.getFrom(), parameters.getUntil(), context).visit(tree)); } catch (ParseCancellationException e) { String additionalInfo = null; if (e.getMessage() != null) additionalInfo = e.getMessage(); if (e.getCause() != null) additionalInfo = e.getCause().getMessage(); throw new InvalidParameterValueException("Could not parse target: " + targetString + " (" + additionalInfo + ")"); } } FullHttpResponse response; try { response = timeLimiter.callWithTimeout(new Callable<FullHttpResponse>() { @Override public FullHttpResponse call() throws EvaluationException, LogarithmicScaleNotAllowed { return handleInternal(targets, parameters); } }, readerConfiguration.getRequestTimeout(), TimeUnit.SECONDS, true); } catch (UncheckedTimeoutException e) { logger.debug("Request timed out: " + parameters); statsService.incTimedOutRequests(parameters.getTenant()); response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.REQUEST_ENTITY_TOO_LARGE); } catch (EvaluationException | LogarithmicScaleNotAllowed e) { throw e; } catch (Exception e) { logger.error(e); response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.INTERNAL_SERVER_ERROR); } timer.stop(); logger.debug("Request took " + timer.elapsed(TimeUnit.MILLISECONDS) + " milliseconds (" + parameters + ")"); return response; } private FullHttpResponse handleInternal(List<Target> targets, RenderParameters parameters) throws EvaluationException, LogarithmicScaleNotAllowed { // now evaluate each target producing list of TimeSeries List<TimeSeries> results = new ArrayList<>(); for(Target target : targets) { results.addAll(evaluator.eval(target)); } return ResponseFormatter.formatResponse(results, parameters); } }