| /* |
| * 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.netbeans.core; |
| |
| import java.awt.AWTEvent; |
| import java.awt.Component; |
| import java.awt.Cursor; |
| import java.awt.EventQueue; |
| import java.awt.Frame; |
| import java.awt.KeyboardFocusManager; |
| import java.awt.Toolkit; |
| import java.awt.Window; |
| import java.io.ByteArrayOutputStream; |
| import java.io.DataOutputStream; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| import javax.swing.JFrame; |
| import javax.swing.JRootPane; |
| import javax.swing.SwingUtilities; |
| import org.netbeans.modules.sampler.Sampler; |
| import org.openide.util.Exceptions; |
| import org.openide.util.Lookup; |
| import org.openide.util.Mutex; |
| import org.openide.util.NbBundle; |
| import org.openide.util.RequestProcessor; |
| import org.openide.windows.WindowManager; |
| |
| /** |
| * Logging event queue that can report problems about too long execution times |
| * |
| * |
| * @author Jaroslav Tulach <jaroslav.tulach@netbeans.org> |
| */ |
| final class TimableEventQueue extends EventQueue |
| implements Runnable { |
| private static final Logger LOG = Logger.getLogger(TimableEventQueue.class.getName()); |
| static final RequestProcessor RP = new RequestProcessor("Timeable Event Queue Watch Dog", 1, false, false); // NOI18N |
| private static final int QUANTUM; |
| private static final int REPORT; |
| static { |
| int quantum = 10000; // 10s |
| int report = 20000; // 20s |
| assert (quantum = 100) > 0; // 100ms in not production mode |
| assert (report = 3000) > 0; // 3s in not production mode |
| |
| QUANTUM = Integer.getInteger("org.netbeans.core.TimeableEventQueue.quantum", quantum); // NOI18N |
| REPORT = Integer.getInteger("org.netbeans.core.TimeableEventQueue.report", report); // NOI18N |
| } |
| private static final int WAIT_CURSOR_LIMIT = Integer.getInteger("org.netbeans.core.TimeableEventQueue.waitcursor", 15000); // NOI18N |
| private static final int PAUSE = Integer.getInteger("org.netbeans.core.TimeableEventQueue.pause", 15000); // NOI18N |
| |
| private final RequestProcessor.Task TIMEOUT; |
| private final RequestProcessor.Task WAIT_CURSOR_CHECKER; |
| private volatile long ignoreTill; |
| private volatile long start; |
| private volatile Sampler stoppable; |
| private volatile boolean isWaitCursor; |
| static volatile Thread eq; |
| private final Frame mainWindow; |
| |
| private TimableEventQueue(Frame f) { |
| this.mainWindow = f; |
| TIMEOUT = RP.create(this); |
| TIMEOUT.setPriority(Thread.MIN_PRIORITY); |
| WAIT_CURSOR_CHECKER = RP.create(new Runnable() { |
| |
| @Override |
| public void run() { |
| isWaitCursor |= isWaitCursor(); |
| } |
| }, true); |
| WAIT_CURSOR_CHECKER.setPriority(Thread.MIN_PRIORITY); |
| ignoreTill = System.currentTimeMillis() + PAUSE; |
| } |
| |
| static void initialize() { |
| initialize(null, true); |
| } |
| static void initialize(final Frame f, final boolean defaultWindow) { |
| boolean install = Boolean.valueOf(NbBundle.getMessage(TimableEventQueue.class, "TimableEventQueue.install")); // NOI18N |
| if (!install) { |
| return; |
| } |
| |
| // #28536: make sure a JRE bug does not prevent the event queue from having |
| // the right context class loader |
| // and #35470: do it early, before any module-loaded AWT code might run |
| // and #36820: even that isn't always early enough, so we need to push |
| // a new EQ to enforce the context loader |
| // XXX this is a hack! |
| try { |
| Mutex.EVENT.writeAccess (new Mutex.Action<Void>() { |
| @Override |
| public Void run() { |
| Frame use = f; |
| if (defaultWindow && use == null) { |
| use = WindowManager.getDefault().getMainWindow(); |
| } |
| ClassLoader scl = Lookup.getDefault().lookup(ClassLoader.class); |
| if (scl != null) { |
| Thread.currentThread().setContextClassLoader(scl); |
| } |
| Toolkit.getDefaultToolkit().getSystemEventQueue().push(new TimableEventQueue(use)); |
| LOG.fine("Initialization done"); |
| return null; |
| } |
| }); |
| } catch (Exception e) { |
| e.printStackTrace(); |
| } |
| } |
| |
| @Override |
| protected void dispatchEvent(AWTEvent event) { |
| eq = Thread.currentThread(); |
| boolean scheduled = false; |
| try { |
| scheduled = tick("dispatchEvent"); // NOI18N |
| super.dispatchEvent(event); |
| } finally { |
| if (scheduled) { |
| done(); |
| } |
| } |
| } |
| |
| private void done() { |
| TIMEOUT.cancel(); |
| TIMEOUT.waitFinished(); |
| if (!WAIT_CURSOR_CHECKER.cancel()) { |
| WAIT_CURSOR_CHECKER.waitFinished(); |
| } |
| |
| LOG.log(Level.FINE, "isWait cursor {0}", isWaitCursor); // NOI18N |
| long r; |
| if (isWaitCursor) { |
| r = REPORT * 10; |
| if (r > WAIT_CURSOR_LIMIT) { |
| r = (WAIT_CURSOR_LIMIT > REPORT) ? WAIT_CURSOR_LIMIT : REPORT; |
| } |
| } else { |
| r = REPORT; |
| } |
| isWaitCursor = false; |
| long time = System.currentTimeMillis() - start; |
| if (time > QUANTUM) { |
| LOG.log(Level.FINE, "done, timer stopped, took {0}", time); // NOI18N |
| if (time > r) { |
| LOG.log(Level.WARNING, "too much time in AWT thread {0}", stoppable); // NOI18N |
| ignoreTill = System.currentTimeMillis() + PAUSE; |
| report(stoppable, time); |
| stoppable = null; |
| } |
| } else { |
| LOG.log(Level.FINEST, "done, timer stopped, took {0}", time); |
| } |
| Sampler ss = stoppable; |
| if (ss != null) { |
| ss.cancel(); |
| stoppable = null; |
| } |
| return; |
| } |
| |
| private boolean isShowing() { |
| return mainWindow == null || mainWindow.isShowing(); |
| } |
| |
| private boolean tick(String name) { |
| start = System.currentTimeMillis(); |
| if (start >= ignoreTill && isShowing()) { |
| LOG.log(Level.FINEST, "tick, schedule a timer for {0}", name); |
| TIMEOUT.schedule(QUANTUM); |
| return true; |
| } |
| return false; |
| } |
| |
| @Override |
| public void run() { |
| if (stoppable != null) { |
| LOG.log(Level.WARNING, "Still previous controller {0}", stoppable); |
| return; |
| } |
| Sampler selfSampler = createSelfSampler(); |
| if (selfSampler != null) { |
| selfSampler.start(); |
| stoppable = selfSampler; |
| } |
| isWaitCursor |= isWaitCursor(); |
| if (!isWaitCursor) { |
| WAIT_CURSOR_CHECKER.schedule(Math.max(REPORT - QUANTUM, 0)); |
| } |
| } |
| |
| private static void report(final Sampler ss, final long time) { |
| if (ss == null) { |
| return; |
| } |
| class R implements Runnable { |
| @Override |
| public void run() { |
| try { |
| ByteArrayOutputStream out = new ByteArrayOutputStream(); |
| DataOutputStream dos = new DataOutputStream(out); |
| ss.stopAndWriteTo(dos); |
| dos.close(); |
| if (dos.size() > 0) { |
| Object[] params = new Object[]{out.toByteArray(), time}; |
| Logger.getLogger("org.netbeans.ui.performance").log(Level.CONFIG, "Slowness detected", params); |
| } else { |
| LOG.log(Level.WARNING, "no snapshot taken"); // NOI18N |
| } |
| } catch (Exception ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| } |
| } |
| RP.post(new R()); |
| } |
| |
| private static Sampler createSelfSampler() { |
| return Sampler.createSampler("awt"); // NOI18N |
| } |
| |
| private static boolean isWaitCursor() { |
| Component focus = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); |
| if (focus != null) { |
| if (focus.getCursor().getType() == Cursor.WAIT_CURSOR) { |
| LOG.finer("wait cursor on focus owner"); // NOI18N |
| return true; |
| } |
| Window w = SwingUtilities.windowForComponent(focus); |
| if (w != null && isWaitCursorOnWindow(w)) { |
| LOG.finer("wait cursor on window"); // NOI18N |
| return true; |
| } |
| } |
| for (Frame f : Frame.getFrames()) { |
| if (isWaitCursorOnWindow(f)) { |
| LOG.finer("wait cursor on frame"); // NOI18N |
| return true; |
| } |
| } |
| LOG.finest("no wait cursor"); // NOI18N |
| return false; |
| } |
| |
| private static boolean isWaitCursorOnWindow(Window w) { |
| if (w.getCursor().getType() == Cursor.WAIT_CURSOR) { |
| return true; |
| } |
| if (w instanceof JFrame) { |
| JRootPane root = ((JFrame)w).getRootPane(); |
| if (null != root) { |
| Component glass = root.getGlassPane(); |
| if (null != glass && glass.getCursor().getType() == Cursor.WAIT_CURSOR) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| /* |
| long now = System.currentTimeMillis(); |
| ignoreTill = now + PAUSE; |
| long howLong = now - start; |
| |
| // Logger UI_LOG = Logger.getLogger("org.netbeans.ui.performance"); // NOI18N |
| LogRecord rec = new LogRecord(Level.INFO, "LOG_EventQueueBlocked"); // NOI18N |
| rec.setParameters(new Object[] { howLong }); |
| EQException eq = new EQException(myStack); |
| rec.setThrown(eq); |
| rec.setResourceBundleName("org.netbeans.core.Bundle"); // NOI18N |
| rec.setResourceBundle(ResourceBundle.getBundle("org.netbeans.core.Bundle")); // NOI18N |
| // UI_LOG.log(rec); |
| LOG.log(rec); |
| } |
| |
| private static final class EQException extends Exception { |
| private volatile Map<Thread, StackTraceElement[]> stack; |
| |
| public EQException(Map<Thread, StackTraceElement[]> stack) { |
| this.stack = stack; |
| for (Map.Entry<Thread, StackTraceElement[]> en : stack.entrySet()) { |
| if (en.getKey().getName().indexOf("AWT-EventQueue") >= 0) { |
| setStackTrace(en.getValue()); |
| break; |
| } |
| } |
| } |
| |
| @Override |
| public String getMessage() { |
| return threadDump("AWT Event Queue Thread Blocked", stack); // NOI18N |
| } |
| |
| private static void appendThread(StringBuilder sb, String indent, Thread t, Map<Thread,StackTraceElement[]> data) { |
| sb.append(indent).append("Thread ").append(t.getName()).append('\n'); |
| indent = indent.concat(" "); |
| StackTraceElement[] arr = data.get(t); |
| if (arr != null) { |
| for (StackTraceElement e : arr) { |
| sb.append(indent).append(e.getClassName()).append('.').append(e.getMethodName()) |
| .append(':').append(e.getLineNumber()).append('\n'); |
| } |
| } else { |
| sb.append(indent).append("no stacktrace info"); // NOI18N |
| } |
| } |
| |
| private static void appendGroup(StringBuilder sb, String indent, ThreadGroup tg, Map<Thread,StackTraceElement[]> data) { |
| sb.append(indent).append("Group ").append(tg.getName()).append('\n'); |
| indent = indent.concat(" "); |
| |
| int groups = tg.activeGroupCount(); |
| ThreadGroup[] chg = new ThreadGroup[groups]; |
| tg.enumerate(chg, false); |
| for (ThreadGroup inner : chg) { |
| if (inner != null) appendGroup(sb, indent, inner, data); |
| } |
| |
| int threads = tg.activeCount(); |
| Thread[] cht= new Thread[threads]; |
| tg.enumerate(cht, false); |
| for (Thread t : cht) { |
| if (t != null) appendThread(sb, indent, t, data); |
| } |
| } |
| |
| private static String threadDump(String msg, Map<Thread,StackTraceElement[]> all) { |
| ThreadGroup root = Thread.currentThread().getThreadGroup(); |
| while (root.getParent() != null) root = root.getParent(); |
| |
| StringBuilder sb = new StringBuilder(); |
| sb.append(msg).append('\n'); |
| appendGroup(sb, "", root, all); |
| sb.append('\n').append("---"); |
| return sb.toString(); |
| } |
| |
| } |
| */ |
| } |