org.apache.streams.local.queues.ThroughputQueue.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.streams.local.queues.ThroughputQueue.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
 *
 *   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.streams.local.queues;

import org.apache.streams.local.builders.LocalStreamBuilder;

import org.apache.commons.lang.NotImplementedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.management.ManagementFactory;
import java.util.Collection;
import java.util.Iterator;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.management.InstanceAlreadyExistsException;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.NotCompliantMBeanException;
import javax.management.ObjectName;

/**
 * A {@link java.util.concurrent.BlockingQueue} implementation that allows the measure measurement of how
 * data flows through the queue.  Is also a {@code MBean} so the flow statistics can be viewed through
 * JMX. Registration of the bean happens whenever a constructor receives a non-null id.
 * <p/>
 * !!! Warning !!!
 * Only the necessary methods for the local streams runtime are implemented.  All other methods throw a
 * {@link org.apache.commons.lang.NotImplementedException}.
 */
public class ThroughputQueue<E> implements BlockingQueue<E>, ThroughputQueueMXBean {

    public static final String NAME_TEMPLATE = "org.apache.streams.local:type=ThroughputQueue,name=%s,identifier=%s,startedAt=%s";

    private static final Logger LOGGER = LoggerFactory.getLogger(ThroughputQueue.class);

    private BlockingQueue<ThroughputElement<E>> underlyingQueue;
    private AtomicLong elementsAdded;
    private AtomicLong elementsRemoved;
    private AtomicLong startTime;
    private AtomicLong totalQueueTime;
    private long maxQueuedTime;
    private volatile boolean active;
    private ReadWriteLock maxQueueTimeLock;

    /**
     * Creates an unbounded, unregistered {@code ThroughputQueue}
     */
    public ThroughputQueue() {
        this(-1, null, LocalStreamBuilder.DEFAULT_STREAM_IDENTIFIER, -1);
    }

    /**
     *
     * @param streamIdentifier
     * @param startedAt
     */
    public ThroughputQueue(String streamIdentifier, long startedAt) {
        this(-1, null, streamIdentifier, startedAt);
    }

    /**
     * Creates a bounded, unregistered {@code ThroughputQueue}
     *
     * @param maxSize maximum capacity of queue, if maxSize < 1 then unbounded
     */
    public ThroughputQueue(int maxSize) {
        this(maxSize, null, LocalStreamBuilder.DEFAULT_STREAM_IDENTIFIER, -1);
    }

    /**
     *
     * @param maxSize
     * @param streamIdentifier
     * @param startedAt
     */
    public ThroughputQueue(int maxSize, String streamIdentifier, long startedAt) {
        this(maxSize, null, streamIdentifier, startedAt);
    }

    /**
     * Creates an unbounded, registered {@code ThroughputQueue}
     *
     * @param id unique id for this queue to be registered with. if id == NULL then not registered
     */
    public ThroughputQueue(String id) {
        this(-1, id, LocalStreamBuilder.DEFAULT_STREAM_IDENTIFIER, -1);
    }

    /**
     *
     * @param id
     * @param streamIdentifier
     * @param startedAt
     */
    public ThroughputQueue(String id, String streamIdentifier, long startedAt) {
        this(-1, id, streamIdentifier, startedAt);
    }

    /**
     *
     * @param maxSize
     * @param id
     */
    public ThroughputQueue(int maxSize, String id) {
        this(maxSize, id, LocalStreamBuilder.DEFAULT_STREAM_IDENTIFIER, -1);

    }

