All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.wavefront.agent.queueing.TaskQueueFactoryImpl Maven / Gradle / Ivy

There is a newer version: 9999.0
Show newest version
package com.wavefront.agent.queueing;

import com.google.common.collect.ImmutableMap;
import com.squareup.tape2.QueueFile;
import com.wavefront.agent.data.DataSubmissionTask;
import com.wavefront.agent.handlers.HandlerKey;
import com.wavefront.common.TaggedMetricName;
import com.wavefront.metrics.ExpectedAgentMetric;
import com.yammer.metrics.Metrics;
import com.yammer.metrics.core.Counter;
import com.yammer.metrics.core.Gauge;

import javax.annotation.Nonnull;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.nio.channels.OverlappingFileLockException;
import java.nio.file.Files;
import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiConsumer;
import java.util.logging.Logger;

/**
 * A caching implementation of a {@link TaskQueueFactory}.
 *
 * @author [email protected].
 */
public class TaskQueueFactoryImpl implements TaskQueueFactory {
  private static final Logger logger =
      Logger.getLogger(TaskQueueFactoryImpl.class.getCanonicalName());
  private final Map>> taskQueues = new ConcurrentHashMap<>();

  private final String bufferFile;
  private final boolean purgeBuffer;
  private final boolean disableSharding;
  private final int shardSize;

  private static final Counter bytesWritten = Metrics.newCounter(new TaggedMetricName("buffer",
      "bytes-written"));
  private static final Counter ioTimeWrites = Metrics.newCounter(new TaggedMetricName("buffer",
      "io-time-writes"));

  /**
   * @param bufferFile      File name prefix for queue file names.
   * @param purgeBuffer     Whether buffer files should be nuked before starting (this may cause
   *                        data loss if queue files are not empty).
   * @param disableSharding disable buffer sharding (use single file)
   * @param shardSize       target shard size (in MBytes)
   */
  public TaskQueueFactoryImpl(String bufferFile, boolean purgeBuffer,
                              boolean disableSharding, int shardSize) {
    this.bufferFile = bufferFile;
    this.purgeBuffer = purgeBuffer;
    this.disableSharding = disableSharding;
    this.shardSize = shardSize;

    Metrics.newGauge(ExpectedAgentMetric.BUFFER_BYTES_LEFT.metricName,
        new Gauge() {
          @Override
          public Long value() {
            try {
              long availableBytes = taskQueues.values().stream().
                  flatMap(x -> x.values().stream()).
                  map(TaskQueue::getAvailableBytes).
                  filter(Objects::nonNull).mapToLong(x -> x).sum();

              File bufferDirectory = new File(bufferFile).getAbsoluteFile();
              while (bufferDirectory != null && bufferDirectory.getUsableSpace() == 0) {
                bufferDirectory = bufferDirectory.getParentFile();
              }
              if (bufferDirectory != null) {
                return bufferDirectory.getUsableSpace() + availableBytes;
              }
            } catch (Throwable t) {
              logger.warning("cannot compute remaining space in buffer file partition: " + t);
            }
            return null;
          }
        }
    );
  }

  public > TaskQueue getTaskQueue(@Nonnull HandlerKey key,
                                                                     int threadNum) {
    //noinspection unchecked
    TaskQueue taskQueue = (TaskQueue) taskQueues.computeIfAbsent(key, x -> new TreeMap<>()).
        computeIfAbsent(threadNum, x -> createTaskQueue(key, threadNum));
    try {
      // check if queue is closed and re-create if it is.
      taskQueue.peek();
    } catch (IllegalStateException e) {
      taskQueue = createTaskQueue(key, threadNum);
      taskQueues.get(key).put(threadNum, taskQueue);
    }
    return taskQueue;
  }

  private > TaskQueue createTaskQueue(
      @Nonnull HandlerKey handlerKey, int threadNum) {
    String fileName = bufferFile + "." + handlerKey.getEntityType().toString() + "." +
        handlerKey.getHandle() + "." + threadNum;
    String lockFileName = fileName + ".lck";
    String spoolFileName = fileName + ".spool";
    // Having two proxy processes write to the same buffer file simultaneously causes buffer
    // file corruption. To prevent concurrent access from another process, we try to obtain
    // exclusive access to a .lck file. trylock() is platform-specific so there is no
    // iron-clad guarantee, but it works well in most cases.
    try {
      File lockFile = new File(lockFileName);
      if (lockFile.exists()) {
        Files.deleteIfExists(lockFile.toPath());
      }
      FileChannel channel = new RandomAccessFile(lockFile, "rw").getChannel();
      if (channel.tryLock() == null) {
        throw new OverlappingFileLockException();
      }
    } catch (SecurityException e) {
      logger.severe("Error writing to the buffer lock file " + lockFileName +
          " - please make sure write permissions are correct for this file path and restart the " +
          "proxy: " + e);
      return new TaskQueueStub<>();
    } catch (OverlappingFileLockException e) {
      logger.severe("Error requesting exclusive access to the buffer " +
          "lock file " + lockFileName + " - please make sure that no other processes " +
          "access this file and restart the proxy: " + e);
      return new TaskQueueStub<>();
    } catch (IOException e) {
      logger.severe("Error requesting access to buffer lock file " + lockFileName + " Channel is " +
          "closed or an I/O error has occurred - please restart the proxy: " + e);
      return new TaskQueueStub<>();
    }
    try {
      File buffer = new File(spoolFileName);
      if (purgeBuffer) {
        if (buffer.delete()) {
          logger.warning("Retry buffer has been purged: " + spoolFileName);
        }
      }
      BiConsumer statsUpdater = (bytes, millis) -> {
        bytesWritten.inc(bytes);
        ioTimeWrites.inc(millis);
      };
      com.wavefront.agent.queueing.QueueFile queueFile = disableSharding ?
          new ConcurrentQueueFile(new TapeQueueFile(new QueueFile.Builder(
              new File(spoolFileName)).build(), statsUpdater)) :
          new ConcurrentShardedQueueFile(spoolFileName, ".spool", shardSize * 1024 * 1024,
              s -> new TapeQueueFile(new QueueFile.Builder(new File(s)).build(), statsUpdater));
      // TODO: allow configurable compression types and levels
      return new InstrumentedTaskQueueDelegate<>(new FileBasedTaskQueue<>(queueFile,
          new RetryTaskConverter(handlerKey.getHandle(), TaskConverter.CompressionType.LZ4)),
          "buffer", ImmutableMap.of("port", handlerKey.getHandle()), handlerKey.getEntityType());
    } catch (Exception e) {
      logger.severe("WF-006: Unable to open or create queue file " + spoolFileName + ": " +
          e.getMessage());
      return new TaskQueueStub<>();
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy