/*-
 *
 *  This file is part of Oracle NoSQL Database
 *  Copyright (C) 2011, 2014 Oracle and/or its affiliates.  All rights reserved.
 *
 *  Oracle NoSQL Database is free software: you can redistribute it and/or
 *  modify it under the terms of the GNU Affero General Public License
 *  as published by the Free Software Foundation, version 3.
 *
 *  Oracle NoSQL Database is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public
 *  License in the LICENSE file along with Oracle NoSQL Database.  If not,
 *  see <http://www.gnu.org/licenses/>.
 *
 *  An active Oracle commercial licensing agreement for this product
 *  supercedes this license.
 *
 *  For more information please contact:
 *
 *  Vice President Legal, Development
 *  Oracle America, Inc.
 *  5OP-10
 *  500 Oracle Parkway
 *  Redwood Shores, CA 94065
 *
 *  or
 *
 *  berkeleydb-info_us@oracle.com
 *
 *  [This line intentionally left blank.]
 *  [This line intentionally left blank.]
 *  [This line intentionally left blank.]
 *  [This line intentionally left blank.]
 *  [This line intentionally left blank.]
 *  [This line intentionally left blank.]
 *  EOF
 *
 */

package oracle.kv.impl.measurement;

import java.io.Serializable;

import oracle.kv.impl.util.FormatUtils;

import com.sleepycat.utilint.Latency;

/**
 * A wrapper for measurements generated by LatencyStat
 */
public class LatencyInfo implements Serializable {

    /*
     * ZEROS are used for filling out .csv files when a latency measurement
     * hasn't been taken.
     */
    public static final String ZEROS = "0,0,0,0,0,0,0,0";
    public static final LatencyInfo ZERO_MEASUREMENT =
        new LatencyInfo(PerfStatType.NOP_INT, 0, 0, new Latency(0));

    private static final long serialVersionUID = 1L;

    /* An id which indicates which PerfStatType should be used. */
    private final int perfStatId;
    private final Latency latency;
    private boolean hasBeenRolledUp = false;

    /* The time span covered by this measurement */
    private long startMillis;
    private long endMillis;

    public LatencyInfo(PerfStatType perfType, 
                       long startMillis, 
                       long endMillis, 
                       Latency latency) {
        perfStatId = perfType.getId();
        this.startMillis = startMillis;
        this.endMillis = endMillis;
        this.latency = latency;
    }

    /**
     * Initialize a new LatencyMeasure from the contents of another one.
     * Generally use to summarize and rollup stats.
     */
    public LatencyInfo(PerfStatType perfType, LatencyInfo other) {
        this.perfStatId = perfType.getId();
        this.startMillis = other.startMillis;
        this.endMillis = other.endMillis;
        this.latency = other.latency.clone();
    }

    public long getThroughputPerSec() {
        long elapsedMillis = endMillis - startMillis;

        if (elapsedMillis == 0) {
            return 0;
        }

        /* 
         * Be sure to convert to long, to prevent overflows when doing the
         * throughput calculation.
         */
        long longOps = latency.getTotalOps();
        long throughput = (longOps * 1000)/elapsedMillis;

        if (throughput > 0) {
            return throughput;
        }
        if (longOps > 0) {

            /*
             * The throughput might be 0 if a very few number of operations
             * were executed, and the resulting fraction rounded down to
             * 0. That would be very confusing, so return 1.
             */
            return 1;
        }
        return 0;
    }

    public static String getCSVHeader(String opName) {
        String prefix = '"' + opName + "\n";
        String names = (
            prefix + "TotalOps" + '"' + "," +
            prefix + "PerSec" + '"' + "," +
            prefix + "TotalReq" + '"' + "," +
            prefix + "Min" + '"' + "," +
            prefix + "Max" + '"' + "," +
            prefix + "Avg" + '"' + "," +
            prefix + "95th" + '"' + "," +
            prefix + "99th" + '"').intern();
        return names;
    }

    /*
     * NOTE: the ZEROS static string should match this number of columns
     * generated.
     */
    public String getCSVStats() {
        return
            latency.getTotalOps() + "," +
            getThroughputPerSec() + "," +
            latency.getTotalRequests() + "," +
            latency.getMin() + ","+
            latency.getMax() + ","+
            latency.getAvg() + ","+
            latency.get95thPercent() + ","+
            latency.get99thPercent();
    }

    public void rollup(LatencyInfo other) {
        hasBeenRolledUp = true;
        latency.rollup(other.latency);
        if (other.startMillis < startMillis) {
            startMillis = other.startMillis;
        }
        if (other.endMillis > endMillis) {
            endMillis = other.endMillis;
        }
    }
  
    public int getPerfStatId() {
        return perfStatId;
    }

    @Override
    public String toString() {
        PerfStatType type = PerfStatType.getType(perfStatId);

        /*
         * If this measurement has been rolled up, the 95th and 99th are
         * always zero, since those cannot be combined without the original
         * LatencyStat histogram. Omit those from the display.
         */
        String latencyVals =
            "perSec=" + getThroughputPerSec() +
            " totalOps=" + latency.getTotalOps() +
            " totalReq=" + latency.getTotalRequests() +
            " min=" + latency.getMin() + " max=" + latency.getMax() +
            " avg=" + latency.getAvg();
        if (!hasBeenRolledUp) {
            latencyVals += " 95th=" + latency.get95thPercent() + " 99th=" +
                latency.get99thPercent();
            if (latency.getRequestsOverflow() > 0) {
                latencyVals += " overflow=" + latency.getRequestsOverflow();
            }
        }

        return type +
        " (" + FormatUtils.formatTime(endMillis) + ") " + latencyVals;
    }
    
    public long getStart() {
        return startMillis;
    }

    public long getEnd() {
        return endMillis;
    }

    public Latency getLatency() {
        return latency;
    }
}
