View Javadoc

1   /**
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  package org.apache.hadoop.hbase.monitoring;
20  
21  import java.io.PrintWriter;
22  import java.lang.ref.WeakReference;
23  import java.lang.reflect.InvocationHandler;
24  import java.lang.reflect.Method;
25  import java.lang.reflect.Proxy;
26  import java.util.ArrayList;
27  import java.util.Iterator;
28  import java.util.List;
29  
30  import org.apache.commons.collections.buffer.CircularFifoBuffer;
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
33  import org.apache.hadoop.conf.Configuration;
34  import org.apache.hadoop.hbase.HBaseConfiguration;
35  import org.apache.hadoop.hbase.classification.InterfaceAudience;
36  import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
37  import org.apache.hadoop.hbase.util.Threads;
38  
39  import com.google.common.collect.Lists;
40  
41  /**
42   * Singleton which keeps track of tasks going on in this VM.
43   * A Task here is anything which takes more than a few seconds
44   * and the user might want to inquire about the status
45   */
46  @InterfaceAudience.Private
47  public class TaskMonitor {
48    private static final Log LOG = LogFactory.getLog(TaskMonitor.class);
49  
50    public static final String MAX_TASKS_KEY = "hbase.taskmonitor.max.tasks";
51    public static final int DEFAULT_MAX_TASKS = 1000;
52    public static final String RPC_WARN_TIME_KEY = "hbase.taskmonitor.rpc.warn.time";
53    public static final long DEFAULT_RPC_WARN_TIME = 0;
54    public static final String EXPIRATION_TIME_KEY = "hbase.taskmonitor.expiration.time";
55    public static final long DEFAULT_EXPIRATION_TIME = 60*1000;
56    public static final String MONITOR_INTERVAL_KEY = "hbase.taskmonitor.monitor.interval";
57    public static final long DEFAULT_MONITOR_INTERVAL = 10*1000;
58    
59    private static TaskMonitor instance;
60  
61    private final int maxTasks;
62    private final long rpcWarnTime;
63    private final long expirationTime;
64    private final CircularFifoBuffer tasks;
65    private final long monitorInterval;
66    private Thread monitorThread;
67  
68    TaskMonitor() {
69      this(HBaseConfiguration.create());
70    }
71  
72    TaskMonitor(Configuration conf) {
73      maxTasks = conf.getInt(MAX_TASKS_KEY, DEFAULT_MAX_TASKS);
74      expirationTime = conf.getLong(EXPIRATION_TIME_KEY, DEFAULT_EXPIRATION_TIME);
75      rpcWarnTime = conf.getLong(RPC_WARN_TIME_KEY, DEFAULT_RPC_WARN_TIME);
76      tasks = new CircularFifoBuffer(maxTasks);
77      monitorInterval = conf.getLong(MONITOR_INTERVAL_KEY, DEFAULT_MONITOR_INTERVAL);
78      monitorThread = new Thread(new MonitorRunnable());
79      Threads.setDaemonThreadRunning(monitorThread, "Monitor thread for TaskMonitor");
80    }
81  
82    /**
83     * Get singleton instance.
84     * TODO this would be better off scoped to a single daemon
85     */
86    public static synchronized TaskMonitor get() {
87      if (instance == null) {
88        instance = new TaskMonitor(HBaseConfiguration.create());
89      }
90      return instance;
91    }
92    
93    public synchronized MonitoredTask createStatus(String description) {
94      MonitoredTask stat = new MonitoredTaskImpl();
95      stat.setDescription(description);
96      MonitoredTask proxy = (MonitoredTask) Proxy.newProxyInstance(
97          stat.getClass().getClassLoader(),
98          new Class<?>[] { MonitoredTask.class },
99          new PassthroughInvocationHandler<MonitoredTask>(stat));
100     TaskAndWeakRefPair pair = new TaskAndWeakRefPair(stat, proxy);
101     tasks.add(pair);
102     return proxy;
103   }
104 
105   public synchronized MonitoredRPCHandler createRPCStatus(String description) {
106     MonitoredRPCHandler stat = new MonitoredRPCHandlerImpl();
107     stat.setDescription(description);
108     MonitoredRPCHandler proxy = (MonitoredRPCHandler) Proxy.newProxyInstance(
109         stat.getClass().getClassLoader(),
110         new Class<?>[] { MonitoredRPCHandler.class },
111         new PassthroughInvocationHandler<MonitoredRPCHandler>(stat));
112     TaskAndWeakRefPair pair = new TaskAndWeakRefPair(stat, proxy);
113     tasks.add(pair);
114     return proxy;
115   }
116 
117   private synchronized void warnStuckTasks() {
118     if (rpcWarnTime > 0) {
119       final long now = EnvironmentEdgeManager.currentTime();
120       for (Iterator<TaskAndWeakRefPair> it = tasks.iterator();
121           it.hasNext();) {
122         TaskAndWeakRefPair pair = it.next();
123         MonitoredTask stat = pair.get();
124         if ((stat instanceof MonitoredRPCHandler) &&
125             (stat.getState() == MonitoredTaskImpl.State.RUNNING) &&
126             (now >= stat.getWarnTime() + rpcWarnTime)) {
127           LOG.warn("Task may be stuck: " + stat);
128           stat.setWarnTime(now);
129         }
130       }
131     }
132   }
133 
134   private synchronized void purgeExpiredTasks() {
135     for (Iterator<TaskAndWeakRefPair> it = tasks.iterator();
136          it.hasNext();) {
137       TaskAndWeakRefPair pair = it.next();
138       MonitoredTask stat = pair.get();
139       
140       if (pair.isDead()) {
141         // The class who constructed this leaked it. So we can
142         // assume it's done.
143         if (stat.getState() == MonitoredTaskImpl.State.RUNNING) {
144           LOG.warn("Status " + stat + " appears to have been leaked");
145           stat.cleanup();
146         }
147       }
148       
149       if (canPurge(stat)) {
150         it.remove();
151       }
152     }
153   }
154 
155   /**
156    * Produces a list containing copies of the current state of all non-expired 
157    * MonitoredTasks handled by this TaskMonitor.
158    * @return A complete list of MonitoredTasks.
159    */
160   public synchronized List<MonitoredTask> getTasks() {
161     purgeExpiredTasks();
162     ArrayList<MonitoredTask> ret = Lists.newArrayListWithCapacity(tasks.size());
163     for (Iterator<TaskAndWeakRefPair> it = tasks.iterator();
164          it.hasNext();) {
165       TaskAndWeakRefPair pair = it.next();
166       MonitoredTask t = pair.get();
167       ret.add(t.clone());
168     }
169     return ret;
170   }
171 
172   private boolean canPurge(MonitoredTask stat) {
173     long cts = stat.getCompletionTimestamp();
174     return (cts > 0 && EnvironmentEdgeManager.currentTime() - cts > expirationTime);
175   }
176 
177   public void dumpAsText(PrintWriter out) {
178     long now = EnvironmentEdgeManager.currentTime();
179     
180     List<MonitoredTask> tasks = getTasks();
181     for (MonitoredTask task : tasks) {
182       out.println("Task: " + task.getDescription());
183       out.println("Status: " + task.getState() + ":" + task.getStatus());
184       long running = (now - task.getStartTime())/1000;
185       if (task.getCompletionTimestamp() != -1) {
186         long completed = (now - task.getCompletionTimestamp()) / 1000;
187         out.println("Completed " + completed + "s ago");
188         out.println("Ran for " +
189             (task.getCompletionTimestamp() - task.getStartTime())/1000
190             + "s");
191       } else {
192         out.println("Running for " + running + "s");
193       }
194       out.println();
195     }
196   }
197 
198   public synchronized void shutdown() {
199     if (this.monitorThread != null) {
200       monitorThread.interrupt();
201     }
202   }
203 
204   /**
205    * This class encapsulates an object as well as a weak reference to a proxy
206    * that passes through calls to that object. In art form:
207    * <code>
208    *     Proxy  <------------------
209    *       |                       \
210    *       v                        \
211    * PassthroughInvocationHandler   |  weak reference
212    *       |                       /
213    * MonitoredTaskImpl            / 
214    *       |                     /
215    * StatAndWeakRefProxy  ------/
216    *
217    * Since we only return the Proxy to the creator of the MonitorableStatus,
218    * this means that they can leak that object, and we'll detect it
219    * since our weak reference will go null. But, we still have the actual
220    * object, so we can log it and display it as a leaked (incomplete) action.
221    */
222   private static class TaskAndWeakRefPair {
223     private MonitoredTask impl;
224     private WeakReference<MonitoredTask> weakProxy;
225     
226     public TaskAndWeakRefPair(MonitoredTask stat,
227         MonitoredTask proxy) {
228       this.impl = stat;
229       this.weakProxy = new WeakReference<MonitoredTask>(proxy);
230     }
231     
232     public MonitoredTask get() {
233       return impl;
234     }
235     
236     public boolean isDead() {
237       return weakProxy.get() == null;
238     }
239   }
240   
241   /**
242    * An InvocationHandler that simply passes through calls to the original 
243    * object.
244    */
245   private static class PassthroughInvocationHandler<T> implements InvocationHandler {
246     private T delegatee;
247     
248     public PassthroughInvocationHandler(T delegatee) {
249       this.delegatee = delegatee;
250     }
251 
252     @Override
253     public Object invoke(Object proxy, Method method, Object[] args)
254         throws Throwable {
255       return method.invoke(delegatee, args);
256     }    
257   }
258 
259   private class MonitorRunnable implements Runnable {
260     private boolean running = true;
261 
262     @Override
263     public void run() {
264       while (running) {
265         try {
266           Thread.sleep(monitorInterval);
267           if (tasks.isFull()) {
268             purgeExpiredTasks();
269           }
270           warnStuckTasks();
271         } catch (InterruptedException e) {
272           running = false;
273         }
274       }
275     }
276   }
277 }