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

com.sleepycat.je.dbi.StartupTracker Maven / Gradle / Ivy

The newest version!
/*-
 * Copyright (C) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
 *
 * This file was distributed by Oracle as part of a version of Oracle Berkeley
 * DB Java Edition made available at:
 *
 * http://www.oracle.com/technetwork/database/database-technologies/berkeleydb/downloads/index.html
 *
 * Please see the LICENSE file included in the top-level directory of the
 * appropriate version of Oracle Berkeley DB Java Edition for a copy of the
 * license and additional information.
 */

package com.sleepycat.je.dbi;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.util.EnumMap;
import java.util.Formatter;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.sleepycat.je.EnvironmentFailureException;
import com.sleepycat.je.ProgressListener;
import com.sleepycat.je.RecoveryProgress;
import com.sleepycat.je.config.EnvironmentParams;
import com.sleepycat.je.recovery.CheckpointEnd;
import com.sleepycat.je.recovery.RecoveryInfo;
import com.sleepycat.je.utilint.DbLsn;
import com.sleepycat.je.utilint.LoggerUtils;
import com.sleepycat.je.utilint.StatGroup;

/**
 * Store and calculate elapsed time, counts, and other statistics about
 * environment open. No synchronization is used, which generally works because
 * with the exception of replication, environment startup is currently a
 * serial, single threaded event. Replicated environments must be sure to
 * record startup times only at thread safe points.
 */
public class StartupTracker {

    /* 
     * Statistics are kept about startup phases, defined below. Phases can
     * be nested, so the child and root fields are used to express this
     * relationship. For example:
     *  TotalEnvOpen
     *      TotalRecovery
     *         FindEndOfLog
     *          ..
     *         BuildTree
     *             ReadMapIN 
     *             ..
     *         Ckpt
     *  TotalJoinGroup encompasses the following two phases. 
     *      FindMaster
     *      BecomeConsistent
     * Keep these enums in order of execution, so that the display is easier to
     * comprehend. Of course, some phases subsume other phases, but in general,
     * this enum order follows the order of execution.
     */
    public enum Phase {
            TOTAL_ENV_OPEN("Environment Open"),
            TOTAL_RECOVERY,
            FIND_END_OF_LOG,
            FIND_LAST_CKPT, 
            BUILD_TREE,
            READ_MAP_INS,
            REDO_MAP_INS,
            UNDO_MAP_LNS,
            REDO_MAP_LNS,
            READ_INS,
            REDO_INS,
            UNDO_LNS,
            REDO_LNS,
            FINISH_VERIFICATION,
            POPULATE_UP,
            POPULATE_EP,
            REMOVE_TEMP_DBS,
            CKPT,
            TOTAL_JOIN_GROUP("Replication Join Group"),
            FIND_MASTER,
            BECOME_CONSISTENT;

        private Phase[] children;
        private Phase root;
        private String reportLabel;

        private Phase() {
        }

        private Phase(String reportLabel) {
            this.reportLabel = reportLabel;
        }