    /**
     * Creates a bounded, registered {@code ThroughputQueue}
     *
     * @param maxSize maximum capacity of queue, if maxSize < 1 then unbounded
     * @param id      unique id for this queue to be registered with. if id == NULL then not registered
     */
    public ThroughputQueue(int maxSize, String id, String streamIdentifier, long startedAt) {
        if (maxSize < 1) {
            this.underlyingQueue = new LinkedBlockingQueue<>();
        } else {
            this.underlyingQueue = new LinkedBlockingQueue<>(maxSize);
        }
        this.elementsAdded = new AtomicLong(0);
        this.elementsRemoved = new AtomicLong(0);
        this.startTime = new AtomicLong(-1);
        this.active = false;
        this.maxQueuedTime = 0;
        this.maxQueueTimeLock = new ReentrantReadWriteLock();
        this.totalQueueTime = new AtomicLong(0);
        if (id != null) {
            try {
                ObjectName name = new ObjectName(String.format(NAME_TEMPLATE, id, streamIdentifier, startedAt));
                MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
                mbs.registerMBean(this, name);
            } catch (MalformedObjectNameException | InstanceAlreadyExistsException | MBeanRegistrationException
                    | NotCompliantMBeanException e) {
                LOGGER.error("Failed to register MXBean : {}", e);
                throw new RuntimeException(e);
            }
        }
    }

    @Override
    public boolean add(E e) {
        if (this.underlyingQueue.add(new ThroughputElement<E>(e))) {
            internalAddElement();
            return true;
        }
        return false;
    }

    @Override
    public boolean offer(E e) {
        if (this.underlyingQueue.offer(new ThroughputElement<E>(e))) {
            internalAddElement();
            return true;
        }
        return false;
    }

    @Override
    public void put(E e) throws InterruptedException {
        this.underlyingQueue.put(new ThroughputElement<E>(e));
        internalAddElement();
    }

    @Override
    public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException {
        if (this.underlyingQueue.offer(new ThroughputElement<E>(e), timeout, unit)) {
            internalAddElement();
            return true;
        }
        return false;
    }

    @Override
    public E take() throws InterruptedException {
        ThroughputElement<E> e = this.underlyingQueue.take();
        internalRemoveElement(e);
        return e.getElement();
    }

    @Override
    public E poll(long timeout, TimeUnit unit) throws InterruptedException {
        ThroughputElement<E> e = this.underlyingQueue.poll(timeout, unit);
        if (e != null) {
            internalRemoveElement(e);
            return e.getElement();
        }
        return null;
    }

    @Override
    public int remainingCapacity() {
        return this.underlyingQueue.remainingCapacity();
    }

    @Override
    public boolean remove(Object o) {
        try {
            return this.underlyingQueue.remove(new ThroughputElement<E>((E) o));
        } catch (ClassCastException cce) {
            return false;
        }
    }

    @Override
    public boolean contains(Object o) {
        try {
            return this.underlyingQueue.contains(new ThroughputElement<E>((E) o));
        } catch (ClassCastException cce) {
            return false;
        }
    }

    @Override
    public int drainTo(Collection<? super E> c) {
        throw new NotImplementedException();
    }

    @Override
    public int drainTo(Collection<? super E> c, int maxElements) {
        throw new NotImplementedException();
    }

    @Override
    public E remove() {
        ThroughputElement<E> e = this.underlyingQueue.remove();
        if (e != null) {
            internalRemoveElement(e);
            return e.getElement();
        }
        return null;
    }

    @Override
    public E poll() {
        ThroughputElement<E> e = this.underlyingQueue.poll();
        if (e != null) {
            internalRemoveElement(e);
            return e.getElement();
        }
        return null;
    }

    @Override
    public E element() {
        throw new NotImplementedException();
    }

    @Override
    public E peek() {
        ThroughputElement<E> e = this.underlyingQueue.peek();
        if (e != null) {
            return e.getElement();
        }
        return null;
    }

    @Override
    public int size() {
        return this.underlyingQueue.size();
    }

    @Override
    public boolean isEmpty() {
        return this.underlyingQueue.isEmpty();
    }

    @Override
    public Iterator<E> iterator() {
        throw new NotImplementedException();
    }

    @Override
    public Object[] toArray() {
        throw new NotImplementedException();
    }

    @Override
    public <T> T[] toArray(T[] a) {
        throw new NotImplementedException();
    }

    @Override
    public boolean containsAll(Collection<?> c) {
        throw new NotImplementedException();
    }

    @Override
    public boolean addAll(Collection<? extends E> c) {
        throw new NotImplementedException();
    }

    @Override
    public boolean removeAll(Collection<?> c) {
        throw new NotImplementedException();
    }

