/** * 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.util; import java.util.ArrayList; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.classification.InterfaceAudience; import org.apache.hadoop.classification.InterfaceStability; /** Utility to assist with generation of progress reports. Applications build * a hierarchy of {@link Progress} instances, each modelling a phase of * execution. The root is constructed with {@link #Progress()}. Nodes for * sub-phases are created by calling {@link #addPhase()}. */ @InterfaceAudience.LimitedPrivate({"MapReduce"}) @InterfaceStability.Unstable public class Progress { private static final Log LOG = LogFactory.getLog(Progress.class); private String status = ""; private float progress; private int currentPhase; private ArrayList<Progress> phases = new ArrayList<Progress>(); private Progress parent; // Each phase can have different progress weightage. For example, in // Map Task, map phase accounts for 66.7% and sort phase for 33.3%. // User needs to give weightages as parameters to all phases(when adding // phases) in a Progress object, if he wants to give weightage to any of the // phases. So when nodes are added without specifying weightage, it means // fixed weightage for all phases. private boolean fixedWeightageForAllPhases = false; private float progressPerPhase = 0.0f; private ArrayList<Float> progressWeightagesForPhases = new ArrayList<Float>(); /** Creates a new root node. */ public Progress() {} /** Adds a named node to the tree. */ public Progress addPhase(String status) { Progress phase = addPhase(); phase.setStatus(status); return phase; } /** Adds a node to the tree. Gives equal weightage to all phases */ public synchronized Progress addPhase() { Progress phase = addNewPhase(); // set equal weightage for all phases progressPerPhase = 1.0f / (float)phases.size(); fixedWeightageForAllPhases = true; return phase; } /** Adds a new phase. Caller needs to set progress weightage */ private synchronized Progress addNewPhase() { Progress phase = new Progress(); phases.add(phase); phase.setParent(this); return phase; } /** Adds a named node with a specified progress weightage to the tree. */ public Progress addPhase(String status, float weightage) { Progress phase = addPhase(weightage); phase.setStatus(status); return phase; } /** Adds a node with a specified progress weightage to the tree. */ public synchronized Progress addPhase(float weightage) { Progress phase = new Progress(); progressWeightagesForPhases.add(weightage); phases.add(phase); phase.setParent(this); // Ensure that the sum of weightages does not cross 1.0 float sum = 0; for (int i = 0; i < phases.size(); i++) { sum += progressWeightagesForPhases.get(i); } if (sum > 1.0) { LOG.warn("Sum of weightages can not be more than 1.0; But sum = " + sum); } return phase; } /** Adds n nodes to the tree. Gives equal weightage to all phases */ public synchronized void addPhases(int n) { for (int i = 0; i < n; i++) { addNewPhase(); } // set equal weightage for all phases progressPerPhase = 1.0f / (float)phases.size(); fixedWeightageForAllPhases = true; } /** * returns progress weightage of the given phase * @param phaseNum the phase number of the phase(child node) for which we need * progress weightage * @return returns the progress weightage of the specified phase */ float getProgressWeightage(int phaseNum) { if (fixedWeightageForAllPhases) { return progressPerPhase; // all phases are of equal weightage } return progressWeightagesForPhases.get(phaseNum); } synchronized Progress getParent() { return parent; } synchronized void setParent(Progress parent) { this.parent = parent; } /** Called during execution to move to the next phase at this level in the * tree. */ public synchronized void startNextPhase() { currentPhase++; } /** Returns the current sub-node executing. */ public synchronized Progress phase() { return phases.get(currentPhase); } /** Completes this node, moving the parent node to its next child. */ public void complete() { // we have to traverse up to our parent, so be careful about locking. Progress myParent; synchronized(this) { progress = 1.0f; myParent = parent; } if (myParent != null) { // this will synchronize on the parent, so we make sure we release // our lock before getting the parent's, since we're traversing // against the normal traversal direction used by get() or toString(). // We don't need transactional semantics, so we're OK doing this. myParent.startNextPhase(); } } /** Called during execution on a leaf node to set its progress. */ public synchronized void set(float progress) { this.progress = progress; } /** Returns the overall progress of the root. */ // this method probably does not need to be synchronized as getInternal() is // synchronized and the node's parent never changes. Still, it doesn't hurt. public synchronized float get() { Progress node = this; while (node.getParent() != null) { // find the root node = parent; } return node.getInternal(); } /** * Returns progress in this node. get() would give overall progress of the * root node(not just given current node). */ public synchronized float getProgress() { return getInternal(); } /** Computes progress in this node. */ private synchronized float getInternal() { int phaseCount = phases.size(); if (phaseCount != 0) { float subProgress = 0.0f; float progressFromCurrentPhase = 0.0f; if (currentPhase < phaseCount) { subProgress = phase().getInternal(); progressFromCurrentPhase = getProgressWeightage(currentPhase) * subProgress; } float progressFromCompletedPhases = 0.0f; if (fixedWeightageForAllPhases) { // same progress weightage for each phase progressFromCompletedPhases = progressPerPhase * currentPhase; } else { for (int i = 0; i < currentPhase; i++) { // progress weightages of phases could be different. Add them progressFromCompletedPhases += getProgressWeightage(i); } } return progressFromCompletedPhases + progressFromCurrentPhase; } else { return progress; } } public synchronized void setStatus(String status) { this.status = status; } public String toString() { StringBuilder result = new StringBuilder(); toString(result); return result.toString(); } private synchronized void toString(StringBuilder buffer) { buffer.append(status); if (phases.size() != 0 && currentPhase < phases.size()) { buffer.append(" > "); phase().toString(buffer); } } }