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

org.voltdb.client.exampleutils.PerfCounter Maven / Gradle / Ivy

There is a newer version: 13.3.2-preview1
Show newest version
/* This file is part of VoltDB.
 * Copyright (C) 2008-2017 VoltDB Inc.
 *
 * This program 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, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program 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
 * along with VoltDB.  If not, see .
 */
package org.voltdb.client.exampleutils;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.voltdb.client.ClientResponse;

/**
 * A thread-safe performance counter used to track procedure/statement execution statistics.
 *
 * @author Seb Coursol
 * @since 2.0
 */
public class PerfCounter implements Cloneable
{
    private static final Lock lock = new ReentrantLock();
    private long StartTime = Long.MAX_VALUE;
    private long EndTime   = Long.MIN_VALUE;
    private long min   = 999999999l;
    private long max   = -1l;
    private long tot   = 0l;
    private long cnt   = 0l;
    private long err   = 0l;

    /*
     * The buckets cover 0 - 500ms+.
     *
     * The first 100 buckets cover 0 - 100ms, 1ms each (e.g. (0, 1ms]). The next
     * 8 buckets cover 100 - 500ms, 50ms each. The last one covers 500ms+.
     */
    private long[] lat = new long[109];

    /**
     * Creates a new performance counter and immediately starts tracking time (for rate/second calculations).
     */
    public PerfCounter() { this(true); }

    /**
     * Creates a new performance counter, optionally starting time tracking.
     *
     * @param start the flag indicating whether time tracking should be started immediately.  When false time tracking is started on the first counter update.
     */
    public PerfCounter(boolean start)
    {
        if (start)
            StartTime = System.currentTimeMillis();
    }

    /**
     * Gets the time (in milliseconds since 1/1/1970 00:00 UTC) at which this counter's time tracking began (the time at which this counter was created - if created with the start parameter set to true - or the time of the first update).
     */
    public long getStartTime()
    {
        return StartTime;
    }

    /**
     * Gets the time (in milliseconds since 1/1/1970 00:00 UTC) at which this counter's time tracking ended (the time if the last update).
     */
    public long getEndTime()
    {
        return EndTime;
    }

    /**
     * Gets the minimum execution latency tracked by this counter.
     */
    public long getMinLatency()
    {
        return min;
    }

    /**
     * Gets the maximum execution latency tracked by this counter.
     */
    public long getMaxLatency()
    {
        return max;
    }

    /**
     * Gets the total execution duration tracked by this counter (the sum of execution durations of all calls tracked by this counter).
     */
    public long getTotalExecutionDuration()
    {
        return tot;
    }

    /**
     * Gets the number of execution calls tracked by this counter.
     */
    public long getExecutionCount()
    {
        return cnt;
    }

    /**
     * Gets the number of execution errors tracked by this counter.
     */
    public long getErrorCount()
    {
        return err;
    }

    /**
     * Gets the latency distribution buckets of execution calls tracked by this counter.
     */
    public long[] getLatencyBuckets()
    {
        return lat;
    }

    /**
     * Gets the elapsed duration between the start and end time of this counter.
     */
    public long getElapsedDuration()
    {
        return this.EndTime-this.StartTime;
    }

    /**
     * Gets the average number of execution calls per second for all execution calls tracked by this counter.
     */
    public double getTransactionRatePerSecond()
    {
        return getExecutionCount()*1000d/getElapsedDuration();
    }

    /**
     * Gets the average execution latency for calls tracked by this counter.
     */
    public double getAverageLatency()
    {
        return (double)getTotalExecutionDuration()/(double)getExecutionCount();
    }

    /**
     * Tracks a call execution by processing the ClientResponse sent back by the VoltDB server.
     *
     * @param response the response sent by the VoltDB server, containing details about the procedure/statement execution.
     */
    public void update(ClientResponse response)
    {
        this.update(response.getClientRoundtrip(), response.getStatus() == ClientResponse.SUCCESS);
    }

    /**
     * Tracks a generic call execution by reporting the execution duration.  This method should be used for successful calls only.
     *
     * @param executionDuration the duration of the execution call to track in this counter.
     * @see #update(long executionDuration, boolean success)
     */
    public void update(long executionDuration)
    {
        this.update(executionDuration, true);
    }

    /**
     * Tracks a generic call execution by reporting the execution duration.  This method should be used for successful calls only.
     *
     * @param executionDuration the duration of the execution call to track in this counter.
     * @param success the flag indicating whether the execution call was successful.
     */
    public void update(long executionDuration, boolean success)
    {
        lock.lock();
        try
        {
            EndTime = System.currentTimeMillis();
            if (StartTime == Long.MAX_VALUE)
                StartTime = EndTime-executionDuration;
            cnt++;
            tot+= executionDuration;
            if (min > executionDuration)
                min = executionDuration;
            if (max < executionDuration)
                max = executionDuration;
            int bucket = (int) executionDuration;
            if (executionDuration > 100)
                bucket = Math.min((int)((executionDuration-100l)/50l),8) + 100;
            lat[bucket]++;
            if (!success)
                err++;
        }
        finally
        {
            lock.unlock();
        }
    }

    /**
     * Gets a representation of this counter as a single-line short-format string detailing the statistics tracked by this counter.
     *
     * @return the string representation of this counter.
     * @see #toString(boolean useSimpleFormat)
     */
    @Override
    public String toString()
    {
        return toString(true);
    }