    @Override
    public boolean retainAll(Collection<?> c) {
        throw new NotImplementedException();
    }

    @Override
    public void clear() {
        throw new NotImplementedException();
    }

    @Override
    public long getCurrentSize() {
        return this.elementsAdded.get() - this.elementsRemoved.get();
    }

    /**
     * If elements have been removed from the queue or no elements have been added, it returns the average wait time
     * in milliseconds. If elements have been added, but none have been removed, it returns the time waited by the first
     * element in the queue.
     *
     * @return the average wait time in milliseconds
     */
    @Override
    public double getAvgWait() {
        if (this.elementsRemoved.get() == 0) {
            if (this.getCurrentSize() > 0) {
                return this.underlyingQueue.peek().getWaited();
            } else {
                return 0.0;
            }
        } else {
            return (double) this.totalQueueTime.get() / (double) this.elementsRemoved.get();
        }
    }

    @Override
    public long getMaxWait() {
        ThroughputElement<E> e = this.underlyingQueue.peek();
        long max = -1;
        try {
            this.maxQueueTimeLock.readLock().lock();
            if (e != null && e.getWaited() > this.maxQueuedTime) {
                max = e.getWaited();
            } else {
                max = this.maxQueuedTime;
            }
        } finally {
            this.maxQueueTimeLock.readLock().unlock();
        }
        return max;
    }

    @Override
    public long getRemoved() {
        return this.elementsRemoved.get();
    }

    @Override
    public long getAdded() {
        return this.elementsAdded.get();
    }

    @Override
    public double getThroughput() {
        if (active) {
            return this.elementsRemoved.get() / ((System.currentTimeMillis() - this.startTime.get()) / 1000.0);
        }
        return 0.0;
    }

    /**
     * Handles updating the stats whenever elements are added to the queue
     */
    private void internalAddElement() {
        this.elementsAdded.incrementAndGet();
        synchronized (this) {
            if (!this.active) {
                this.startTime.set(System.currentTimeMillis());
                this.active = true;
            }
        }
    }

    /**
     * Handle updating the stats whenever elements are removed from the queue
     * @param e Element removed
     */
    private void internalRemoveElement(ThroughputElement<E> e) {
        if (e != null) {
            this.elementsRemoved.incrementAndGet();
            Long queueTime = e.getWaited();
            this.totalQueueTime.addAndGet(queueTime);
            boolean unlocked = false;
            try {
                this.maxQueueTimeLock.readLock().lock();
                if (this.maxQueuedTime < queueTime) {
                    this.maxQueueTimeLock.readLock().unlock();
                    unlocked = true;
                    try {
                        this.maxQueueTimeLock.writeLock().lock();
                        this.maxQueuedTime = queueTime;
                    } finally {
                        this.maxQueueTimeLock.writeLock().unlock();
                    }
                }
            } finally {
                if (!unlocked)
                    this.maxQueueTimeLock.readLock().unlock();
            }
        }
    }

    /**
     * Element wrapper to measure time waiting on the queue
     *
     * @param <E>
     */
    private class ThroughputElement<E> {

        private long queuedTime;
        private E element;

        protected ThroughputElement(E element) {
            this.element = element;
            this.queuedTime = System.currentTimeMillis();
        }

        /**
         * Get the time this element has been waiting on the queue.
         * current time - time element was queued
         *
         * @return time this element has been waiting on the queue in milliseconds
         */
        public long getWaited() {
            return System.currentTimeMillis() - this.queuedTime;
        }

        /**
         * Get the queued element
         *
         * @return the element
         */
        public E getElement() {
            return this.element;
        }

        /**
         * Measures equality by the element and ignores the queued time
         * @param obj
         * @return
         */
        @Override
        public boolean equals(Object obj) {
            if (obj instanceof ThroughputElement && obj != null) {
                ThroughputElement that = (ThroughputElement) obj;
                if (that.getElement() == null && this.getElement() == null) {
                    return true;
                } else if (that.getElement() != null) {
                    return that.getElement().equals(this.getElement());
                } else {
                    return false;
                }
            }
            return false;
        }
    }
}