        static {
            TOTAL_ENV_OPEN.children = new Phase[] 
                {TOTAL_RECOVERY};
            TOTAL_RECOVERY.children = new Phase[] 
                {FIND_END_OF_LOG,
                 FIND_LAST_CKPT,
                 BUILD_TREE,
                 FINISH_VERIFICATION,
                 POPULATE_UP,
                 POPULATE_EP,
                 REMOVE_TEMP_DBS,
                 CKPT};
            BUILD_TREE.children = new Phase[] 
                {READ_MAP_INS,
                 REDO_MAP_INS,
                 UNDO_MAP_LNS,
                 REDO_MAP_LNS,
                 READ_INS,
                 REDO_INS,
                 UNDO_LNS,
                 REDO_LNS};
            TOTAL_JOIN_GROUP.children = new Phase[] 
                {FIND_MASTER, 
                 BECOME_CONSISTENT};

            TOTAL_RECOVERY.root = TOTAL_ENV_OPEN;
            FIND_END_OF_LOG.root = TOTAL_ENV_OPEN;
            FIND_LAST_CKPT.root = TOTAL_ENV_OPEN; 
            BUILD_TREE.root = TOTAL_ENV_OPEN;
            READ_MAP_INS.root = TOTAL_ENV_OPEN;
            REDO_MAP_INS.root = TOTAL_ENV_OPEN;
            UNDO_MAP_LNS.root = TOTAL_ENV_OPEN;
            REDO_MAP_LNS.root = TOTAL_ENV_OPEN;
            READ_INS.root = TOTAL_ENV_OPEN;
            REDO_INS.root = TOTAL_ENV_OPEN;
            UNDO_LNS.root = TOTAL_ENV_OPEN;
            REDO_LNS.root = TOTAL_ENV_OPEN;
            FINISH_VERIFICATION.root = TOTAL_ENV_OPEN;
            POPULATE_UP.root = TOTAL_ENV_OPEN;
            POPULATE_EP.root = TOTAL_ENV_OPEN;
            REMOVE_TEMP_DBS.root = TOTAL_ENV_OPEN;
            CKPT.root = TOTAL_ENV_OPEN;

            FIND_MASTER.root = TOTAL_JOIN_GROUP;
            BECOME_CONSISTENT.root = TOTAL_JOIN_GROUP;
        }
    }

    private final Map elapsed;
    private final Map counters;
    private final Map stats;
    private final Logger logger;
    private final EnvironmentImpl envImpl;
    private RecoveryInfo info;
    private long lastDumpMillis;

    public StartupTracker(EnvironmentImpl envImpl) {

        elapsed = new EnumMap(Phase.class);
        counters = new EnumMap(Phase.class);
        stats = new EnumMap(Phase.class);
        for (Phase p : Phase.values()){
            elapsed.put(p, new Elapsed());
        }

        this.envImpl = envImpl;

        logger = LoggerUtils.getLogger(getClass());
        lastDumpMillis = System.currentTimeMillis();
    }

    public void setRecoveryInfo(RecoveryInfo rInfo) {
        info = rInfo;
    }

    /**
     * Note that a particular phase is starting. 
     */
    public void start(Phase phase) {
        String msg = "Starting " + phase;
        if (info != null) {
            msg += " " + info;
        }
        LoggerUtils.logMsg(logger, envImpl, Level.CONFIG, msg);

        elapsed.get(phase).start();
        Counter c = new Counter();
        counters.put(phase, c);       
        if (!phase.equals(Phase.TOTAL_ENV_OPEN)) {

            /* 
             * LogManager does not exist yet so we can't reference it. Anyway,
             * cache misses are 0 to start with, so TOTAL_ENV_OPEN does not 
             * have to set the starting cache miss count.
             */
            c.setCacheMissStart(envImpl.getLogManager().getNCacheMiss());
        }
    }
    
    /**
     * Note that a particular phase is ending.
     */
    public void stop(Phase phase) {
        Elapsed e = elapsed.get(phase);
        e.end();
        Counter c = getCounter(phase);
        c.setCacheMissEnd(envImpl.getLogManager().getNCacheMiss());

        /* Log this phase to the je.info file. */
        String msg = "Stopping " + phase;
        if (info != null) {
            msg += " " + info;
        }
        LoggerUtils.logMsg(logger, envImpl, Level.CONFIG, msg);

        /*
         * Conditionally log the whole report to the je.info file, either 
         * because this family of phases has ended, or because this startup
         * is taking a very long time.
         *
         * Take care to only check the value of dumpThreshold here, rather than
         * setting it in the StartupTracker constructor, because StartupTracker
         * is instantiated before the DbConfigManager and the
         * STARTUP_DUMP_THRESHOLD param cannot be read.
         */
        int dumpThreshold = envImpl.getConfigManager().getDuration
            (EnvironmentParams.STARTUP_DUMP_THRESHOLD);

        /* We're at the end of a family of phases. */
        if (phase.root == null) {
            if ((e.getEnd() - e.getStart()) > dumpThreshold) {
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                PrintStream p = new PrintStream(baos);
                displayStats(p, phase);
                LoggerUtils.logMsg(logger, envImpl, Level.INFO,
                                   baos.toString());
                return;
            }
        }
         
        /* 
         * It's not the ending phase, but this has been taking a very long
         * time, so dump some information.
         */
         if ((System.currentTimeMillis() - lastDumpMillis) > dumpThreshold) {
             ByteArrayOutputStream baos = new ByteArrayOutputStream();
             PrintStream p = new PrintStream(baos);
             displayInterim(p, phase);
             LoggerUtils.logMsg(logger, envImpl, Level.INFO, baos.toString());
         }
    }