    /**
     * Gets a representation of this counter as a string detailing the statistics tracked by this counter.
     *
     * @param useSimpleFormat the flag indicating whether to use a short one-line format, or detailed statistics including latency bucketing.
     * @return the string representation of this counter.
     */
    public String toString(boolean useSimpleFormat)
    {
        SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
        dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
        long elapsedDuration = (this.StartTime == Long.MAX_VALUE) ? 1 : this.EndTime-this.StartTime;
        if (useSimpleFormat)
            return String.format(
                                  "%8s | Txn.: %,11d%s @ %,11.1f TPS | Lat. = %7d <  %7.2f < %7d\n"
                                , dateFormat.format(new Date(Math.round(elapsedDuration/1000d)*1000l))
                                , this.cnt
                                , this.err > 0 ? String.format(" [!%,11d]", this.err) : ""
                                , (this.cnt*1000d / (double)elapsedDuration)
                                , this.min == 999999999l ? 0l : this.min
                                , (double)this.tot/(double)this.cnt
                                , this.max == -1l ? 0l : this.max
                                );
        else {
            long[] coarseLat = new long[9];
            // Roll up the latencies below 100ms
            for (int i = 0; i < 4; i++) {
                for (int j = 0; j < 25; j++) {
                    coarseLat[i] += this.lat[i * 25 + j];
                }
            }
            // Roll up the rest
            for (int i = 4; i < 8; i++) {
                coarseLat[i] = this.lat[100 + i - 4];
            }
            for (int j = 104; j < this.lat.length; j++) {
                coarseLat[8] += this.lat[j];
            }

            return String.format(
                                 "-------------------------------------------------------------------------------------\n" +
                                 "Final:   | Txn.: %,11d%s @ %,11.1f TPS | Lat. = %7d <  %7.2f < %7d\n" +
                                 "-------------------------------------------------------------------------------------\n" +
                                 "Lat.:     25 <     50 <     75 <    100 <    150 <    200 <    250 <    300 <    300+\n" +
                                 "-------------------------------------------------------------------------------------\n" +
                                 "%%     %6.2f | %6.2f | %6.2f | %6.2f | %6.2f | %6.2f | %6.2f | %6.2f | %6.2f\n"
                                , this.cnt
                                , this.err > 0 ? String.format(" [!%,11d]", this.err) : ""
                                , (this.cnt*1000d / elapsedDuration)
                                , this.min == 999999999l ? 0l : this.min
                                , (double)this.tot/(double)this.cnt
                                , this.max == -1l ? 0l : this.max
                                , 100*(double)coarseLat[0]/this.cnt
                                , 100*(double)coarseLat[1]/this.cnt
                                , 100*(double)coarseLat[2]/this.cnt
                                , 100*(double)coarseLat[3]/this.cnt
                                , 100*(double)coarseLat[4]/this.cnt
                                , 100*(double)coarseLat[5]/this.cnt
                                , 100*(double)coarseLat[6]/this.cnt
                                , 100*(double)coarseLat[7]/this.cnt
                                , 100*(double)coarseLat[8]/this.cnt
                                );
        }
    }

    /**
     * Format the statistics into a delimiter separated string.
     *
     * Currently, the format is
     * "start (ms), end (ms), total proc calls, min lat., max lat., lat. buckets..."
     *
     * @param delimiter Delimiter to separate values, e.g. ',' or '\t'
     * @return
     */
    public String toRawString(char delimiter)
    {
        StringBuilder sb = new StringBuilder();
        sb.append(getStartTime())
          .append(delimiter)
          .append(getEndTime())
          .append(delimiter)
          .append(getExecutionCount())
          .append(delimiter)
          .append(getMinLatency())
          .append(delimiter)
          .append(getMaxLatency());
        // There are 109 buckets
        for (long latency : getLatencyBuckets()) {
            sb.append(delimiter).append(latency);
        }

        return sb.toString();
    }

    /**
     * Clones this counter to provide a snapshot copy of statistics.
     *
     * @return the snapshot clone of this counter.
     */
    @Override
    public PerfCounter clone()
    {
        PerfCounter counter = new PerfCounter(false);
        counter.StartTime = this.StartTime;
        counter.EndTime = this.EndTime;
        counter.min = this.min;
        counter.max = this.max;
        counter.tot = this.tot;
        counter.cnt = this.cnt;
        counter.err = this.err;
        for(int i=0;i<9;i++)
            counter.lat[i] = this.lat[i];
        return counter;
    }

    /**
     * Merges the statistics of this counter with another counter.
     *
     * @param other the counter to merge statistics from.
     * @return the reference to the current counter after modification.  Useful for command-chaining.
     */
    public PerfCounter merge(PerfCounter other)
    {
        lock.lock();
        try
        {
            this.StartTime = Math.min(this.StartTime, other.StartTime);
            this.EndTime = Math.max(this.EndTime, other.EndTime);
            this.min = Math.min(this.min,other.min);
            this.max = Math.max(this.max,other.max);
            this.tot = this.tot+other.tot;
            this.cnt = this.cnt+other.cnt;
            this.err = this.err+other.err;
            for(int i=0;i<109;i++)
                this.lat[i] = this.lat[i]+other.lat[i];
        }
        finally
        {
            lock.unlock();
        }
        return this;
    }

    /**
     * Merges the statistics of a list of counters.
     *
     * @param counters the list of counters to merge statistics from.
     * @return the new counter containing aggregated statistics from all the provided counters.
     */
    public static PerfCounter merge(PerfCounter[] counters)
    {
        PerfCounter counter = counters[0].clone();
        for(int i=1;i




© 2015 - 2024 Weber Informatics LLC | Privacy Policy