blob: 1090309a60d7a2164ec1e671bd035e9ae027094b [file] [log] [blame]
/*-
* 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<Phase, Elapsed> elapsed;
private final Map<Phase, Counter> counters;
private final Map<Phase, StatGroup> stats;
private final Logger logger;
private final EnvironmentImpl envImpl;
private RecoveryInfo info;
private long lastDumpMillis;
public StartupTracker(EnvironmentImpl envImpl) {
elapsed = new EnumMap<Phase, Elapsed>(Phase.class);
counters = new EnumMap<Phase, Counter>(Phase.class);
stats = new EnumMap<Phase, StatGroup>(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<RecoveryProgress> 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<Phase, Counter> 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<Phase, Elapsed> 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<Phase, StatGroup> 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<Phase, Elapsed> 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<Phase, StatGroup> 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;
}
}
}