Shared.java

/*
 *      Copyright (C) 2014 Robert Stupp, Koeln, Germany, robert-stupp.de
 *
 *   Licensed 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.caffinitas.ohc.benchmark;

import java.io.PrintStream;
import java.lang.management.ManagementFactory;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.Notification;
import javax.management.NotificationListener;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import javax.management.openmbean.CompositeDataSupport;

import com.codahale.metrics.Snapshot;
import org.caffinitas.ohc.OHCache;

final class Shared
{
    private static final double NANOS_PER_MILLI = 1000000d;
    static OHCache<Long, byte[]> cache;

    static final AtomicBoolean fatal = new AtomicBoolean();
    static MergeableTimer[] timers = new MergeableTimer[]{ new MergeableTimer(), new MergeableTimer() };
    static final ConcurrentHashMap<String, GCStats> gcStats = new ConcurrentHashMap<>();

    static
    {
        MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer();
        NotificationListener gcListener = new NotificationListener()
        {
            public void handleNotification(Notification notification, Object handback)
            {
                CompositeDataSupport userData = (CompositeDataSupport) notification.getUserData();
                String gcName = (String) userData.get("gcName");
                CompositeDataSupport gcInfo = (CompositeDataSupport) userData.get("gcInfo");
                Long duration = (Long) gcInfo.get("duration");
                GCStats stats = gcStats.get(gcName);
                if (stats == null)
                {
                    GCStats ex = gcStats.putIfAbsent(gcName, stats = new GCStats());
                    if (ex != null)
                        stats = ex;
                }
                stats.count.incrementAndGet();
                stats.duration.addAndGet(duration);
                Number gcThreadCount = (Number) gcInfo.get("GcThreadCount");
                if (gcThreadCount != null && gcThreadCount.intValue() > stats.cores)
                    stats.cores = gcThreadCount.intValue();
            }
        };
        try
        {
            for (ObjectInstance inst : mbeanServer.queryMBeans(ObjectName.getInstance("java.lang:type=GarbageCollector,name=*"), null))
                mbeanServer.addNotificationListener(inst.getObjectName(), gcListener, null, null);
        }
        catch (MalformedObjectNameException | InstanceNotFoundException e)
        {
            throw new RuntimeException(e);
        }
    }

    static void clearStats()
    {
        timers[0] = new MergeableTimer();
        timers[1] = new MergeableTimer();
        gcStats.clear();
        cache.resetStatistics();
    }

    static final class GCStats
    {
        final AtomicLong count = new AtomicLong();
        final AtomicLong duration = new AtomicLong();
        int cores;
    }

    static void printStats(String title, boolean bucketHistogram, PrintStream csv)
    {
        if (bucketHistogram)
            System.out.printf("%s%n     %s%n" +
                              "   Histogram:%n%s%n", title, cache.stats(), cache.getBucketHistogram());
        else
            System.out.printf("%s%n     %s%n", title, cache.stats());
        for (Map.Entry<String, GCStats> gcStat : gcStats.entrySet())
        {
            GCStats gs = gcStat.getValue();
            long count = gs.count.longValue();
            long duration = gs.duration.longValue();
            double runtimeAvg = ((double) duration) / count;
            System.out.printf("     GC  %-15s : count: %8d    duration: %8dms (avg:%6.2fms)    cores: %d%n",
                              gcStat.getKey(),
                              count, duration, runtimeAvg, gs.cores);
        }

        if (csv != null)
            csv.print(timers[0].runtime());

        dumpStats(timers[0], "Reads", csv);
        dumpStats(timers[1], "Writes", csv);

        if (csv != null)
            csv.println();
    }

    private static void dumpStats(MergeableTimer timer, String header, PrintStream csv)
    {
        Snapshot snap = timer.histogram.getSnapshot();
        double snapMean = snap.getMean() / NANOS_PER_MILLI;
        double snapStdDev = snap.getStdDev() / NANOS_PER_MILLI;
        double snap75 = snap.get75thPercentile() / NANOS_PER_MILLI;
        double snap95 = snap.get95thPercentile() / NANOS_PER_MILLI;
        double snap98 = snap.get98thPercentile() / NANOS_PER_MILLI;
        double snap99 = snap.get99thPercentile() / NANOS_PER_MILLI;
        double snap999 = snap.get999thPercentile() / NANOS_PER_MILLI;
        double snapMedian = snap.getMedian() / NANOS_PER_MILLI;
        double snapMax = (double) snap.getMax() / NANOS_PER_MILLI;
        double snapMin = (double) snap.getMin() / NANOS_PER_MILLI;
        double meanRate = timer.meter.getMeanRate();
        long count = timer.meter.getCount();
        double oneMinuteRate = timer.meter.getOneMinuteRate();
        double fiveMinuteRate = timer.meter.getFiveMinuteRate();
        double fifteenMinuteRate = timer.meter.getFifteenMinuteRate();
        System.out.printf("     %-10s: one/five/fifteen/mean:  %.0f/%.0f/%.0f/%.0f%n" +
                          "                 count:                  %10d %n" +
                          "                 min/max/mean/stddev:    %8.5f/%8.5f/%8.5f/%8.5f%n" +
                          "                 75/95/98/99/999/median: %8.5f/%8.5f/%8.5f/%8.5f/%8.5f/%8.5f%n",
                          header,
                          //
                          oneMinuteRate, fiveMinuteRate, fifteenMinuteRate, meanRate,
                          //
                          count,
                          //
                          snapMin, snapMax, snapMean, snapStdDev,
                          snap75, snap95, snap98, snap99, snap999, snapMedian);

        if (csv != null)
            csv.printf(";%d;%.0f;%.0f;%.0f;%.0f;%.5f;%.5f;%.5f;%.5f;%.5f;%.5f;%.5f;%.5f;%.5f;%.5f",
                       count,
                       oneMinuteRate, fiveMinuteRate, fifteenMinuteRate, meanRate,
                       snapMin, snapMax, snapMean, snapStdDev,
                       snap75, snap95, snap98, snap99, snap999, snapMedian);
    }
}