org.apache.gobblin.runtime.BoundedBlockingRecordQueue.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.gobblin.runtime.BoundedBlockingRecordQueue.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.gobblin.runtime;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;

import com.codahale.metrics.Gauge;
import com.codahale.metrics.Meter;
import com.codahale.metrics.MetricRegistry;

import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.collect.Queues;

import org.apache.gobblin.configuration.ConfigurationKeys;

/**
 * A class implementing a bounded blocking queue with timeout for buffering records between a producer and a consumer.
 *
 * <p>
 *   In addition to the normal queue operations, this class also keeps track of the following statistics:
 *
 *   <ul>
 *     <li>Queue size.</li>
 *     <li>Queue fill ratio (queue size/queue capacity).</li>
 *     <li>Put attempt count.</li>
 *     <li>Mean rate of put attempts (puts/sec).</li>
 *     <li>Get attempt count.</li>
 *     <li>Mean rate of get attempts (gets/sec).</li>
 *   </ul>
 * </p>
 *
 * @author Yinan Li
 */
public class BoundedBlockingRecordQueue<T> {

    private final int capacity;
    private final long timeout;
    private final TimeUnit timeoutTimeUnit;
    private final BlockingQueue<T> blockingQueue;

    private final Optional<QueueStats> queueStats;

    private BoundedBlockingRecordQueue(Builder<T> builder) {
        Preconditions.checkArgument(builder.capacity > 0, "Invalid queue capacity");
        Preconditions.checkArgument(builder.timeout > 0, "Invalid timeout time");

        this.capacity = builder.capacity;
        this.timeout = builder.timeout;
        this.timeoutTimeUnit = builder.timeoutTimeUnit;
        this.blockingQueue = Queues.newArrayBlockingQueue(builder.capacity);

        this.queueStats = builder.ifCollectStats ? Optional.of(new QueueStats()) : Optional.<QueueStats>absent();
    }

    /**
     * Put a record to the tail of the queue, waiting (up to the configured timeout time)
     * for an empty space to become available.
     *
     * @param record the record to put to the tail of the queue
     * @return whether the record has been successfully put into the queue
     * @throws InterruptedException if interrupted while waiting
     */
    public boolean put(T record) throws InterruptedException {
        boolean offered = this.blockingQueue.offer(record, this.timeout, this.timeoutTimeUnit);
        if (this.queueStats.isPresent()) {
            this.queueStats.get().putsRateMeter.mark();
        }
        return offered;
    }

    /**
     * Get a record from the head of the queue, waiting (up to the configured timeout time)
     * for a record to become available.
     *
     * @return the record at the head of the queue, or <code>null</code> if no record is available
     * @throws InterruptedException if interrupted while waiting
     */
    public T get() throws InterruptedException {
        T record = this.blockingQueue.poll(this.timeout, this.timeoutTimeUnit);
        if (this.queueStats.isPresent()) {
            this.queueStats.get().getsRateMeter.mark();
        }
        return record;
    }

    /**
     * Get a {@link QueueStats} object representing queue statistics of this {@link BoundedBlockingRecordQueue}.
     *
     * @return a {@link QueueStats} object wrapped in an {@link com.google.common.base.Optional},
     *         which means it may be absent if collecting of queue statistics is not enabled.
     */
    public Optional<QueueStats> stats() {
        return this.queueStats;
    }

    /**
     * Clear the queue.
     */
    public void clear() {
        this.blockingQueue.clear();
    }

    /**
     * Get a new {@link BoundedBlockingRecordQueue.Builder}.
     *
     * @param <T> record type
     * @return a new {@link BoundedBlockingRecordQueue.Builder}
     */
    public static <T> Builder<T> newBuilder() {
        return new Builder<>();
    }

    /**
     * A builder class for {@link BoundedBlockingRecordQueue}.
     *
     * @param <T> record type
     */
    public static class Builder<T> {

        private int capacity = ConfigurationKeys.DEFAULT_FORK_RECORD_QUEUE_CAPACITY;
        private long timeout = ConfigurationKeys.DEFAULT_FORK_RECORD_QUEUE_TIMEOUT;
        private TimeUnit timeoutTimeUnit = TimeUnit.MILLISECONDS;
        private boolean ifCollectStats = false;

        /**
         * Configure the capacity of the queue.
         *
         * @param capacity the capacity of the queue
         * @return this {@link Builder} instance
         */
        public Builder<T> hasCapacity(int capacity) {
            this.capacity = capacity;
            return this;
        }

        /**
         * Configure the timeout time of queue operations.
         *
         * @param timeout the time timeout time
         * @return this {@link Builder} instance
         */
        public Builder<T> useTimeout(long timeout) {
            this.timeout = timeout;
            return this;
        }

