| /** |
| * 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 java.util.EnumSet; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.TreeSet; |
| |
| import org.apache.hadoop.classification.InterfaceAudience; |
| import org.apache.hadoop.util.Time; |
| |
| /** |
| * StartupProgressView is an immutable, consistent, read-only view of namenode |
| * startup progress. Callers obtain an instance by calling |
| * {@link StartupProgress#createView()} to clone current startup progress state. |
| * Subsequent updates to startup progress will not alter the view. This isolates |
| * the reader from ongoing updates and establishes a guarantee that the values |
| * returned by the view are consistent and unchanging across 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. |
| * |
| * Methods that return primitive long may return {@link Long#MIN_VALUE} as a |
| * sentinel value to indicate that the property is undefined. |
| */ |
| @InterfaceAudience.Private |
| public class StartupProgressView { |
| |
| private final Map<Phase, PhaseTracking> phases; |
| |
| /** |
| * Returns the sum of the counter values for all steps in the specified phase. |
| * |
| * @param phase Phase to get |
| * @return long sum of counter values for all steps |
| */ |
| public long getCount(Phase phase) { |
| long sum = 0; |
| for (Step step: getSteps(phase)) { |
| sum += getCount(phase, step); |
| } |
| return sum; |
| } |
| |
| /** |
| * Returns the counter value for the specified phase and step. |
| * |
| * @param phase Phase to get |
| * @param step Step to get |
| * @return long counter value for phase and step |
| */ |
| public long getCount(Phase phase, Step step) { |
| StepTracking tracking = getStepTracking(phase, step); |
| return tracking != null ? tracking.count.get() : 0; |
| } |
| |
| /** |
| * Returns overall elapsed time, calculated as time between start of loading |
| * fsimage and end of safemode. |
| * |
| * @return long elapsed time |
| */ |
| public long getElapsedTime() { |
| return getElapsedTime(phases.get(Phase.LOADING_FSIMAGE), |
| phases.get(Phase.SAFEMODE)); |
| } |
| |
| /** |
| * Returns elapsed time for the specified phase, calculated as (end - begin) if |
| * phase is complete or (now - begin) if phase is running or 0 if the phase is |
| * still pending. |
| * |
| * @param phase Phase to get |
| * @return long elapsed time |
| */ |
| public long getElapsedTime(Phase phase) { |
| return getElapsedTime(phases.get(phase)); |
| } |
| |
| /** |
| * Returns elapsed time for the specified phase and step, calculated as |
| * (end - begin) if step is complete or (now - begin) if step is running or 0 |
| * if the step is still pending. |
| * |
| * @param phase Phase to get |
| * @param step Step to get |
| * @return long elapsed time |
| */ |
| public long getElapsedTime(Phase phase, Step step) { |
| return getElapsedTime(getStepTracking(phase, step)); |
| } |
| |
| /** |
| * Returns the optional file name associated with the specified phase, possibly |
| * null. |
| * |
| * @param phase Phase to get |
| * @return String optional file name, possibly null |
| */ |
| public String getFile(Phase phase) { |
| return phases.get(phase).file; |
| } |
| |
| /** |
| * Returns overall percent complete, calculated by aggregating percent complete |
| * of all phases. This is an approximation that assumes all phases have equal |
| * running time. In practice, this isn't true, but there isn't sufficient |
| * information available to predict proportional weights for each phase. |
| * |
| * @return float percent complete |
| */ |
| public float getPercentComplete() { |
| if (getStatus(Phase.SAFEMODE) == Status.COMPLETE) { |
| return 1.0f; |
| } else { |
| float total = 0.0f; |
| int numPhases = 0; |
| for (Phase phase: phases.keySet()) { |
| ++numPhases; |
| total += getPercentComplete(phase); |
| } |
| return getBoundedPercent(total / numPhases); |
| } |
| } |
| |
| /** |
| * Returns percent complete for the specified phase, calculated by aggregating |
| * the counter values and totals for all steps within the phase. |
| * |
| * @param phase Phase to get |
| * @return float percent complete |
| */ |
| public float getPercentComplete(Phase phase) { |
| if (getStatus(phase) == Status.COMPLETE) { |
| return 1.0f; |
| } else { |
| long total = getTotal(phase); |
| long count = 0; |
| for (Step step: getSteps(phase)) { |
| count += getCount(phase, step); |
| } |
| return total > 0 ? getBoundedPercent(1.0f * count / total) : 0.0f; |
| } |
| } |
| |
| /** |
| * Returns percent complete for the specified phase and step, calculated as |
| * counter value divided by total. |
| * |
| * @param phase Phase to get |
| * @param step Step to get |
| * @return float percent complete |
| */ |
| public float getPercentComplete(Phase phase, Step step) { |
| if (getStatus(phase) == Status.COMPLETE) { |
| return 1.0f; |
| } else { |
| long total = getTotal(phase, step); |
| long count = getCount(phase, step); |
| return total > 0 ? getBoundedPercent(1.0f * count / total) : 0.0f; |
| } |
| } |
| |
| /** |
| * Returns all phases. |
| * |
| * @return Iterable<Phase> containing all phases |
| */ |
| public Iterable<Phase> getPhases() { |
| return EnumSet.allOf(Phase.class); |
| } |
| |
| /** |
| * Returns all steps within a phase. |
| * |
| * @param phase Phase to get |
| * @return Iterable<Step> all steps |
| */ |
| public Iterable<Step> getSteps(Phase phase) { |
| return new TreeSet<Step>(phases.get(phase).steps.keySet()); |
| } |
| |
| /** |
| * Returns the optional size in bytes associated with the specified phase, |
| * possibly Long.MIN_VALUE if undefined. |
| * |
| * @param phase Phase to get |
| * @return long optional size in bytes, possibly Long.MIN_VALUE |
| */ |
| public long getSize(Phase phase) { |
| return phases.get(phase).size; |
| } |
| |
| /** |
| * 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 the sum of the totals for all steps in the specified phase. |
| * |
| * @param phase Phase to get |
| * @return long sum of totals for all steps |
| */ |
| public long getTotal(Phase phase) { |
| long sum = 0; |
| for (StepTracking tracking: phases.get(phase).steps.values()) { |
| if (tracking.total != Long.MIN_VALUE) { |
| sum += tracking.total; |
| } |
| } |
| return sum; |
| } |
| |
| /** |
| * Returns the total for the specified phase and step. |
| * |
| * @param phase Phase to get |
| * @param step Step to get |
| * @return long total |
| */ |
| public long getTotal(Phase phase, Step step) { |
| StepTracking tracking = getStepTracking(phase, step); |
| return tracking != null && tracking.total != Long.MIN_VALUE ? |
| tracking.total : 0; |
| } |
| |
| /** |
| * Creates a new StartupProgressView by cloning data from the specified |
| * StartupProgress. |
| * |
| * @param prog StartupProgress to clone |
| */ |
| StartupProgressView(StartupProgress prog) { |
| phases = new HashMap<Phase, PhaseTracking>(); |
| for (Map.Entry<Phase, PhaseTracking> entry: prog.phases.entrySet()) { |
| phases.put(entry.getKey(), entry.getValue().clone()); |
| } |
| } |
| |
| /** |
| * Returns elapsed time, calculated as (end - begin) if both are defined or |
| * (now - begin) if end is undefined or 0 if both are undefined. Begin and end |
| * time come from the same AbstractTracking instance. |
| * |
| * @param tracking AbstractTracking containing begin and end time |
| * @return long elapsed time |
| */ |
| private long getElapsedTime(AbstractTracking tracking) { |
| return getElapsedTime(tracking, tracking); |
| } |
| |
| /** |
| * Returns elapsed time, calculated as (end - begin) if both are defined or |
| * (now - begin) if end is undefined or 0 if both are undefined. Begin and end |
| * time may come from different AbstractTracking instances. |
| * |
| * @param beginTracking AbstractTracking containing begin time |
| * @param endTracking AbstractTracking containing end time |
| * @return long elapsed time |
| */ |
| private long getElapsedTime(AbstractTracking beginTracking, |
| AbstractTracking endTracking) { |
| final long elapsed; |
| if (beginTracking != null && beginTracking.beginTime != Long.MIN_VALUE && |
| endTracking != null && endTracking.endTime != Long.MIN_VALUE) { |
| elapsed = endTracking.endTime - beginTracking.beginTime; |
| } else if (beginTracking != null && |
| beginTracking.beginTime != Long.MIN_VALUE) { |
| elapsed = Time.monotonicNow() - beginTracking.beginTime; |
| } else { |
| elapsed = 0; |
| } |
| return Math.max(0, elapsed); |
| } |
| |
| /** |
| * Returns the StepTracking internal data structure for the specified phase |
| * and step, possibly null if not found. |
| * |
| * @param phase Phase to get |
| * @param step Step to get |
| * @return StepTracking for phase and step, possibly null |
| */ |
| private StepTracking getStepTracking(Phase phase, Step step) { |
| PhaseTracking phaseTracking = phases.get(phase); |
| Map<Step, StepTracking> steps = phaseTracking != null ? |
| phaseTracking.steps : null; |
| return steps != null ? steps.get(step) : null; |
| } |
| |
| /** |
| * Returns the given value restricted to the range [0.0, 1.0]. |
| * |
| * @param percent float value to restrict |
| * @return float value restricted to range [0.0, 1.0] |
| */ |
| private static float getBoundedPercent(float percent) { |
| return Math.max(0.0f, Math.min(1.0f, percent)); |
| } |
| } |