blob: 6249a84e7f93f50b3526a40b9bfa241feec4f4d2 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.hadoop.hdfs.server.namenode.startupprogress;
import static org.apache.hadoop.util.Time.monotonicNow;
import java.util.EnumSet;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.apache.hadoop.classification.InterfaceAudience;
/**
* StartupProgress is used in various parts of the namenode codebase to indicate
* startup progress. Its methods provide ways to indicate begin and end of a
* {@link Phase} or {@link Step} within a phase. Additional methods provide ways
* to associate a step or phase with optional information, such as a file name or
* file size. It also provides counters, which can be incremented by the caller
* to indicate progress through a long-running task.
*
* This class is thread-safe. Any number of threads may call any methods, even
* for the same phase or step, without risk of corrupting internal state. For
* all begin/end methods and set methods, the last one in wins, overwriting any
* prior writes. Instances of {@link Counter} provide an atomic increment
* operation to prevent lost updates.
*
* After startup completes, the tracked data is frozen. Any subsequent updates
* or counter increments are no-ops.
*
* For read access, call {@link #createView()} to create a consistent view with
* a clone of the data.
*/
@InterfaceAudience.Private
public class StartupProgress {
// package-private for access by StartupProgressView
final Map<Phase, PhaseTracking> phases =
new ConcurrentHashMap<Phase, PhaseTracking>();
/**
* Allows a caller to increment a counter for tracking progress.
*/
public static interface Counter {
/**
* Atomically increments this counter, adding 1 to the current value.
*/
void increment();
}
/**
* Creates a new StartupProgress by initializing internal data structure for
* tracking progress of all defined phases.
*/
public StartupProgress() {
for (Phase phase: EnumSet.allOf(Phase.class)) {
phases.put(phase, new PhaseTracking());
}
}
/**
* Begins execution of the specified phase.
*
* @param phase Phase to begin
*/
public void beginPhase(Phase phase) {
if (!isComplete()) {
phases.get(phase).beginTime = monotonicNow();
}
}
/**
* Begins execution of the specified step within the specified phase. This is
* a no-op if the phase is already completed.
*
* @param phase Phase within which the step should be started
* @param step Step to begin
*/
public void beginStep(Phase phase, Step step) {
if (!isComplete(phase)) {
lazyInitStep(phase, step).beginTime = monotonicNow();
}
}
/**
* Ends execution of the specified phase.
*
* @param phase Phase to end
*/
public void endPhase(Phase phase) {
if (!isComplete()) {
phases.get(phase).endTime = monotonicNow();
}
}
/**
* Ends execution of the specified step within the specified phase. This is
* a no-op if the phase is already completed.
*
* @param phase Phase within which the step should be ended
* @param step Step to end
*/
public void endStep(Phase phase, Step step) {
if (!isComplete(phase)) {
lazyInitStep(phase, step).endTime = monotonicNow();
}
}
/**
* Returns the current run status of the specified phase.
*
* @param phase Phase to get
* @return Status run status of phase
*/
public Status getStatus(Phase phase) {
PhaseTracking tracking = phases.get(phase);
if (tracking.beginTime == Long.MIN_VALUE) {
return Status.PENDING;
} else if (tracking.endTime == Long.MIN_VALUE) {
return Status.RUNNING;
} else {
return Status.COMPLETE;
}
}
/**
* Returns a counter associated with the specified phase and step. Typical
* usage is to increment a counter within a tight loop. Callers may use this
* method to obtain a counter once and then increment that instance repeatedly
* within a loop. This prevents redundant lookup operations and object
* creation within the tight loop. Incrementing the counter is an atomic
* operation, so there is no risk of lost updates even if multiple threads
* increment the same counter.
*
* @param phase Phase to get
* @param step Step to get
* @return Counter associated with phase and step
*/
public Counter getCounter(Phase phase, Step step) {
if (!isComplete(phase)) {
final StepTracking tracking = lazyInitStep(phase, step);
return new Counter() {
@Override
public void increment() {
tracking.count.incrementAndGet();
}
};
} else {
return new Counter() {
@Override
public void increment() {
// no-op, because startup has completed
}
};
}
}
/**
* Sets counter to the specified value.
*
* @param phase Phase to set
* @param step Step to set
* @param count long to set
*/
public void setCount(Phase phase, Step step, long count) {
lazyInitStep(phase, step).count.set(count);
}
/**
* Sets the optional file name associated with the specified phase. For
* example, this can be used while loading fsimage to indicate the full path to
* the fsimage file.
*
* @param phase Phase to set
* @param file String file name to set
*/
public void setFile(Phase phase, String file) {
if (!isComplete()) {
phases.get(phase).file = file;
}
}
/**
* Sets the optional size in bytes associated with the specified phase. For
* example, this can be used while loading fsimage to indicate the size of the
* fsimage file.
*
* @param phase Phase to set
* @param size long to set
*/
public void setSize(Phase phase, long size) {
if (!isComplete()) {
phases.get(phase).size = size;
}
}
/**
* Sets the total associated with the specified phase and step. For example,
* this can be used while loading edits to indicate the number of operations to
* be applied.
*
* @param phase Phase to set
* @param step Step to set
* @param total long to set
*/
public void setTotal(Phase phase, Step step, long total) {
if (!isComplete(phase)) {
lazyInitStep(phase, step).total = total;
}
}
/**
* Creates a {@link StartupProgressView} containing data cloned from this
* StartupProgress. Subsequent updates to this StartupProgress will not be
* shown in the view. This gives a consistent, unchanging view for callers
* that need to perform multiple related read operations. Calculations that
* require aggregation, such as overall percent complete, will not be impacted
* by mutations performed in other threads mid-way through the calculation.
*
* @return StartupProgressView containing cloned data
*/
public StartupProgressView createView() {
return new StartupProgressView(this);
}
/**
* Returns true if the entire startup process has completed, determined by
* checking if each phase is complete.
*
* @return boolean true if the entire startup process has completed
*/
private boolean isComplete() {
return EnumSet.allOf(Phase.class).stream().allMatch(this::isComplete);
}
/**
* Returns true if the given startup phase has been completed.
*
* @param phase Which phase to check for completion
* @return boolean true if the given startup phase has completed.
*/
private boolean isComplete(Phase phase) {
return getStatus(phase) == Status.COMPLETE;
}
/**
* Lazily initializes the internal data structure for tracking the specified
* phase and step. Returns either the newly initialized data structure or the
* existing one. Initialization is atomic, so there is no risk of lost updates
* even if multiple threads attempt to initialize the same step simultaneously.
*
* @param phase Phase to initialize
* @param step Step to initialize
* @return StepTracking newly initialized, or existing if found
*/
private StepTracking lazyInitStep(Phase phase, Step step) {
ConcurrentMap<Step, StepTracking> steps = phases.get(phase).steps;
if (!steps.containsKey(step)) {
steps.putIfAbsent(step, new StepTracking());
}
return steps.get(step);
}
}