    /**
     * Record new progress states for any registered environment progress
     * listener.
     */
    public void setProgress(RecoveryProgress progress) {
        ProgressListener progressListener = 
            envImpl.getRecoveryProgressListener();

        if (progressListener == null) {
            return;
        }
        if (!progressListener.progress(progress, -1, -1)) {
            throw new EnvironmentFailureException
              (envImpl, EnvironmentFailureReason.PROGRESS_LISTENER_HALT,
              "EnvironmentConfig.recoveryProgressListener: ");      
        }
    }

    /**
     * Return the counter for this phase so we can update one of the detail
     * values stored there.
     */
    public Counter getCounter(Phase phase) {
        return counters.get(phase);
    }

    /** 
     * Save stats for a given phase. 
     */
    public void setStats(Phase phase, StatGroup sg) {
        stats.put(phase, sg);
    }

    /** 
     * Generate a description of the four recovery locations (firstActive,
     * ckptStart, ckptend, end of Log) and the distance inbetween.
     */
    private String displayRecoveryInterval() {
        StringBuilder returnInfo = new StringBuilder();

        CheckpointEnd cEnd = info.checkpointEnd;
        if (cEnd != null) {
            returnInfo.append("checkpointId = ");
            returnInfo.append(cEnd.getId());
            if (cEnd.getInvoker() == null) {
                returnInfo.append(" ");
            } else {
                returnInfo.append("[").append(cEnd.getInvoker());
                returnInfo.append("] ");
            }
        }

        long fileMax =
            envImpl.getConfigManager().getLong(EnvironmentParams.LOG_FILE_MAX);

        long useStart = info.checkpointStartLsn == DbLsn.NULL_LSN ?
            0 : info.checkpointStartLsn;
        long head = DbLsn.getNoCleaningDistance(useStart,
                                                info.firstActiveLsn,
                                                fileMax);

        long useEnd = info.checkpointEndLsn == DbLsn.NULL_LSN ?
            0 : info.checkpointEndLsn;
        long ckpt = DbLsn.getNoCleaningDistance(useEnd,
                                                info.checkpointStartLsn,
                                                fileMax);

        long useLast = info.lastUsedLsn == DbLsn.NULL_LSN ?
            0 : info.lastUsedLsn;
        long tail = DbLsn.getNoCleaningDistance(useLast,
                                                info.checkpointEndLsn,
                                                fileMax);
        returnInfo.append(
            "firstActive[" + 
            DbLsn.getNoFormatString(info.firstActiveLsn) +
            "], ckptStart[" +
            DbLsn.getNoFormatString(info.checkpointStartLsn) +
            "], ckptEnd[" +
            DbLsn.getNoFormatString(info.checkpointEndLsn) +
            "], lastUsed[" +
            DbLsn.getNoFormatString(info.lastUsedLsn) +
            "]\n");
        StringBuilder sb = new StringBuilder();
        Formatter f = new Formatter(sb);
        f.format("%24s bytes = %,d\n%24s bytes = %,d\n%24s bytes = %,d",
                 "firstActive->ckptStart", head,
                 "ckptStart->ckptEnd", ckpt,
                 "ckptEnd->end bytes", tail);
        
        return returnInfo.toString() + "\nApproximate distances:\n" + 
            sb.toString();
    }

