com.addthis.hydra.query.tracker.TrackerHandler.java Source code

Java tutorial

Introduction

Here is the source code for com.addthis.hydra.query.tracker.TrackerHandler.java

Source

/*
 * 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.addthis.hydra.query.tracker;

import java.util.Arrays;
import java.util.concurrent.TimeUnit;

import com.addthis.hydra.data.query.Query;
import com.addthis.hydra.data.query.QueryException;
import com.addthis.hydra.data.query.QueryOpProcessor;
import com.addthis.hydra.query.aggregate.DetailedStatusTask;
import com.addthis.hydra.query.web.DataChannelOutputToNettyBridge;
import com.addthis.hydra.util.StringMapHelper;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelProgressiveFuture;
import io.netty.channel.ChannelProgressiveFutureListener;
import io.netty.channel.ChannelProgressivePromise;
import io.netty.channel.ChannelPromise;
import io.netty.util.concurrent.DefaultPromise;
import io.netty.util.concurrent.Promise;

public class TrackerHandler extends ChannelOutboundHandlerAdapter implements ChannelProgressiveFutureListener {

    private static final Logger log = LoggerFactory.getLogger(TrackerHandler.class);

    private final QueryTracker queryTracker;
    private final String[] opsLog;

    // set when added to pipeline
    private DataChannelOutputToNettyBridge queryUser;
    private ChannelProgressivePromise queryPromise;
    private ChannelHandlerContext ctx;

    // set on query
    private Query query;
    private QueryEntry queryEntry;
    private QueryOpProcessor opProcessorConsumer;
    private ChannelProgressivePromise opPromise;
    private ChannelPromise requestPromise;

    public TrackerHandler(QueryTracker queryTracker, String[] opsLog) {
        this.queryTracker = queryTracker;
        this.opsLog = opsLog;
    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        this.queryPromise = ctx.newProgressivePromise();
        this.opPromise = ctx.newProgressivePromise();
        this.ctx = ctx;
    }

    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        if (msg instanceof Query) {
            writeQuery(ctx, (Query) msg, promise);
        } else {
            super.write(ctx, msg, promise);
        }
    }

    protected void writeQuery(final ChannelHandlerContext ctx, Query msg, ChannelPromise promise) throws Exception {
        this.requestPromise = promise;
        this.queryUser = new DataChannelOutputToNettyBridge(ctx, promise);
        this.query = msg;
        query.queryPromise = queryPromise;
        // create a processor chain based in query ops terminating the query user
        this.opProcessorConsumer = query.newProcessor(queryUser, opPromise);
        queryEntry = new QueryEntry(query, opsLog, requestPromise, this);

        // Check if the uuid is repeated, then make a new one
        if (queryTracker.running.putIfAbsent(query.uuid(), queryEntry) != null) {
            String old = query.uuid();
            query.useNextUUID();
            log.warn("Query uuid was already in use in running. Going to try assigning a new one. old:{} new:{}",
                    old, query.uuid());
            if (queryTracker.running.putIfAbsent(query.uuid(), queryEntry) != null) {
                throw new QueryException("Query uuid was STILL somehow already in use : " + query.uuid());
            }
        }

        log.debug("Executing.... {} {}", query.uuid(), queryEntry.queryDetails);

        ctx.pipeline().remove(this);

        opPromise.addListener(this);
        queryPromise.addListener(this);
        requestPromise.addListener(this);
        ctx.write(opProcessorConsumer, queryPromise);
    }

    @Override
    public void operationProgressed(ChannelProgressiveFuture future, long progress, long total) throws Exception {
        if (future == queryPromise) {
            queryEntry.preOpLines.addAndGet((int) total);
        } else {
            queryEntry.postOpLines.addAndGet((int) total);
        }
    }

    @Override
    public void operationComplete(ChannelProgressiveFuture future) throws Exception {
        if (future == queryPromise) {
            // only care about operation progressed events for the gathering promise
            return;
        } else if (future == opPromise) {
            // tell aggregator about potential early termination from the op promise
            if (future.isSuccess()) {
                queryPromise.trySuccess();
            } else {
                queryPromise.tryFailure(opPromise.cause());
            }
            return;
        }
        // else the entire request is over; either from an error the last http write completing

        // tell the op processor about potential early termination (which may tell the gatherer in turn)
        if (future.isSuccess()) {
            opPromise.trySuccess();
        } else {
            opPromise.tryFailure(future.cause());
        }
        QueryEntry runE = queryTracker.running.remove(query.uuid());
        if (runE == null) {
            log.warn("failed to remove running for {}", query.uuid());
        }

        Promise<QueryEntryInfo> promise = new DefaultPromise<>(ctx.executor());
        queryEntry.getDetailedQueryEntryInfo(promise);
        QueryEntryInfo entryInfo = promise.getNow();
        if (entryInfo == null) {
            log.warn("Failed to get detailed status for completed query {}; defaulting to brief", query.uuid());
            entryInfo = queryEntry.getStat();
        }

        try {
            StringMapHelper queryLine = new StringMapHelper().put("query.path", query.getPaths()[0])
                    .put("query.ops", Arrays.toString(opsLog)).put("sources", query.getParameter("sources"))
                    .put("time", System.currentTimeMillis()).put("time.run", entryInfo.runTime)
                    .put("job.id", query.getJob()).put("job.alias", query.getParameter("track.alias"))
                    .put("query.id", query.uuid()).put("lines", entryInfo.lines)
                    .put("sender", query.getParameter("sender"));
            if (!future.isSuccess()) {
                Throwable queryFailure = future.cause();
                queryLine.put("type", "query.error").put("error", queryFailure.getMessage());
                queryTracker.queryErrors.inc();
            } else {
                queryLine.put("type", "query.done");
                queryTracker.recentlyCompleted.put(query.uuid(), entryInfo);
            }
            queryTracker.log(queryLine);
            queryTracker.queryMeter.update(entryInfo.runTime, TimeUnit.MILLISECONDS);
        } catch (Exception e) {
            log.error("Error while doing record keeping for a query.", e);
        }
    }

    public void submitDetailedStatusTask(DetailedStatusTask task) {
        ctx.write(task);
    }

}