/** * 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 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. * * @param phase Phase to begin * @param step Step to begin */ public void beginStep(Phase phase, Step step) { if (!isComplete()) { 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. * * @param phase Phase to end * @param step Step to end */ public void endStep(Phase phase, Step step) { if (!isComplete()) { 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) { final StepTracking tracking = lazyInitStep(phase, step); if (!isComplete()) { 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()) { 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() { for (Phase phase: EnumSet.allOf(Phase.class)) { if (getStatus(phase) != Status.COMPLETE) { return false; } } return true; } /** * 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); } }