    private String displayTimestamp(Long time) {
        StringBuilder sb = new StringBuilder();
        Formatter timestampFormatter = new Formatter(sb);
        timestampFormatter.format("%tD,%tH:%tM:%tS:%tL", 
                                  time, time, time, time, time);
        return sb.toString();
    }

    /**
     * Display a phase and its children, showing elapsed time as a  
     * percentage of the phases' root.
     */
    private void displayPhaseSubtree(PrintStream stream,
                                     Phase parent,
                                     Elapsed parentTime,
                                     Elapsed rootElapsed) {

        String headerFormat = "%24s  %% of total  %s\n";
        String parentFormat = "%20s             %3d %s\n";
        String dataFormat   = "%24s         %3d %s\n";
        String divider = "                         "+
            "-------------------------";

        if (parent.children == null) {
            return;
        }

        if ((parentTime.getEnd() - parentTime.getStart()) ==0) {
            return;
        }

        stream.println("\n");
        stream.printf(headerFormat, " ", Elapsed.DISPLAY_COLUMNS);
        stream.printf(parentFormat, parent, 
                      parentTime.getPercentage(rootElapsed), parentTime);
        stream.println(divider);

        for (Phase child : parent.children) {
            Elapsed time = elapsed.get(child);
            if (time.getStart() == 0) {
                continue;
            }
            stream.printf(dataFormat, 
                          child,
                          time.getPercentage(rootElapsed),
                          time);
        }
    }

    private void displayCounters(PrintStream stream, Phase root) {
        String basicFormat = "%20s   %s\n";
        boolean headerNotPrinted = true;
        for (Map.Entry c : counters.entrySet()) {
            Phase p = c.getKey();
            if (p.root != root) {
                continue;
            }
            Counter counter = c.getValue();
            if (counter.isEmpty()) {
                continue;
            }

            if (headerNotPrinted) {
                stream.println();
                stream.printf(basicFormat, " " , Counter.DISPLAY_COLUMNS);
                headerNotPrinted = false;
            }
            stream.printf(basicFormat, c.getKey(), counter);
        }
    }
    
    /**
     * Display all information that has been tracked for this family of
     * phases.
     */
    public void displayStats(PrintStream stream, Phase root ) {
        lastDumpMillis = System.currentTimeMillis();
        Elapsed rootTime = elapsed.get(root);

        stream.println("\n=== " + root.reportLabel + " Report  ===");
        stream.println("start = " + displayTimestamp(rootTime.getStart()));
        stream.println("end   = " + displayTimestamp(rootTime.getEnd()));
        if (root == Phase.TOTAL_ENV_OPEN) {
            stream.print(displayRecoveryInterval());
        }

        /* Elapsed time. */
        for (Map.Entry x : elapsed.entrySet()) {
            Phase p = x.getKey();
            if (p.root == null) {
               if (p != root) {
                   continue;
               }
            } else if (p.root != root) {
               continue;
            }
            
            displayPhaseSubtree(stream, x.getKey(),x.getValue(), rootTime);
        }

        /* Counters */
        displayCounters(stream, root);

        /* Stats */
        for (Map.Entry s : stats.entrySet()) {
            Phase p = s.getKey();
            if (p.root != root) {
                continue;
            }
            stream.println(s.getKey() + " stats:");
            stream.println(s.getValue());
        }
    }

