org.graylog2.buffers.processors.OutputBufferProcessor.java Source code

Java tutorial

Introduction

Here is the source code for org.graylog2.buffers.processors.OutputBufferProcessor.java

Source

/**
 * This file is part of Graylog.
 *
 * Graylog is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Graylog is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Graylog.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.graylog2.buffers.processors;

import com.codahale.metrics.Counter;
import com.codahale.metrics.InstrumentedExecutorService;
import com.codahale.metrics.Meter;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.Timer;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.google.common.util.concurrent.Uninterruptibles;
import com.lmax.disruptor.WorkHandler;
import org.graylog2.Configuration;
import org.graylog2.outputs.DefaultMessageOutput;
import org.graylog2.outputs.OutputRouter;
import org.graylog2.plugin.GlobalMetricNames;
import org.graylog2.plugin.Message;
import org.graylog2.plugin.ServerStatus;
import org.graylog2.plugin.buffers.MessageEvent;
import org.graylog2.plugin.outputs.MessageOutput;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.inject.Inject;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import static com.codahale.metrics.MetricRegistry.name;

public class OutputBufferProcessor implements WorkHandler<MessageEvent> {

    private static final Logger LOG = LoggerFactory.getLogger(OutputBufferProcessor.class);

    private static final String INCOMING_MESSAGES_METRICNAME = name(OutputBufferProcessor.class,
            "incomingMessages");
    private static final String PROCESS_TIME_METRICNAME = name(OutputBufferProcessor.class, "processTime");

    private final ExecutorService executor;

    private final Configuration configuration;
    private final ServerStatus serverStatus;

    private final Meter incomingMessages;
    private final Counter outputThroughput;
    private final Timer processTime;

    private final OutputRouter outputRouter;
    private final MessageOutput defaultMessageOutput;

    @Inject
    public OutputBufferProcessor(Configuration configuration, MetricRegistry metricRegistry,
            ServerStatus serverStatus, OutputRouter outputRouter,
            @DefaultMessageOutput MessageOutput defaultMessageOutput) {
        this.configuration = configuration;
        this.serverStatus = serverStatus;
        this.outputRouter = outputRouter;
        this.defaultMessageOutput = defaultMessageOutput;

        final String nameFormat = "outputbuffer-processor-executor-%d";
        final int corePoolSize = configuration.getOutputBufferProcessorThreadsCorePoolSize();
        final int maxPoolSize = configuration.getOutputBufferProcessorThreadsMaxPoolSize();
        final int keepAliveTime = configuration.getOutputBufferProcessorKeepAliveTime();
        this.executor = executorService(metricRegistry, nameFormat, corePoolSize, maxPoolSize, keepAliveTime);

        this.incomingMessages = metricRegistry.meter(INCOMING_MESSAGES_METRICNAME);
        this.outputThroughput = metricRegistry.counter(GlobalMetricNames.OUTPUT_THROUGHPUT);
        this.processTime = metricRegistry.timer(PROCESS_TIME_METRICNAME);
    }

    private ExecutorService executorService(final MetricRegistry metricRegistry, final String nameFormat,
            final int corePoolSize, final int maxPoolSize, final int keepAliveTime) {
        final ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat(nameFormat).build();
        return new InstrumentedExecutorService(
                new ThreadPoolExecutor(corePoolSize, maxPoolSize, keepAliveTime, TimeUnit.MILLISECONDS,
                        new LinkedBlockingQueue<Runnable>(), threadFactory),
                metricRegistry, name(this.getClass(), "executor-service"));
    }

    /**
     * Each message will be written to one or more outputs.
     * <p>
     * The default output is always being used for every message, but optionally the message can be routed to additional
     * outputs, currently based on the stream outputs that are configured in the system.
     * </p>
     * <p>
     * The stream outputs are time limited so one bad output does not impact throughput too much. Essentially this means
     * that the work of writing to the outputs is performed, but the writer threads will not wait forever for stream
     * outputs to finish their work. <b>This might lead to increased memory usage!</b>
     * </p>
     * <p>
     * The default output, however, is allowed to block and is not subject to time limiting. This is important because it
     * can exert back pressure on the processing pipeline this way, making sure we don't run into excessive heap usage.
     * </p>
     *
     * @param event the message to write to outputs
     * @throws Exception
     */
    @Override
    public void onEvent(MessageEvent event) throws Exception {
        incomingMessages.mark();

        final Message msg = event.getMessage();
        if (msg == null) {
            LOG.debug("Skipping null message.");
            return;
        }
        LOG.debug("Processing message <{}> from OutputBuffer.", msg.getId());

        final Set<MessageOutput> messageOutputs = outputRouter.getStreamOutputsForMessage(msg);
        msg.recordCounter(serverStatus, "matched-outputs", messageOutputs.size());

        final Future<?> defaultOutputCompletion = processMessage(msg, defaultMessageOutput);

        final CountDownLatch streamOutputsDoneSignal = new CountDownLatch(messageOutputs.size());
        for (final MessageOutput output : messageOutputs) {
            processMessage(msg, output, streamOutputsDoneSignal);
        }

        // Wait until all writer threads for stream outputs have finished or timeout is reached.
        if (!streamOutputsDoneSignal.await(configuration.getOutputModuleTimeout(), TimeUnit.MILLISECONDS)) {
            LOG.warn("Timeout reached. Not waiting any longer for stream output writer threads to complete.");
        }

        // now block until the default output has finished. most batching outputs will already been done because their
        // fast path is really fast (usually an insert into a queue), but the slow flush path might block for a long time
        // this exerts the back pressure to the system
        if (defaultOutputCompletion != null) {
            Uninterruptibles.getUninterruptibly(defaultOutputCompletion);
        } else {
            LOG.error("The default output future was null, this is a bug!");
        }

        if (msg.hasRecordings()) {
            LOG.debug("Message event trace: {}", msg.recordingsAsString());
        }

        outputThroughput.inc();

        LOG.debug("Wrote message <{}> to all outputs. Finished handling.", msg.getId());
    }

    private Future<?> processMessage(final Message msg, final MessageOutput defaultMessageOutput) {
        return processMessage(msg, defaultMessageOutput, new CountDownLatch(0));
    }

    private Future<?> processMessage(final Message msg, final MessageOutput output,
            final CountDownLatch doneSignal) {
        if (output == null) {
            LOG.error("Output was null!");
            return Futures.immediateCancelledFuture();
        }
        if (!output.isRunning()) {
            LOG.debug("Skipping stopped output {}", output.getClass().getName());
            return Futures.immediateCancelledFuture();
        }

        Future<?> future = null;
        try {
            LOG.debug("Writing message to [{}].", output.getClass());
            if (LOG.isTraceEnabled()) {
                LOG.trace("Message id for [{}]: <{}>", output.getClass(), msg.getId());
            }
            future = executor.submit(new Runnable() {
                @Override
                public void run() {
                    try (Timer.Context ignored = processTime.time()) {
                        output.write(msg);
                    } catch (Exception e) {
                        LOG.error("Error in output [" + output.getClass() + "].", e);
                    } finally {
                        doneSignal.countDown();
                    }
                }
            });
        } catch (Exception e) {
            LOG.error("Could not write message batch to output [" + output.getClass() + "].", e);
            doneSignal.countDown();
        }
        return future;
    }
}