1 /**
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements. See the NOTICE file distributed with this
4  * work for additional information regarding copyright ownership. The ASF
5  * licenses this file to you under the Apache License, Version 2.0 (the
6  * "License"); you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14  * License for the specific language governing permissions and limitations under
15  * the License.
16  */
17 package org.apache.hadoop.hdfs.server.namenode.startupprogress;
18 
19 import java.util.EnumSet;
20 import java.util.HashMap;
21 import java.util.Map;
22 import java.util.TreeSet;
23 
24 import org.apache.hadoop.classification.InterfaceAudience;
25 import org.apache.hadoop.util.Time;
26 
27 /**
28  * StartupProgressView is an immutable, consistent, read-only view of namenode
29  * startup progress.  Callers obtain an instance by calling
30  * {@link StartupProgress#createView()} to clone current startup progress state.
31  * Subsequent updates to startup progress will not alter the view.  This isolates
32  * the reader from ongoing updates and establishes a guarantee that the values
33  * returned by the view are consistent and unchanging across multiple related
34  * read operations.  Calculations that require aggregation, such as overall
35  * percent complete, will not be impacted by mutations performed in other threads
36  * mid-way through the calculation.
37  *
38  * Methods that return primitive long may return {@link Long#MIN_VALUE} as a
39  * sentinel value to indicate that the property is undefined.
40  */
41 @InterfaceAudience.Private
42 public class StartupProgressView {
43 
44   private final Map<Phase, PhaseTracking> phases;
45 
46   /**
47    * Returns the sum of the counter values for all steps in the specified phase.
48    *
49    * @param phase Phase to get
50    * @return long sum of counter values for all steps
51    */
getCount(Phase phase)52   public long getCount(Phase phase) {
53     long sum = 0;
54     for (Step step: getSteps(phase)) {
55       sum += getCount(phase, step);
56     }
57     return sum;
58   }
59 
60   /**
61    * Returns the counter value for the specified phase and step.
62    *
63    * @param phase Phase to get
64    * @param step Step to get
65    * @return long counter value for phase and step
66    */
getCount(Phase phase, Step step)67   public long getCount(Phase phase, Step step) {
68     StepTracking tracking = getStepTracking(phase, step);
69     return tracking != null ? tracking.count.get() : 0;
70   }
71 
72   /**
73    * Returns overall elapsed time, calculated as time between start of loading
74    * fsimage and end of safemode.
75    *
76    * @return long elapsed time
77    */
getElapsedTime()78   public long getElapsedTime() {
79     return getElapsedTime(phases.get(Phase.LOADING_FSIMAGE),
80       phases.get(Phase.SAFEMODE));
81   }
82 
83   /**
84    * Returns elapsed time for the specified phase, calculated as (end - begin) if
85    * phase is complete or (now - begin) if phase is running or 0 if the phase is
86    * still pending.
87    *
88    * @param phase Phase to get
89    * @return long elapsed time
90    */
getElapsedTime(Phase phase)91   public long getElapsedTime(Phase phase) {
92     return getElapsedTime(phases.get(phase));
93   }
94 
95   /**
96    * Returns elapsed time for the specified phase and step, calculated as
97    * (end - begin) if step is complete or (now - begin) if step is running or 0
98    * if the step is still pending.
99    *
100    * @param phase Phase to get
101    * @param step Step to get
102    * @return long elapsed time
103    */
getElapsedTime(Phase phase, Step step)104   public long getElapsedTime(Phase phase, Step step) {
105     return getElapsedTime(getStepTracking(phase, step));
106   }
107 
108   /**
109    * Returns the optional file name associated with the specified phase, possibly
110    * null.
111    *
112    * @param phase Phase to get
113    * @return String optional file name, possibly null
114    */
getFile(Phase phase)115   public String getFile(Phase phase) {
116     return phases.get(phase).file;
117   }
118 
119   /**
120    * Returns overall percent complete, calculated by aggregating percent complete
121    * of all phases.  This is an approximation that assumes all phases have equal
122    * running time.  In practice, this isn't true, but there isn't sufficient
123    * information available to predict proportional weights for each phase.
124    *
125    * @return float percent complete
126    */
getPercentComplete()127   public float getPercentComplete() {
128     if (getStatus(Phase.SAFEMODE) == Status.COMPLETE) {
129       return 1.0f;
130     } else {
131       float total = 0.0f;
132       int numPhases = 0;
133       for (Phase phase: phases.keySet()) {
134         ++numPhases;
135         total += getPercentComplete(phase);
136       }
137       return getBoundedPercent(total / numPhases);
138     }
139   }
140 
141   /**
142    * Returns percent complete for the specified phase, calculated by aggregating
143    * the counter values and totals for all steps within the phase.
144    *
145    * @param phase Phase to get
146    * @return float percent complete
147    */
getPercentComplete(Phase phase)148   public float getPercentComplete(Phase phase) {
149     if (getStatus(phase) == Status.COMPLETE) {
150       return 1.0f;
151     } else {
152       long total = getTotal(phase);
153       long count = 0;
154       for (Step step: getSteps(phase)) {
155         count += getCount(phase, step);
156       }
157       return total > 0 ? getBoundedPercent(1.0f * count / total) : 0.0f;
158     }
159   }
160 
161   /**
162    * Returns percent complete for the specified phase and step, calculated as
163    * counter value divided by total.
164    *
165    * @param phase Phase to get
166    * @param step Step to get
167    * @return float percent complete
168    */
getPercentComplete(Phase phase, Step step)169   public float getPercentComplete(Phase phase, Step step) {
170     if (getStatus(phase) == Status.COMPLETE) {
171       return 1.0f;
172     } else {
173       long total = getTotal(phase, step);
174       long count = getCount(phase, step);
175       return total > 0 ? getBoundedPercent(1.0f * count / total) : 0.0f;
176     }
177   }
178 
179   /**
180    * Returns all phases.
181    *
182    * @return Iterable<Phase> containing all phases
183    */
getPhases()184   public Iterable<Phase> getPhases() {
185     return EnumSet.allOf(Phase.class);
186   }
187 
188   /**
189    * Returns all steps within a phase.
190    *
191    * @param phase Phase to get
192    * @return Iterable<Step> all steps
193    */
getSteps(Phase phase)194   public Iterable<Step> getSteps(Phase phase) {
195     return new TreeSet<Step>(phases.get(phase).steps.keySet());
196   }
197 
198   /**
199    * Returns the optional size in bytes associated with the specified phase,
200    * possibly Long.MIN_VALUE if undefined.
201    *
202    * @param phase Phase to get
203    * @return long optional size in bytes, possibly Long.MIN_VALUE
204    */
getSize(Phase phase)205   public long getSize(Phase phase) {
206     return phases.get(phase).size;
207   }
208 
209   /**
210    * Returns the current run status of the specified phase.
211    *
212    * @param phase Phase to get
213    * @return Status run status of phase
214    */
getStatus(Phase phase)215   public Status getStatus(Phase phase) {
216     PhaseTracking tracking = phases.get(phase);
217     if (tracking.beginTime == Long.MIN_VALUE) {
218       return Status.PENDING;
219     } else if (tracking.endTime == Long.MIN_VALUE) {
220       return Status.RUNNING;
221     } else {
222       return Status.COMPLETE;
223     }
224   }
225 
226   /**
227    * Returns the sum of the totals for all steps in the specified phase.
228    *
229    * @param phase Phase to get
230    * @return long sum of totals for all steps
231    */
getTotal(Phase phase)232   public long getTotal(Phase phase) {
233     long sum = 0;
234     for (StepTracking tracking: phases.get(phase).steps.values()) {
235       if (tracking.total != Long.MIN_VALUE) {
236         sum += tracking.total;
237       }
238     }
239     return sum;
240   }
241 
242   /**
243    * Returns the total for the specified phase and step.
244    *
245    * @param phase Phase to get
246    * @param step Step to get
247    * @return long total
248    */
getTotal(Phase phase, Step step)249   public long getTotal(Phase phase, Step step) {
250     StepTracking tracking = getStepTracking(phase, step);
251     return tracking != null && tracking.total != Long.MIN_VALUE ?
252       tracking.total : 0;
253   }
254 
255   /**
256    * Creates a new StartupProgressView by cloning data from the specified
257    * StartupProgress.
258    *
259    * @param prog StartupProgress to clone
260    */
StartupProgressView(StartupProgress prog)261   StartupProgressView(StartupProgress prog) {
262     phases = new HashMap<Phase, PhaseTracking>();
263     for (Map.Entry<Phase, PhaseTracking> entry: prog.phases.entrySet()) {
264       phases.put(entry.getKey(), entry.getValue().clone());
265     }
266   }
267 
268   /**
269    * Returns elapsed time, calculated as (end - begin) if both are defined or
270    * (now - begin) if end is undefined or 0 if both are undefined.  Begin and end
271    * time come from the same AbstractTracking instance.
272    *
273    * @param tracking AbstractTracking containing begin and end time
274    * @return long elapsed time
275    */
getElapsedTime(AbstractTracking tracking)276   private long getElapsedTime(AbstractTracking tracking) {
277     return getElapsedTime(tracking, tracking);
278   }
279 
280   /**
281    * Returns elapsed time, calculated as (end - begin) if both are defined or
282    * (now - begin) if end is undefined or 0 if both are undefined.  Begin and end
283    * time may come from different AbstractTracking instances.
284    *
285    * @param beginTracking AbstractTracking containing begin time
286    * @param endTracking AbstractTracking containing end time
287    * @return long elapsed time
288    */
getElapsedTime(AbstractTracking beginTracking, AbstractTracking endTracking)289   private long getElapsedTime(AbstractTracking beginTracking,
290       AbstractTracking endTracking) {
291     final long elapsed;
292     if (beginTracking != null && beginTracking.beginTime != Long.MIN_VALUE &&
293         endTracking != null && endTracking.endTime != Long.MIN_VALUE) {
294       elapsed = endTracking.endTime - beginTracking.beginTime;
295     } else if (beginTracking != null &&
296         beginTracking.beginTime != Long.MIN_VALUE) {
297       elapsed = Time.monotonicNow() - beginTracking.beginTime;
298     } else {
299       elapsed = 0;
300     }
301     return Math.max(0, elapsed);
302   }
303 
304   /**
305    * Returns the StepTracking internal data structure for the specified phase
306    * and step, possibly null if not found.
307    *
308    * @param phase Phase to get
309    * @param step Step to get
310    * @return StepTracking for phase and step, possibly null
311    */
getStepTracking(Phase phase, Step step)312   private StepTracking getStepTracking(Phase phase, Step step) {
313     PhaseTracking phaseTracking = phases.get(phase);
314     Map<Step, StepTracking> steps = phaseTracking != null ?
315       phaseTracking.steps : null;
316     return steps != null ? steps.get(step) : null;
317   }
318 
319   /**
320    * Returns the given value restricted to the range [0.0, 1.0].
321    *
322    * @param percent float value to restrict
323    * @return float value restricted to range [0.0, 1.0]
324    */
getBoundedPercent(float percent)325   private static float getBoundedPercent(float percent) {
326     return Math.max(0.0f, Math.min(1.0f, percent));
327   }
328 }
329