    /**
     * Display all information available so far.
     */
    private void displayInterim(PrintStream stream, Phase phase ) {
        lastDumpMillis = System.currentTimeMillis();

        stream.println("\n=== Interim " + phase + " Report  ===");

        stream.println(displayRecoveryInterval());

        /* Elapsed time. */
        boolean headerNotPrinted = true;
        for (Map.Entry x : elapsed.entrySet()) {
            Phase p = x.getKey();
            Elapsed e = x.getValue();
            if (e.start == 0) {
                continue;
            }
            if (headerNotPrinted) {
                stream.println("                             Elapsed(ms)");
                headerNotPrinted = false;
            }
            stream.printf("%20s : %s\n", p, e);
        }

        /* Counters */
        displayCounters(stream, phase.root);

        /* Stats */
        for (Map.Entry s : stats.entrySet()) {
            stream.println(s.getKey() + " stats:");
            stream.println(s.getValue());
        }
    }

    /** Measures elapsed time in millisecond granularity. */
    static private class Elapsed {

        /* For dumping elapsed values in a column */
        static String DISPLAY_COLUMNS = " Elapsed(ms)";

        private long start;
        private long end;

        public long getStart() {
            return start;
        }

        public long getEnd() {
            return end;
        }

        /* Mark the start of a phase. */
        private void start() {
            start = System.currentTimeMillis();
        }

        /* Mark the end of a phase. */
        private void end() {
            end = System.currentTimeMillis();
        }

        private int getPercentage(Elapsed rootTime) {
            if (rootTime == null) {
                return 0;
            } 

            long rootTotal = rootTime.end-rootTime.start;
            if (rootTotal <= 0) {
                return 0;
            }

            if (end == 0) {
                return 0;
            }
            return (int)(((float) (end-start)/ rootTotal) * 100);
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            Formatter f = new Formatter(sb);
            if (end != 0) {
                f.format("%,13d", (end-start));
            } else {
                if (start != 0) {
                    f.format("%13s  %tD,%tH:%tM:%tS:%tL",
                             "started at", start, start, start, start, start);
                } else {
                    f.format("%13s", "none");
                }
            }
            return sb.toString(); 
        }
    }

    /**
     * Record number of log entries processed during a given recovery phase.
     */
    static public class Counter {
        private int numRead;
        private int numProcessed;
        private int numDeleted;
        private int numAux;
        private long numRepeatIteratorReads;
        private long startCacheMiss;
        private long endCacheMiss;

        /* If nothing is set, don't print this one. */
        private boolean isEmpty() {
            return((numRead==0) &&
                   (numProcessed==0) &&
                   (numDeleted==0) &&
                   (numAux==0) &&
                   (numRepeatIteratorReads==0) &&
                   ((endCacheMiss-startCacheMiss)==0));
        }

        public void incNumRead() {
            numRead++;
        }

        public void setNumRead(int numRead) {
            this.numRead = numRead;
        }

        public void incNumProcessed() {
            numProcessed++;
        }

        public void setNumProcessed(int numProcessed) {
            this.numProcessed = numProcessed;
        }

        public void incNumDeleted() {
            numDeleted++;
        }

        public void setNumDeleted(int numDeleted) {
            this.numDeleted = numDeleted;
        }

        /**
         * Keep track of auxiliary log entries processed during this pass. 
         * For example, LNs are the main target of the undoLN pass, but we
         * also read aborts and commits.
         */
        public void incNumAux() {
            numAux++;
        }

        public void setRepeatIteratorReads(long repeats) {
            numRepeatIteratorReads = repeats;
        }

        public void setCacheMissStart(long miss) {
            startCacheMiss = miss;
        }

        public void setCacheMissEnd(long miss) {
            endCacheMiss = miss;
        }

        static String DISPLAY_COLUMNS =
"      nRead nProcessed   nDeleted       nAux  nRepeatRd nCacheMiss";
 
        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            Formatter f = new Formatter(sb);
            f.format("%,11d%,11d%,11d%,11d%,11d%,11d",
                     numRead, numProcessed, numDeleted, numAux, 
                     numRepeatIteratorReads, (endCacheMiss - startCacheMiss));
            return sb.toString();
        }

        public int getNumProcessed() {
            return numProcessed;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy