org.apache.usergrid.chop.runner.drivers.ResultsLog.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.usergrid.chop.runner.drivers.ResultsLog.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.usergrid.chop.runner.drivers;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;

import org.junit.runner.Result;
import org.apache.usergrid.chop.api.Constants;
import org.apache.usergrid.chop.api.IterationChop;
import org.apache.usergrid.chop.api.TimeChop;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.core.JsonEncoding;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Preconditions;
import com.netflix.config.DynamicBooleanProperty;
import com.netflix.config.DynamicLongProperty;
import com.netflix.config.DynamicPropertyFactory;
import com.netflix.config.DynamicStringProperty;

/** An asynchronous results log implementation. */
public class ResultsLog implements IResultsLog, Runnable {
    private static final Logger LOG = LoggerFactory.getLogger(ResultsLog.class);

    private final AtomicLong resultCount = new AtomicLong();
    private final AtomicBoolean isOpen = new AtomicBoolean(false);
    private LinkedBlockingDeque<Result> buffer = new LinkedBlockingDeque<Result>();
    private Thread thread;
    private JsonGenerator jgen;

    private Tracker tracker;
    private DynamicStringProperty resultsFile;
    private DynamicLongProperty waitTime;
    private DynamicBooleanProperty prettyPrint;

    public ResultsLog(Tracker tracker) throws IOException {
        this.tracker = tracker;
        File defaultFile = File.createTempFile(tracker.getTestClass().getCanonicalName(), "log");
        LOG.info("Default results log file path = {}", defaultFile.getAbsolutePath());

        resultsFile = DynamicPropertyFactory.getInstance().getStringProperty(RESULTS_FILE_KEY,
                defaultFile.getAbsolutePath());
        LOG.info("Actual results log file path = {}", resultsFile.get());
        waitTime = DynamicPropertyFactory.getInstance().getLongProperty(WAIT_TIME_KEY, 200);
        prettyPrint = DynamicPropertyFactory.getInstance().getBooleanProperty(Constants.PRETTY_PRINT_RESULTS, true);
    }

    @Override
    public void open() throws IOException {
        synchronized (isOpen) {
            if (isOpen.compareAndSet(false, true)) {
                resultCount.set(0);

                // write the json header for recording the chop results
                JsonFactory factory = new JsonFactory();
                jgen = factory.createGenerator(new File(resultsFile.get()), JsonEncoding.UTF8);

                if (prettyPrint.get()) {
                    jgen.useDefaultPrettyPrinter();
                }

                jgen.setCodec(new ObjectMapper());

                setupJsonStream();

                thread = new Thread(this, "ResultLog Writer");
                thread.start();
            }
        }
    }

    /**
     * Sets up the JSON preamble to start streaming results into the entity. This must be
     * protected via isOpen. This is an unsafe call, make sure you know how it is used.
     */
    private void setupJsonStream() throws IOException {
        Class<?> testClass = tracker.getTestClass();
        jgen.writeStartObject();
        jgen.writeStringField("testClass", testClass.getCanonicalName());
        jgen.writeNumberField("startTime", tracker.getStartTime());

        if (testClass.isAnnotationPresent(TimeChop.class)) {
            jgen.writeStringField("chopType", "TimeChop");
            jgen.writeObjectField("chopParameters", testClass.getAnnotation(TimeChop.class));
        } else if (tracker.getTestClass().isAnnotationPresent(IterationChop.class)) {
            jgen.writeStringField("chopType", "IterationChop");
            jgen.writeObjectField("chopParameters", testClass.getAnnotation(IterationChop.class));
        } else {
            throw new IllegalStateException(
                    "Supplied testClass " + testClass.getCanonicalName() + "has no chop annotation.");
        }

        jgen.writeFieldName("runResults");
        jgen.writeStartArray();
        jgen.flush();
    }

    /**
     * Cleans up the JSON preamble to close streaming results into the entity. This must
     * be protected via isOpen. This is an unsafe call, make sure you know how it is used.
     */
    private void cleanupJsonStream() throws IOException {
        jgen.writeEndArray(); // end the array of results

        // write summary information after the
        jgen.writeNumberField("stopTime", tracker.getStopTime());
        jgen.writeNumberField("totalRunTime", tracker.getTotalRunTime());
        jgen.writeNumberField("actualIterations", tracker.getActualIterations());
        jgen.writeNumberField("actualTime", tracker.getActualTime());
        jgen.writeNumberField("failures", tracker.getFailures());
        jgen.writeNumberField("ignores", tracker.getIgnores());
        jgen.writeNumberField("totalTestsRun", tracker.getTotalTestsRun());
        jgen.writeNumberField("maxTime", tracker.getMaxTime());
        jgen.writeNumberField("minTime", tracker.getMinTime());
        jgen.writeNumberField("meanTime", tracker.getMeanTime());
        jgen.writeEndObject();
        jgen.flush();
    }

    @Override
    public void close() throws IOException {
        if (isOpen.compareAndSet(true, false)) {

            // Forces us to wait until the writer thread dies
            synchronized (isOpen) {
                cleanupJsonStream();
                jgen.flush();
                jgen.close();
                thread = null;
            }
        }
    }

    @Override
    public void truncate() throws IOException {
        if (isOpen.get()) {
            throw new IOException("Cannot truncate while log is open for writing. Close the log then truncate.");
        }

        // Synchronize on isOpen to prevent re-opening while truncating (rare)
        synchronized (isOpen) {
            File results = new File(resultsFile.get());
            FileChannel channel = new FileOutputStream(results, true).getChannel();
            channel.truncate(0);
            channel.close();
            resultCount.set(0);
        }
    }

    @Override
    public void write(Result result) {
        Preconditions.checkState(isOpen.get(), "The result log is not open for writing!");

        try {
            buffer.putFirst(result);
        } catch (InterruptedException e) {
            LOG.error("Was interrupted on write.", e);
        }
    }

    @Override
    public long getResultCount() {
        return resultCount.get();
    }

    @Override
    public String getPath() {
        return resultsFile.get();
    }

    @Override
    public void run() {
        synchronized (isOpen) {
            // Keep writing after closed until buffer is flushed (empty)
            while (isOpen.get() || !buffer.isEmpty()) {
                try {
                    Result result = buffer.pollLast(waitTime.get(), TimeUnit.MILLISECONDS);

                    if (result != null) {
                        resultCount.incrementAndGet();
                        jgen.writeObject(result);
                    }
                } catch (InterruptedException e) {
                    LOG.error("ResultLog thread interrupted.", e);
                } catch (JsonProcessingException e) {
                    LOG.error("Failed to generate the JSON for a result.", e);
                } catch (IOException e) {
                    LOG.error("Failed to write JSON to output stream for a result", e);
                }
            }

            isOpen.notifyAll();
        }
    }
}