View Javadoc

1   /**
2    * Copyright 2011 The Apache Software Foundation
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS,
16   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17   * See the License for the specific language governing permissions and
18   * limitations under the License.
19   */
20  package org.apache.hadoop.hbase.monitoring;
21  
22  import java.io.PrintWriter;
23  import java.lang.ref.WeakReference;
24  import java.lang.reflect.InvocationHandler;
25  import java.lang.reflect.Method;
26  import java.lang.reflect.Proxy;
27  import java.util.ArrayList;
28  import java.util.Iterator;
29  import java.util.List;
30  
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
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  public class TaskMonitor {
43    private static final Log LOG = LogFactory.getLog(TaskMonitor.class);
44  
45    // Don't keep around any tasks that have completed more than
46    // 60 seconds ago
47    private static final long EXPIRATION_TIME = 60*1000;
48  
49    @VisibleForTesting
50    static final int MAX_TASKS = 1000;
51    
52    private static TaskMonitor instance;
53    private List<TaskAndWeakRefPair> tasks =
54      Lists.newArrayList();
55  
56    /**
57     * Get singleton instance.
58     * TODO this would be better off scoped to a single daemon
59     */
60    public static synchronized TaskMonitor get() {
61      if (instance == null) {
62        instance = new TaskMonitor();
63      }
64      return instance;
65    }
66    
67    public synchronized MonitoredTask createStatus(String description) {
68      MonitoredTask stat = new MonitoredTaskImpl();
69      stat.setDescription(description);
70      MonitoredTask proxy = (MonitoredTask) Proxy.newProxyInstance(
71          stat.getClass().getClassLoader(),
72          new Class<?>[] { MonitoredTask.class },
73          new PassthroughInvocationHandler<MonitoredTask>(stat));
74      TaskAndWeakRefPair pair = new TaskAndWeakRefPair(stat, proxy);
75      tasks.add(pair);
76      if (tasks.size() > MAX_TASKS) {
77        purgeExpiredTasks();
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      tasks.add(pair);
91      if (tasks.size() > MAX_TASKS) {
92        purgeExpiredTasks();
93      }
94      return proxy;
95    }
96  
97    private synchronized void purgeExpiredTasks() {
98      int size = 0;
99      
100     for (Iterator<TaskAndWeakRefPair> it = tasks.iterator();
101          it.hasNext();) {
102       TaskAndWeakRefPair pair = it.next();
103       MonitoredTask stat = pair.get();
104       
105       if (pair.isDead()) {
106         // The class who constructed this leaked it. So we can
107         // assume it's done.
108         if (stat.getState() == MonitoredTaskImpl.State.RUNNING) {
109           LOG.warn("Status " + stat + " appears to have been leaked");
110           stat.cleanup();
111         }
112       }
113       
114       if (canPurge(stat)) {
115         it.remove();
116       } else {
117         size++;
118       }
119     }
120     
121     if (size > MAX_TASKS) {
122       LOG.warn("Too many actions in action monitor! Purging some.");
123       tasks = tasks.subList(size - MAX_TASKS, size);
124     }
125   }
126 
127   /**
128    * Produces a list containing copies of the current state of all non-expired 
129    * MonitoredTasks handled by this TaskMonitor.
130    * @return A complete list of MonitoredTasks.
131    */
132   public synchronized List<MonitoredTask> getTasks() {
133     purgeExpiredTasks();
134     ArrayList<MonitoredTask> ret = Lists.newArrayListWithCapacity(tasks.size());
135     for (TaskAndWeakRefPair pair : tasks) {
136       MonitoredTask t = pair.get();
137       ret.add(t.clone());
138     }
139     return ret;
140   }
141 
142   private boolean canPurge(MonitoredTask stat) {
143     long cts = stat.getCompletionTimestamp();
144     return (cts > 0 && System.currentTimeMillis() - cts > EXPIRATION_TIME);
145   }
146   
147 
148   public void dumpAsText(PrintWriter out) {
149     long now = System.currentTimeMillis();
150     
151     List<MonitoredTask> tasks = getTasks();
152     for (MonitoredTask task : tasks) {
153       out.println("Task: " + task.getDescription());
154       out.println("Status: " + task.getState() + ":" + task.getStatus());
155       long running = (now - task.getStartTime())/1000;
156       if (task.getCompletionTimestamp() != -1) {
157         long completed = (now - task.getCompletionTimestamp()) / 1000;
158         out.println("Completed " + completed + "s ago");
159         out.println("Ran for " +
160             (task.getCompletionTimestamp() - task.getStartTime())/1000
161             + "s");
162       } else {
163         out.println("Running for " + running + "s");
164       }
165       out.println();
166     }
167   }
168 
169   /**
170    * This class encapsulates an object as well as a weak reference to a proxy
171    * that passes through calls to that object. In art form:
172    * <code>
173    *     Proxy  <------------------
174    *       |                       \
175    *       v                        \
176    * PassthroughInvocationHandler   |  weak reference
177    *       |                       /
178    * MonitoredTaskImpl            / 
179    *       |                     /
180    * StatAndWeakRefProxy  ------/
181    *
182    * Since we only return the Proxy to the creator of the MonitorableStatus,
183    * this means that they can leak that object, and we'll detect it
184    * since our weak reference will go null. But, we still have the actual
185    * object, so we can log it and display it as a leaked (incomplete) action.
186    */
187   private static class TaskAndWeakRefPair {
188     private MonitoredTask impl;
189     private WeakReference<MonitoredTask> weakProxy;
190     
191     public TaskAndWeakRefPair(MonitoredTask stat,
192         MonitoredTask proxy) {
193       this.impl = stat;
194       this.weakProxy = new WeakReference<MonitoredTask>(proxy);
195     }
196     
197     public MonitoredTask get() {
198       return impl;
199     }
200     
201     public boolean isDead() {
202       return weakProxy.get() == null;
203     }
204   }
205   
206   /**
207    * An InvocationHandler that simply passes through calls to the original 
208    * object.
209    */
210   private static class PassthroughInvocationHandler<T> implements InvocationHandler {
211     private T delegatee;
212     
213     public PassthroughInvocationHandler(T delegatee) {
214       this.delegatee = delegatee;
215     }
216 
217     @Override
218     public Object invoke(Object proxy, Method method, Object[] args)
219         throws Throwable {
220       return method.invoke(delegatee, args);
221     }    
222   }
223 }