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.logging.Log;
31  import org.apache.commons.logging.LogFactory;
32  import org.apache.hadoop.classification.InterfaceAudience;
33  
34  import com.google.common.annotations.VisibleForTesting;
35  import com.google.common.collect.Lists;
36  
37  /**
38   * Singleton which keeps track of tasks going on in this VM.
39   * A Task here is anything which takes more than a few seconds
40   * and the user might want to inquire about the status
41   */
42  @InterfaceAudience.Private
43  public class TaskMonitor {
44    private static final Log LOG = LogFactory.getLog(TaskMonitor.class);
45  
46    // Don't keep around any tasks that have completed more than
47    // 60 seconds ago
48    private static final long EXPIRATION_TIME = 60*1000;
49  
50    @VisibleForTesting
51    static final int MAX_TASKS = 1000;
52    
53    private static TaskMonitor instance;
54    private List<TaskAndWeakRefPair> tasks =
55      Lists.newArrayList();
56  
57    /**
58     * Get singleton instance.
59     * TODO this would be better off scoped to a single daemon
60     */
61    public static synchronized TaskMonitor get() {
62      if (instance == null) {
63        instance = new TaskMonitor();
64      }
65      return instance;
66    }
67    
68    public synchronized MonitoredTask createStatus(String description) {
69      MonitoredTask stat = new MonitoredTaskImpl();
70      stat.setDescription(description);
71      MonitoredTask proxy = (MonitoredTask) Proxy.newProxyInstance(
72          stat.getClass().getClassLoader(),
73          new Class<?>[] { MonitoredTask.class },
74          new PassthroughInvocationHandler<MonitoredTask>(stat));
75      TaskAndWeakRefPair pair = new TaskAndWeakRefPair(stat, proxy);
76      synchronized (this) {
77        tasks.add(pair);
78      }
79      return proxy;
80    }
81  
82    public synchronized MonitoredRPCHandler createRPCStatus(String description) {
83      MonitoredRPCHandler stat = new MonitoredRPCHandlerImpl();
84      stat.setDescription(description);
85      MonitoredRPCHandler proxy = (MonitoredRPCHandler) Proxy.newProxyInstance(
86          stat.getClass().getClassLoader(),
87          new Class<?>[] { MonitoredRPCHandler.class },
88          new PassthroughInvocationHandler<MonitoredRPCHandler>(stat));
89      TaskAndWeakRefPair pair = new TaskAndWeakRefPair(stat, proxy);
90      synchronized (this) {
91        tasks.add(pair);
92      }
93      return proxy;
94    }
95  
96    private synchronized void purgeExpiredTasks() {
97      int size = 0;
98      
99      for (Iterator<TaskAndWeakRefPair> it = tasks.iterator();
100          it.hasNext();) {
101       TaskAndWeakRefPair pair = it.next();
102       MonitoredTask stat = pair.get();
103       
104       if (pair.isDead()) {
105         // The class who constructed this leaked it. So we can
106         // assume it's done.
107         if (stat.getState() == MonitoredTaskImpl.State.RUNNING) {
108           LOG.warn("Status " + stat + " appears to have been leaked");
109           stat.cleanup();
110         }
111       }
112       
113       if (canPurge(stat)) {
114         it.remove();
115       } else {
116         size++;
117       }
118     }
119     
120     if (size > MAX_TASKS) {
121       LOG.warn("Too many actions in action monitor! Purging some.");
122       tasks = tasks.subList(size - MAX_TASKS, size);
123     }
124   }
125 
126   /**
127    * Produces a list containing copies of the current state of all non-expired 
128    * MonitoredTasks handled by this TaskMonitor.
129    * @return A complete list of MonitoredTasks.
130    */
131   public synchronized List<MonitoredTask> getTasks() {
132     purgeExpiredTasks();
133     ArrayList<MonitoredTask> ret = Lists.newArrayListWithCapacity(tasks.size());
134     for (TaskAndWeakRefPair pair : tasks) {
135       MonitoredTask t = pair.get();
136       ret.add(t.clone());
137     }
138     return ret;
139   }
140 
141   private boolean canPurge(MonitoredTask stat) {
142     long cts = stat.getCompletionTimestamp();
143     return (cts > 0 && System.currentTimeMillis() - cts > EXPIRATION_TIME);
144   }
145   
146 
147   public void dumpAsText(PrintWriter out) {
148     long now = System.currentTimeMillis();
149     
150     List<MonitoredTask> tasks = getTasks();
151     for (MonitoredTask task : tasks) {
152       out.println("Task: " + task.getDescription());
153       out.println("Status: " + task.getState() + ":" + task.getStatus());
154       long running = (now - task.getStartTime())/1000;
155       if (task.getCompletionTimestamp() != -1) {
156         long completed = (now - task.getCompletionTimestamp()) / 1000;
157         out.println("Completed " + completed + "s ago");
158         out.println("Ran for " +
159             (task.getCompletionTimestamp() - task.getStartTime())/1000
160             + "s");
161       } else {
162         out.println("Running for " + running + "s");
163       }
164       out.println();
165     }
166   }
167 
168   /**
169    * This class encapsulates an object as well as a weak reference to a proxy
170    * that passes through calls to that object. In art form:
171    * <code>
172    *     Proxy  <------------------
173    *       |                       \
174    *       v                        \
175    * PassthroughInvocationHandler   |  weak reference
176    *       |                       /
177    * MonitoredTaskImpl            / 
178    *       |                     /
179    * StatAndWeakRefProxy  ------/
180    *
181    * Since we only return the Proxy to the creator of the MonitorableStatus,
182    * this means that they can leak that object, and we'll detect it
183    * since our weak reference will go null. But, we still have the actual
184    * object, so we can log it and display it as a leaked (incomplete) action.
185    */
186   private static class TaskAndWeakRefPair {
187     private MonitoredTask impl;
188     private WeakReference<MonitoredTask> weakProxy;
189     
190     public TaskAndWeakRefPair(MonitoredTask stat,
191         MonitoredTask proxy) {
192       this.impl = stat;
193       this.weakProxy = new WeakReference<MonitoredTask>(proxy);
194     }
195     
196     public MonitoredTask get() {
197       return impl;
198     }
199     
200     public boolean isDead() {
201       return weakProxy.get() == null;
202     }
203   }
204   
205   /**
206    * An InvocationHandler that simply passes through calls to the original 
207    * object.
208    */
209   private static class PassthroughInvocationHandler<T> implements InvocationHandler {
210     private T delegatee;
211     
212     public PassthroughInvocationHandler(T delegatee) {
213       this.delegatee = delegatee;
214     }
215 
216     @Override
217     public Object invoke(Object proxy, Method method, Object[] args)
218         throws Throwable {
219       return method.invoke(delegatee, args);
220     }    
221   }
222 }