org.apache.htrace.core.LocalFileSpanReceiver.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.htrace.core.LocalFileSpanReceiver.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.htrace.core;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.FileSystems;
import java.nio.file.StandardOpenOption;
import java.util.UUID;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Writes the spans it receives to a local file.
 */
public class LocalFileSpanReceiver extends SpanReceiver {
    private static final Log LOG = LogFactory.getLog(LocalFileSpanReceiver.class);
    public static final String PATH_KEY = "local.file.span.receiver.path";
    public static final String CAPACITY_KEY = "local.file.span.receiver.capacity";
    public static final int CAPACITY_DEFAULT = 5000;
    private static ObjectWriter JSON_WRITER = new ObjectMapper().writer();
    private final String path;

    private byte[][] bufferedSpans;
    private int bufferedSpansIndex;
    private final ReentrantLock bufferLock = new ReentrantLock();

    private final FileOutputStream stream;
    private final FileChannel channel;
    private final ReentrantLock channelLock = new ReentrantLock();

    public LocalFileSpanReceiver(HTraceConfiguration conf) {
        int capacity = conf.getInt(CAPACITY_KEY, CAPACITY_DEFAULT);
        if (capacity < 1) {
            throw new IllegalArgumentException(CAPACITY_KEY + " must not be " + "less than 1.");
        }
        String pathStr = conf.get(PATH_KEY);
        if (pathStr == null || pathStr.isEmpty()) {
            path = getUniqueLocalTraceFileName();
        } else {
            path = pathStr;
        }
        boolean success = false;
        try {
            this.stream = new FileOutputStream(path, true);
        } catch (IOException ioe) {
            LOG.error("Error opening " + path + ": " + ioe.getMessage());
            throw new RuntimeException(ioe);
        }
        this.channel = stream.getChannel();
        if (this.channel == null) {
            try {
                this.stream.close();
            } catch (IOException e) {
                LOG.error("Error closing " + path, e);
            }
            LOG.error("Failed to get channel for " + path);
            throw new RuntimeException("Failed to get channel for " + path);
        }
        this.bufferedSpans = new byte[capacity][];
        this.bufferedSpansIndex = 0;
        if (LOG.isDebugEnabled()) {
            LOG.debug("Created new LocalFileSpanReceiver with path = " + path + ", capacity = " + capacity);
        }
    }

    /**
     * Number of buffers to use in FileChannel#write.
     *
     * On UNIX, FileChannel#write uses writev-- a kernel interface that allows
     * us to send multiple buffers at once.  This is more efficient than making a
     * separate write call for each buffer, since it minimizes the number of
     * transitions from userspace to kernel space.
     */
    private final int WRITEV_SIZE = 20;

    private final static ByteBuffer newlineBuf = ByteBuffer.wrap(new byte[] { (byte) 0xa });

    /**
     * Flushes a bufferedSpans array.
     */
    private void doFlush(byte[][] toFlush, int len) throws IOException {
        int bidx = 0, widx = 0;
        ByteBuffer writevBufs[] = new ByteBuffer[2 * WRITEV_SIZE];

        while (true) {
            if (widx == writevBufs.length) {
                channel.write(writevBufs);
                widx = 0;
            }
            if (bidx == len) {
                break;
            }
            writevBufs[widx] = ByteBuffer.wrap(toFlush[bidx]);
            writevBufs[widx + 1] = newlineBuf;
            bidx++;
            widx += 2;
        }
        if (widx > 0) {
            channel.write(writevBufs, 0, widx);
        }
    }

    @Override
    public void receiveSpan(Span span) {
        // Serialize the span data into a byte[].  Note that we're not holding the
        // lock here, to improve concurrency.
        byte jsonBuf[] = null;
        try {
            jsonBuf = JSON_WRITER.writeValueAsBytes(span);
        } catch (JsonProcessingException e) {
            LOG.error("receiveSpan(path=" + path + ", span=" + span + "): " + "Json processing error: "
                    + e.getMessage());
            return;
        }

        // Grab the bufferLock and put our jsonBuf into the list of buffers to
        // flush. 
        byte toFlush[][] = null;
        bufferLock.lock();
        try {
            if (bufferedSpans == null) {
                LOG.debug("receiveSpan(path=" + path + ", span=" + span + "): " + "LocalFileSpanReceiver for "
                        + path + " is closed.");
                return;
            }
            bufferedSpans[bufferedSpansIndex] = jsonBuf;
            bufferedSpansIndex++;
            if (bufferedSpansIndex == bufferedSpans.length) {
                // If we've hit the limit for the number of buffers to flush, 
                // swap out the existing bufferedSpans array for a new array, and
                // prepare to flush those spans to disk.
                toFlush = bufferedSpans;
                bufferedSpansIndex = 0;
                bufferedSpans = new byte[bufferedSpans.length][];
            }
        } finally {
            bufferLock.unlock();
        }
        if (toFlush != null) {
            // We released the bufferLock above, to avoid blocking concurrent
            // receiveSpan calls.  But now, we must take the channelLock, to make
            // sure that we have sole access to the output channel.  If we did not do
            // this, we might get interleaved output.
            //
            // There is a small chance that another thread doing a flush of more
            // recent spans could get ahead of us here, and take the lock before we
            // do.  This is ok, since spans don't have to be written out in order.
            channelLock.lock();
            try {
                doFlush(toFlush, toFlush.length);
            } catch (IOException ioe) {
                LOG.error("Error flushing buffers to " + path + ": " + ioe.getMessage());
            } finally {
                channelLock.unlock();
            }
        }
    }

    @Override
    public void close() throws IOException {
        byte toFlush[][] = null;
        int numToFlush = 0;
        bufferLock.lock();
        try {
            if (bufferedSpans == null) {
                LOG.info("LocalFileSpanReceiver for " + path + " was already closed.");
                return;
            }
            numToFlush = bufferedSpansIndex;
            bufferedSpansIndex = 0;
            toFlush = bufferedSpans;
            bufferedSpans = null;
        } finally {
            bufferLock.unlock();
        }
        channelLock.lock();
        try {
            doFlush(toFlush, numToFlush);
        } catch (IOException ioe) {
            LOG.error("Error flushing buffers to " + path + ": " + ioe.getMessage());
        } finally {
            try {
                stream.close();
            } catch (IOException e) {
                LOG.error("Error closing stream for " + path, e);
            }
            channelLock.unlock();
        }
    }

    public static String getUniqueLocalTraceFileName() {
        String tmp = System.getProperty("java.io.tmpdir", "/tmp");
        String nonce = null;
        BufferedReader reader = null;
        try {
            // On Linux we can get a unique local file name by reading the process id
            // out of /proc/self/stat.  (There isn't any portable way to get the
            // process ID from Java.)
            reader = new BufferedReader(new InputStreamReader(new FileInputStream("/proc/self/stat"), "UTF-8"));
            String line = reader.readLine();
            if (line == null) {
                throw new EOFException();
            }
            nonce = line.split(" ")[0];
        } catch (IOException e) {
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    LOG.warn("Exception in closing " + reader, e);
                }
            }
        }
        if (nonce == null) {
            // If we can't use the process ID, use a random nonce.
            nonce = UUID.randomUUID().toString();
        }
        return new File(tmp, nonce).getAbsolutePath();
    }
}