        /**
         * Configure the timeout time unit of queue operations.
         *
         * @param timeoutTimeUnit the time timeout time unit
         * @return this {@link Builder} instance
         */
        public Builder<T> useTimeoutTimeUnit(TimeUnit timeoutTimeUnit) {
            this.timeoutTimeUnit = timeoutTimeUnit;
            return this;
        }

        /**
         * Configure whether to collect queue statistics.
         *
         * @return this {@link Builder} instance
         */
        public Builder<T> collectStats() {
            this.ifCollectStats = true;
            return this;
        }

        /**
         * Build a new {@link BoundedBlockingRecordQueue}.
         *
         * @return the newly built {@link BoundedBlockingRecordQueue}
         */
        public BoundedBlockingRecordQueue<T> build() {
            return new BoundedBlockingRecordQueue<>(this);
        }
    }

    /**
     * A class for collecting queue statistics.
     *
     * <p>
     *   All statistics will have zero values if collecting of statistics is not enabled.
     * </p>
     */
    public class QueueStats {

        public static final String QUEUE_SIZE = "queueSize";
        public static final String FILL_RATIO = "fillRatio";
        public static final String PUT_ATTEMPT_RATE = "putAttemptRate";
        public static final String GET_ATTEMPT_RATE = "getAttemptRate";
        public static final String PUT_ATTEMPT_COUNT = "putAttemptCount";
        public static final String GET_ATTEMPT_COUNT = "getAttemptCount";

        private final Gauge<Integer> queueSizeGauge;
        private final Gauge<Double> fillRatioGauge;
        private final Meter putsRateMeter;
        private final Meter getsRateMeter;

        public QueueStats() {
            this.queueSizeGauge = new Gauge<Integer>() {
                @Override
                public Integer getValue() {
                    return BoundedBlockingRecordQueue.this.blockingQueue.size();
                }
            };

            this.fillRatioGauge = new Gauge<Double>() {
                @Override
                public Double getValue() {
                    return (double) BoundedBlockingRecordQueue.this.blockingQueue.size()
                            / BoundedBlockingRecordQueue.this.capacity;
                }
            };

            this.putsRateMeter = new Meter();
            this.getsRateMeter = new Meter();
        }

        /**
         * Return the queue size.
         *
         * @return the queue size
         */
        public int queueSize() {
            return this.queueSizeGauge.getValue();
        }

        /**
         * Return the queue fill ratio.
         *
         * @return the queue fill ratio
         */
        public double fillRatio() {
            return this.fillRatioGauge.getValue();
        }

        /**
         * Return the rate of put attempts.
         *
         * @return the rate of put attempts
         */
        public double putAttemptRate() {
            return this.putsRateMeter.getMeanRate();
        }

        /**
         * Return the total count of put attempts.
         *
         * @return the total count of put attempts
         */
        public long putAttemptCount() {
            return this.putsRateMeter.getCount();
        }

        /**
         * Return the rate of get attempts.
         *
         * @return the rate of get attempts
         */
        public double getAttemptRate() {
            return this.getsRateMeter.getMeanRate();
        }

        /**
         * Return the total count of get attempts.
         *
         * @return the total count of get attempts
         */
        public long getAttemptCount() {
            return this.getsRateMeter.getCount();
        }

        /**
         * Register all statistics as {@link com.codahale.metrics.Metric}s with a
         * {@link com.codahale.metrics.MetricRegistry}.
         *
         * @param metricRegistry the {@link com.codahale.metrics.MetricRegistry} to register with
         * @param prefix metric name prefix
         */
        public void registerAll(MetricRegistry metricRegistry, String prefix) {
            metricRegistry.register(MetricRegistry.name(prefix, QUEUE_SIZE), this.queueSizeGauge);
            metricRegistry.register(MetricRegistry.name(prefix, FILL_RATIO), this.fillRatioGauge);
            metricRegistry.register(MetricRegistry.name(prefix, PUT_ATTEMPT_RATE), this.putsRateMeter);
            metricRegistry.register(MetricRegistry.name(prefix, GET_ATTEMPT_RATE), this.getsRateMeter);
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder("[");
            sb.append(QUEUE_SIZE).append("=").append(queueSize()).append(", ");
            sb.append(FILL_RATIO).append("=").append(fillRatio()).append(", ");
            sb.append(PUT_ATTEMPT_RATE).append("=").append(putAttemptRate()).append(", ");
            sb.append(PUT_ATTEMPT_COUNT).append("=").append(putAttemptCount()).append(", ");
            sb.append(GET_ATTEMPT_RATE).append("=").append(getAttemptRate()).append(", ");
            sb.append(GET_ATTEMPT_COUNT).append("=").append(getAttemptCount()).append("]");
            return sb.toString();
        }
    }
}