# ConnectionsView.java

    /*
    *  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.synapse.transport.nhttp.util;

    import org.apache.synapse.commons.jmx.MBeanRegistrar;

    import java.util.*;
    import java.util.concurrent.ThreadFactory;
    import java.util.concurrent.atomic.AtomicInteger;
    import java.util.concurrent.ScheduledExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.TimeUnit;

    /**
     * ConnectionsView MBean can be used to collect and monitor statistics on HTTP connections
     * created by the NHTTP transport. Connection statistics can be divided into two categories,
     * namely short term data and long term data. Short term data is related to the last 15
     * minutes of execution and they are updated every minute. Long term data is related to
     * the last 24 hours of execution and they get updated every 5 minutes.  In addition to the
     * connection statistics this MBean also provides information on the request and response
     * sizes received over the HTTP connections. All messages are divided into six categories
     * based on their sizes and the resulting counts are made available as a table.
     */
    public class ConnectionsView implements ConnectionsViewMBean {

        private static final String NHTTP_CONNECTIONS = "NhttpConnections";

        // Bucket definitions
        private static final int LESS_THAN_1K       = 0;
        private static final int LESS_THAN_10K      = 1;
        private static final int LESS_THAN_100K     = 2;
        private static final int LESS_THAN_1M       = 3;
        private static final int LESS_THAN_10M      = 4;
        private static final int GREATER_THAN_10M   = 5;

        private static final int SHORT_DATA_COLLECTION_PERIOD = 60;
        private static final int LONG_DATA_COLLECTION_PERIOD = 60 * 5;

        private static final int SAMPLES_PER_HOUR = (60 * 60)/LONG_DATA_COLLECTION_PERIOD;

        private Queue<Integer> shortTermDataQueue = new LinkedList<Integer>();
        private Queue<Integer> longTermDataQueue = new LinkedList<Integer>();

        private AtomicInteger activeConnections = new AtomicInteger(0);
        private AtomicInteger shortTermOpenedConnections = new AtomicInteger(0);
        private AtomicInteger longTermOpenedConnections = new AtomicInteger(0);
        // The map keeps the key as connection host:port and value as the number of connections for
        // that host
        private Map<String,AtomicInteger> activeConnectionsPerHost = new HashMap<String,AtomicInteger>();
        // The array length must be equal to the number of buckets
        private AtomicInteger[] requestSizeCounters = new AtomicInteger[6];
        private AtomicInteger[] responseSizeCounters = new AtomicInteger[6];

        private Date resetTime = Calendar.getInstance().getTime();

        private ScheduledExecutorService scheduler;

        private String name;

        public ConnectionsView(final String name) {
            this.name = name;

            this.scheduler = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
                public Thread newThread(Runnable r) {
                    return new Thread(r, name + "-connections-view");
                }
            });

            initCounters(requestSizeCounters);
            initCounters(responseSizeCounters);

            Runnable task = new Runnable() {
                public void run() {
                    // We only need historical data for the last 15 minutes
                    // Therefore no need to keep data older than that...
                    if (shortTermDataQueue.size() == 15) {
                        shortTermDataQueue.remove();
                    }
                    shortTermDataQueue.offer(shortTermOpenedConnections.getAndSet(0));
                }
            };
            // Delay the timer by 1 minute to prevent the task from starting immediately
            scheduler.scheduleAtFixedRate(task, SHORT_DATA_COLLECTION_PERIOD,
                    SHORT_DATA_COLLECTION_PERIOD, TimeUnit.SECONDS);

            Runnable longTermCollector = new Runnable() {
                public void run() {
                    // We only need historical data for the last 24 hours
                    // Therefore no need to keep data older than that...
                    if (longTermDataQueue.size() == 24 * SAMPLES_PER_HOUR) {
                        longTermDataQueue.remove();
                    }
                    longTermDataQueue.offer(longTermOpenedConnections.getAndSet(0));
                }
            };
            scheduler.scheduleAtFixedRate(longTermCollector, LONG_DATA_COLLECTION_PERIOD,
                    LONG_DATA_COLLECTION_PERIOD, TimeUnit.SECONDS);
            boolean registered = false;
            try {
                registered = MBeanRegistrar.getInstance().registerMBean(this, NHTTP_CONNECTIONS, name);
            } finally {
                if (!registered) {
                    scheduler.shutdownNow();
                }
            }
        }

        public void destroy() {
            MBeanRegistrar.getInstance().unRegisterMBean(NHTTP_CONNECTIONS, name);
            if (!scheduler.isShutdown()) {
                scheduler.shutdownNow();
            }
        }

        private void initCounters(AtomicInteger[] counters) {
            for (int i = 0; i < counters.length; i++) {
                if (counters[i] == null) {
                    counters[i] = new AtomicInteger(0);
                } else {
                    counters[i].set(0);
                }
            }
        }

        protected void connected() {
            activeConnections.incrementAndGet();
            shortTermOpenedConnections.incrementAndGet();
            longTermOpenedConnections.incrementAndGet();
        }

        protected void disconnected() {
            activeConnections.decrementAndGet();
        }

        protected void notifyMessageSize(long size, boolean isRequest) {
            // This logic gets executed for each and every transaction. For a typical
            // mediation scenario this method will be called 4 times. Therefore I'm using
            // arrays of integers to keep the overhead down to a minimum. Since the number
            // of buckets is 6, this can be easily managed without using a slow data structure
            // like a HashMap. This approach guarantees O(1) complexity.

            AtomicInteger[] counters = isRequest ? requestSizeCounters : responseSizeCounters;

            if (size < 1024) {
                counters[LESS_THAN_1K].incrementAndGet();
            } else if (size < 10240) {
                counters[LESS_THAN_10K].incrementAndGet();
            } else if (size < 102400) {
                counters[LESS_THAN_100K].incrementAndGet();
            } else if (size < 1048576) {
                counters[LESS_THAN_1M].incrementAndGet();
            } else if (size < 10485760) {
                counters[LESS_THAN_10M].incrementAndGet();
            } else {
                counters[GREATER_THAN_10M].incrementAndGet();
            }
        }

        public int getActiveConnections() {
            return activeConnections.get();
        }

        public int getLastMinuteConnections() {
            return getTotalConnections(1);
        }

        public int getLast5MinuteConnections() {
            return getTotalConnections(5);
        }

        public int getLast15MinuteConnections() {
            return getTotalConnections(15);
        }

        public int getLastHourConnections() {
            return getTotalConnectionsByHour(1);
        }

        public int getLast8HourConnections() {
            return getTotalConnectionsByHour(8);
        }

        public int getLast24HourConnections() {
            return getTotalConnectionsByHour(24);
        }

        public Map getRequestSizesMap() {
            return getCountersMap(requestSizeCounters);
        }

        public Map getResponseSizesMap() {
            return getCountersMap(responseSizeCounters);
        }

        public Date getLastResetTime() {
            return resetTime;
        }

        private Map<String,Integer> getCountersMap(AtomicInteger[] counters) {
            // This ensures that keys are returned in the same order we insert them
            // Provides better readability in the JMX consoles
            Map<String,Integer> map = new LinkedHashMap<String,Integer>();
            map.put("< 1 K", counters[LESS_THAN_1K].get());
            map.put("< 10 K", counters[LESS_THAN_10K].get());
            map.put("< 100 K", counters[LESS_THAN_100K].get());
            map.put("< 1 M", counters[LESS_THAN_1M].get());
            map.put("< 10 M", counters[LESS_THAN_10M].get());
            map.put("> 10 M", counters[GREATER_THAN_10M].get());
            return map;
        }

        public void reset() {
            initCounters(requestSizeCounters);
            initCounters(responseSizeCounters);
            shortTermDataQueue.clear();
            longTermDataQueue.clear();
            resetTime = Calendar.getInstance().getTime();
        }

        /**
         * Return the number of total connections opened during last 'n' munites
         * of execution
         *
         * @param n Number of minutes in the execution history
         * @return The number of connections opened
         */
        private int getTotalConnections(int n) {
            int sum = 0;
            Integer[] array = shortTermDataQueue.toArray(new Integer[shortTermDataQueue.size()]);

            if (n > array.length) {
                for (int i = 0; i < array.length; i++) {
                    sum += array[i];
                }
            } else {
                for (int i = 0; i < n; i++) {
                    sum += array[array.length - 1 - i];
                }
            }
            return sum;
        }

        /**
         * Return the number of total connections opened during last 'n' hours
         * of execution
         *
         * @param n Number of hours in the execution history
         * @return The number of connections opened
         */
        private int getTotalConnectionsByHour(int n) {
            int samples = n * SAMPLES_PER_HOUR;
            int sum = 0;
            Integer[] array = longTermDataQueue.toArray(new Integer[longTermDataQueue.size()]);

            if (samples > array.length) {
                for (int i = 0; i < array.length; i++) {
                    sum += array[i];
                }
            } else {
                for (int i = 0; i < samples; i++) {
                    sum += array[array.length - 1 - i];
                }
            }
            return sum;
        }

        /**
         * Setter method for activeConnectionsPerHost, this will get called during connection creation
         * and Connection shutdown operations
         *
         * @param activeConnectionsPerHost
         */
        public void setActiveConnectionPerHostEntry(Map<String,AtomicInteger> activeConnectionsPerHost){
            this.activeConnectionsPerHost = activeConnectionsPerHost;
        }

        public Map getActiveConnectionsPerHosts(){
            return activeConnectionsPerHost;
        }

    }