changeset 6352:cce238b1b880

RT-35613 [Flight Recorder] Implement Java Flight Recorder events in JavaFX runtime
author Oleg Mazurov <oleg.mazurov@oracle.com>
date Mon, 24 Feb 2014 17:31:59 -0800
parents 7a8fde19001f
children bc5337fb4d3c
files modules/base/src/main/java/com/sun/javafx/logging/JFRInputEvent.java modules/base/src/main/java/com/sun/javafx/logging/JFRLogger.java modules/base/src/main/java/com/sun/javafx/logging/JFRPulseEvent.java modules/base/src/main/java/com/sun/javafx/logging/Logger.java modules/base/src/main/java/com/sun/javafx/logging/PrintLogger.java modules/base/src/main/java/com/sun/javafx/logging/PulseLogger.java modules/graphics/src/main/java/com/sun/javafx/sg/prism/CacheFilter.java modules/graphics/src/main/java/com/sun/javafx/sg/prism/NGNode.java modules/graphics/src/main/java/com/sun/javafx/sg/prism/NGRegion.java modules/graphics/src/main/java/com/sun/javafx/sg/prism/RegionImageCache.java modules/graphics/src/main/java/com/sun/javafx/tk/quantum/GlassViewEventHandler.java modules/graphics/src/main/java/com/sun/javafx/tk/quantum/PaintCollector.java modules/graphics/src/main/java/com/sun/javafx/tk/quantum/PresentingPainter.java modules/graphics/src/main/java/com/sun/javafx/tk/quantum/QuantumToolkit.java modules/graphics/src/main/java/com/sun/javafx/tk/quantum/ViewPainter.java modules/graphics/src/main/java/com/sun/prism/impl/GlyphCache.java modules/graphics/src/main/java/javafx/scene/Scene.java
diffstat 17 files changed, 1077 insertions(+), 538 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/base/src/main/java/com/sun/javafx/logging/JFRInputEvent.java	Mon Feb 24 17:31:59 2014 -0800
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.sun.javafx.logging;
+
+import com.oracle.jrockit.jfr.ContentType;
+import com.oracle.jrockit.jfr.EventDefinition;
+import com.oracle.jrockit.jfr.EventToken;
+import com.oracle.jrockit.jfr.TimedEvent;
+import com.oracle.jrockit.jfr.ValueDefinition;
+
+@EventDefinition(path="javafx/input", name = "JavaFX Input", description="JavaFX input event", stacktrace=false, thread=true)
+public class JFRInputEvent extends TimedEvent {
+
+    @ValueDefinition(name="inputType", description="Input event type", contentType=ContentType.None)
+    private String input;
+    
+    public JFRInputEvent(EventToken eventToken) {
+        super(eventToken);
+    }
+
+    public String getInput() {
+        return input;
+    }
+    
+    public void setInput(String s) {
+        input = s;
+    }    
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/base/src/main/java/com/sun/javafx/logging/JFRLogger.java	Mon Feb 24 17:31:59 2014 -0800
@@ -0,0 +1,183 @@
+/*
+ * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.sun.javafx.logging;
+
+import com.oracle.jrockit.jfr.EventToken;
+import com.oracle.jrockit.jfr.Producer;
+
+/**
+ * Logs pulse related information with Java Flight Recorder.
+ */
+class JFRLogger extends Logger {
+    
+    private static final String PRODUCER_URI = "http://www.oracle.com/technetwork/java/javafx/index.html";
+    private static JFRLogger jfrLogger;
+    
+    private final Producer producer;
+    private final EventToken pulseEventToken;
+    private final EventToken inputEventToken;
+    private final ThreadLocal<JFRPulseEvent> curPhaseEvent;
+    private final ThreadLocal<JFRInputEvent> curInputEvent;
+        
+    private JFRLogger() throws Exception {
+        producer = new Producer("JavaFX producer", "JavaFX producer.", PRODUCER_URI);
+        pulseEventToken = producer.addEvent(JFRPulseEvent.class);
+        inputEventToken = producer.addEvent(JFRInputEvent.class);
+        producer.register();
+        curPhaseEvent = new ThreadLocal() {
+            @Override
+            public JFRPulseEvent initialValue() {
+                return new JFRPulseEvent(pulseEventToken);
+            }
+        };
+        curInputEvent = new ThreadLocal(){
+            @Override
+            public JFRInputEvent initialValue() {
+                return new JFRInputEvent(inputEventToken);
+            }
+        };
+    }
+    
+    public static JFRLogger getInstance() {
+        if (jfrLogger == null) {
+            /* Guards against exceptions in the constructor and the absence of jfr.jar at run time */
+            try {
+                Class klass = Class.forName("com.oracle.jrockit.jfr.FlightRecorder");
+                if (klass != null && com.oracle.jrockit.jfr.FlightRecorder.isActive()) {
+                    jfrLogger = new JFRLogger();
+                }
+            }
+            catch (Exception e) {
+                jfrLogger = null;
+            }
+        }
+        return jfrLogger;
+    }
+    
+    /**
+     *  Pulse number reconstruction for the render thread relies on the current synchronization 
+     *  between the FX and render threads: renderStart() is called on the FX thread after all
+     *  previous RenderJobs have finished and before any new RenderJob is pushed.
+     */
+    private int pulseNumber;
+    private int fxPulseNumber;
+    private int renderPulseNumber;
+    private Thread fxThread;
+    
+    @Override
+    public void pulseStart() {
+        ++pulseNumber;
+        fxPulseNumber = pulseNumber;
+        if (fxThread == null) {
+            fxThread = Thread.currentThread();
+        }
+        newPhase("Pulse start");
+    }
+    
+    @Override
+    public void pulseEnd() {
+        newPhase(null);
+        fxPulseNumber = 0;
+    }
+    
+    @Override
+    public void renderStart() {
+        renderPulseNumber = fxPulseNumber;
+    }
+
+    @Override
+    public void renderEnd() {
+        newPhase(null);
+        renderPulseNumber = 0;
+    }
+    
+    /**
+     * Finishes the current phase and starts a new one if phaseName is not null.
+     * @param phaseName The name for the new phase.
+     */
+    @Override
+    public void newPhase(String phaseName) {
+        if (pulseEventToken == null) {
+            return;
+        }
+        
+        JFRPulseEvent event = curPhaseEvent.get();
+
+        /* Cleanup if recording has finished */
+        if (!pulseEventToken.isEnabled()) {
+            event.setPhase(null);
+            return;
+        }
+        
+        /* Finish the previous phase if any */
+        if (event.getPhase() != null) {
+            event.end();
+            event.commit();
+        }
+
+        /* Done if the new phase name is null */
+        if (phaseName == null) {
+            event.setPhase(null);
+            return;
+        }
+                
+        event.reset();
+        event.begin();
+        event.setPhase(phaseName);
+        event.setPulseNumber(Thread.currentThread() == fxThread ? fxPulseNumber : renderPulseNumber);
+    }
+
+    @Override
+    public void newInput(String input) {
+        if (inputEventToken == null) {
+            return;
+        }
+
+        JFRInputEvent event = curInputEvent.get();
+
+        /* Cleanup if recording has finished */
+        if (!inputEventToken.isEnabled()) {
+            event.setInput(null);
+            return;
+        }
+        
+        /* Finish the previous input event if any */
+        if (event.getInput() != null) {
+            event.end();
+            event.commit();
+        }
+
+        /* Done if the new input is null */
+        if (input == null) {
+            event.setInput(null);
+            return;
+        }
+        
+        event.reset();
+        event.begin();
+        event.setInput(input);
+    }    
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/base/src/main/java/com/sun/javafx/logging/JFRPulseEvent.java	Mon Feb 24 17:31:59 2014 -0800
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.sun.javafx.logging;
+
+import com.oracle.jrockit.jfr.ContentType;
+import com.oracle.jrockit.jfr.EventDefinition;
+import com.oracle.jrockit.jfr.EventToken;
+import com.oracle.jrockit.jfr.TimedEvent;
+import com.oracle.jrockit.jfr.ValueDefinition;
+
+@EventDefinition(path="javafx/pulse", name = "JavaFX Pulse Phase", description="Describes a phase in JavaFX pulse processing", stacktrace=false, thread=true)
+public class JFRPulseEvent extends TimedEvent {
+
+    @ValueDefinition(name="pulseID", description="Pulse number", contentType=ContentType.None, relationKey="http://www.oracle.com/javafx/pulse/id")
+    private int pulseNumber;
+
+    @ValueDefinition(name="phaseName", description="Pulse phase name", contentType=ContentType.None)
+    private String phase;
+    
+    public JFRPulseEvent(EventToken eventToken) {
+        super(eventToken);
+    }
+
+    public int getPulseNumber() {
+        return pulseNumber;
+    }
+     
+    public void setPulseNumber(int n) {
+        pulseNumber = n;
+    } 
+    
+    public String getPhase() {
+        return phase;
+    }
+    
+    public void setPhase(String s) {
+        phase = s;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/base/src/main/java/com/sun/javafx/logging/Logger.java	Mon Feb 24 17:31:59 2014 -0800
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.sun.javafx.logging;
+
+/**
+ * Common API for pulse loggers
+ */
+public class Logger {
+    public void pulseStart() {}
+    public void pulseEnd() {}
+    public void renderStart() {}
+    public void renderEnd() {}
+    public void addMessage(String message) {}
+    public void incrementCounter(String counter) {}
+    public void newPhase(String name) {}
+    public void newInput(String name) {}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/base/src/main/java/com/sun/javafx/logging/PrintLogger.java	Mon Feb 24 17:31:59 2014 -0800
@@ -0,0 +1,434 @@
+/*
+ * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.sun.javafx.logging;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Logs information on a per-pulse basis. When doing performance analysis, a very
+ * easy thing to start with is to run with the PulseLogger enabled, such that various
+ * statistics related to the scene graph and the pulse are recorded and dumped to
+ * the log.
+ * <p>
+ * The pulse logger is designed in such a way as to gather all of the pulse statistics
+ * together even though half of the pulse occurs on the FX thread and half on the
+ * render thread, and therefore two sets of pulse data are being accumulated
+ * concurrently. The {@code pulseStart}, {@code pulseEnd}, {@code renderStart},
+ * and {@code renderEnd} methods must be called appropriately by the runtime
+ * to ensure that the logging system works correctly.
+ */
+class PrintLogger extends Logger {
+
+    /**
+     * A reference to the pulse logger. This will be null if pulse logging
+     * is not enabled.
+     */
+    private static PrintLogger printLogger;
+
+    /**
+     * A time in milliseconds which defines the threshold. If a pulse lasts <em>longer</em> than
+     * the threshold, then it is logged, otherwise an abbreviated representation including
+     * only the time of the pulse is logged.
+     */
+    private static long THRESHOLD = (long)
+            AccessController.doPrivileged(new PrivilegedAction<Integer>() {
+                @Override public Integer run() {
+                    return Integer.getInteger("javafx.pulseLogger.threshold", 17);
+                }
+            });
+    
+    /**
+     * Optionally exit after a given number of pulses
+     */
+    private static final int EXIT_ON_PULSE =
+            AccessController.doPrivileged(new PrivilegedAction<Integer>() {
+                @Override
+                public Integer run() {
+                    return Integer.getInteger("javafx.pulseLogger.exitOnPulse", 0);
+                }
+            });
+
+    /**
+     * We have a simple counter that keeps track of the current pulse number.
+     * INTER_PULSE_DATA is used to mark data that comes between pulses.
+     */
+    private int pulseCount = 1;
+    private static final int INTER_PULSE_DATA = -1;
+
+    /**
+     * When printing the truncated form of the pulse, we just print one truncated
+     * form after another, such as:
+     * 
+     * [5][2][4]
+     * 
+     * This way we don't cause the console to scroll far vertically in the case of fast
+     * pulses. We do this so that relevant information (pulses that exceed the threshold)
+     * is easy to find and remains visible as long as possible in the console. However,
+     * we don't want to scroll too far off to the right either, so we keep track of
+     * how many "quick pulses" have happened in a row. When we've exceeded some fixed
+     * number (20, say) then we will insert a newline into the log.
+     */
+    private volatile int wrapCount = 0;
+
+    /**
+     * References to PulseData for the FX thread (fxData) and the Render thread (renderData).
+     */
+    private volatile PulseData fxData, renderData;
+
+    /**
+     * Keeps track of the start of the previous pulse, such that we can print out
+     * the time interval between the start of pulses.
+     */
+    private long lastPulseStartTime;
+    
+    class ThreadLocalData {
+        String  phaseName;
+        long    phaseStart;
+    }
+    
+    private Thread fxThread;
+    private final ThreadLocal<ThreadLocalData> phaseData = 
+        new ThreadLocal() {
+            @Override
+            public ThreadLocalData initialValue() {
+                return new ThreadLocalData();
+            }
+        };
+    
+
+    /**
+     * The queue of all PulseData objects, both available and those in use.
+     * New PulseData objects are allocated from head if the state is AVAILABLE.
+     * They are re-linked at tail with the state INCOMPLETE. Once fully processed
+     * they will change their state back to AVAILABLE and will become ready for reuse.
+     */
+    private PulseData head;
+    private PulseData tail;
+    
+    /**
+     * A synchronization object for printing arbitrage.
+     */
+    private AtomicInteger active;
+    
+    /**
+     * PulseData object states
+     */
+    private static final int AVAILABLE = 0;
+    private static final int INCOMPLETE = 1;
+    private static final int COMPLETE = 2;
+    
+    /**
+     * Disallow instantiation.
+     */
+    private PrintLogger() {
+        head = new PulseData();
+        tail = new PulseData();
+        head.next = tail;
+        active = new AtomicInteger(0);
+    }
+
+    public static Logger getInstance() {
+        if (printLogger == null) {
+            boolean enabled = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
+                    @Override public Boolean run() {
+                        return Boolean.getBoolean("javafx.pulseLogger");
+                    }
+                });
+            if (enabled) {
+                printLogger = new PrintLogger();
+            }
+        }
+        return printLogger;
+    }
+    
+    /**
+     * Allocate and initialize a PulseData object
+     */
+    private PulseData allocate(int n) {
+        PulseData res;
+        if (head != tail && head.state == AVAILABLE) {
+            res = head;
+            head = head.next;
+            res.next = null;
+        }
+        else {
+            res = new PulseData();
+        }
+        tail.next = res;
+        tail = res;
+        res.init(n);        
+        return res;
+    }
+
+    /**
+     * <strong>MUST</strong> be called at the start of every pulse.
+     * This method will initialize the fxData buffer so that subsequent
+     * calls to fxMessage will write to this buffer.
+     */
+    @Override
+    public void pulseStart() {
+        if (fxThread == null) {
+            fxThread = Thread.currentThread();
+        }
+        if (fxData != null) {
+            // Inter pulse data
+            fxData.state = COMPLETE;
+            if (active.incrementAndGet() == 1) {
+                fxData.printAndReset();
+                active.decrementAndGet();
+            }
+        }
+        fxData = allocate(pulseCount++);
+        if (lastPulseStartTime > 0) {
+            fxData.interval = (fxData.startTime - lastPulseStartTime)/1000000L;
+        }
+        lastPulseStartTime = fxData.startTime;
+    }
+
+    /**
+     * <strong>Must</strong> be called before any set of render jobs
+     * for a given pulse begin. This method will initialize the
+     * renderData buffer so that subsequent calls to renderMessage
+     * will write to this buffer. I have found that sometimes renderMessage
+     * is called without a pulse being started. Such cases are exceptional
+     * and appear to happen only at startup, and such cases are simply not
+     * logged.
+     */
+    @Override
+    public void renderStart() {
+        newPhase(null); // finish the current phase on the FX thread
+        fxData.pushedRender = true;
+        renderData = fxData;
+        active.incrementAndGet();
+    }
+
+    /**
+     * <strong>Must</strong> be called at the end of the pulse. If
+     * there was no rendering started during this pulse, then this
+     * method will cause the pulse data to be logged. Otherwise, the
+     * pulse data is logged when rendering is ended. However, as soon
+     * as pulseEnd is called, we are ready for another call to pulseStart.
+     */
+    @Override
+    public void pulseEnd() {
+        if (!fxData.pushedRender) {
+            fxData.state = COMPLETE;
+            if (active.incrementAndGet() == 1) {
+                fxData.printAndReset();
+                active.decrementAndGet();
+            }
+        }
+        fxData = null;
+    }
+
+    /**
+     * <strong>Must</strong> be called at the end of rendering, if a previous
+     * call to {@link #renderStart()} had been made. This will cause the pulse
+     * data to be logged.
+     */
+    @Override
+    public void renderEnd() {
+        newPhase(null); // finish the current phase on the render thread
+        renderData.state = COMPLETE;
+        for (;;) {
+            renderData.printAndReset();
+            if (active.decrementAndGet() == 0) {
+                break;
+            }
+            renderData = renderData.next;
+        }
+        renderData = null;
+    }
+    
+    /**
+     * Adds a message to the log for the pulse.
+     * @param message The message to log. A newline will be added automatically.
+     */
+    @Override
+    public void addMessage(String message) {
+        PulseData pulseData;
+        if (fxThread == null || Thread.currentThread() == fxThread) {
+            if (fxData == null) {
+                fxData = allocate(INTER_PULSE_DATA);
+            }
+            pulseData = fxData;
+        }
+        else {
+            pulseData = renderData;
+        }
+        if (pulseData == null) {
+            return;
+        }
+        pulseData.message
+            .append("T")
+            .append(Thread.currentThread().getId())
+            .append(" : ")
+            .append(message)
+            .append("\n");
+    }
+
+    /**
+     * Increments the given named per-pulse counter.
+     * @param counter The name for the counter.
+     */
+    @Override
+    public void incrementCounter(String counter) {
+        PulseData pulseData;
+        if (fxThread == null || Thread.currentThread() == fxThread) {
+            if (fxData == null) {
+                fxData = allocate(INTER_PULSE_DATA);
+            }
+            pulseData = fxData;
+        }
+        else {
+            pulseData = renderData;
+        }
+        if (pulseData == null) {
+            return;
+        }
+        Map<String,Counter> counters = pulseData.counters;
+        Counter cval = counters.get(counter);
+        if (cval == null) {
+            cval = new Counter();
+            counters.put(counter, cval);
+        }
+        cval.value += 1;
+    }
+    
+    @Override
+    public void newPhase(String name) {
+        long curTime = System.nanoTime();
+
+        ThreadLocalData curPhase = phaseData.get();
+        if (curPhase.phaseName != null) {            
+            PulseData pulseData = Thread.currentThread() == fxThread ? fxData : renderData;
+            if (pulseData != null) {
+                pulseData.message
+                    .append("T")
+                    .append(Thread.currentThread().getId())
+                    .append(" (").append((curPhase.phaseStart-pulseData.startTime)/1000000L)
+                    .append(" +").append((curTime - curPhase.phaseStart)/1000000L).append("ms): ")
+                    .append(curPhase.phaseName)
+                    .append("\n");
+            }
+        }
+        curPhase.phaseName = name;
+        curPhase.phaseStart = curTime;
+    }
+    
+    /**
+     *  A mutable integer to be used in the counter map
+     */
+    private static class Counter {
+        int     value;
+    }
+    
+    /**
+     * The data we collect per pulse. We store the pulse number
+     * associated with this pulse, along with what time it
+     * started at and the interval since the previous pulse.
+     * We also maintain the message buffer and counters.
+     */
+    private final class PulseData {
+        PulseData next;
+        volatile int state = AVAILABLE;
+        long startTime;
+        long interval;
+        int pulseCount;
+        boolean pushedRender;
+        StringBuffer message = new StringBuffer();
+        Map<String,Counter> counters = new ConcurrentHashMap();
+
+        void init(int n) {
+            state = INCOMPLETE;
+            pulseCount = n;
+            startTime = System.nanoTime();
+            interval = 0;
+            pushedRender = false;
+        }
+        
+        void printAndReset() {
+            long endTime = System.nanoTime();
+            long totalTime = (endTime - startTime)/1000000L;
+            
+            if (state != COMPLETE) {
+                System.err.println("\nWARNING: logging incomplete state");
+            }
+            
+            if (totalTime <= THRESHOLD) {
+                // Don't print inter pulse data
+                if (pulseCount != INTER_PULSE_DATA) {
+                    System.err.print((wrapCount++ % 10 == 0 ? "\n[" : "[") + pulseCount+ " " + interval + "ms:" + totalTime + "ms]");
+                }
+            }
+            else {
+                if (pulseCount == INTER_PULSE_DATA) {
+                    System.err.println("\n\nINTER PULSE LOG DATA");                
+                }
+                else {
+                    System.err.print("\n\nPULSE: " + pulseCount +
+                            " [" + interval + "ms:" + totalTime + "ms]");
+                    if (!pushedRender) {
+                        System.err.print(" Required No Rendering");
+                    }
+                    System.err.println();
+                }
+                System.err.print(message);
+                if (!counters.isEmpty()) {
+                    System.err.println("Counters:");
+                    List<Map.Entry<String,Counter>> entries = new ArrayList(counters.entrySet());
+                    Collections.sort(entries, new Comparator<Map.Entry<String,Counter>>() {
+                        @Override
+                        public int compare(Map.Entry<String,Counter> a, Map.Entry<String,Counter> b) {
+                            return a.getKey().compareTo(b.getKey());
+                        }
+                    });
+                    for (Map.Entry<String, Counter> entry : entries) {
+                        System.err.println("\t" + entry.getKey() + ": " + entry.getValue().value);
+                    }
+                }
+                wrapCount = 0;
+            }
+            
+            // Reset the state
+            message.setLength(0);
+            counters.clear();
+            state = AVAILABLE;
+            if (EXIT_ON_PULSE > 0 && pulseCount >= EXIT_ON_PULSE) {
+                System.err.println("Exiting after pulse #" + pulseCount);
+                System.exit(0);
+            }
+        }
+    }
+}
--- a/modules/base/src/main/java/com/sun/javafx/logging/PulseLogger.java	Mon Feb 24 10:32:51 2014 +1300
+++ b/modules/base/src/main/java/com/sun/javafx/logging/PulseLogger.java	Mon Feb 24 17:31:59 2014 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2009, 2014, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -25,438 +25,74 @@
 
 package com.sun.javafx.logging;
 
-import java.security.AccessController;
-import java.security.PrivilegedAction;
 import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
 import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.atomic.AtomicInteger;
 
-/**
- * Logs information on a per-pulse basis. When doing performance analysis, a very
- * easy thing to start with is to run with the PulseLogger enabled, such that various
- * statistics related to the scene graph and the pulse are recorded and dumped to
- * the log.
- * <p>
- * The pulse logger is designed in such a way as to gather all of the pulse statistics
- * together even though half of the pulse occurs on the FX thread and half on the
- * render thread, and therefore two sets of pulse data are being accumulated
- * concurrently. The {@code pulseStart}, {@code pulseEnd}, {@code renderStart},
- * and {@code renderEnd} methods must be called appropriately by the runtime
- * to ensure that the logging system works correctly.
- */
 public class PulseLogger {
-    /**
-     * Specifies whether pulse logging is enabled or not. This is set via
-     * a command line flag and defaults to false.
-     */
-    public static final boolean PULSE_LOGGING_ENABLED =
-            AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
-                @Override public Boolean run() {
-                    return Boolean.getBoolean("javafx.pulseLogger");
-                }
-            });
-
-    /**
-     * A reference to the pulse logger. This will be null if pulse logging
-     * is not enabled.
-     */
-    public static final PulseLogger PULSE_LOGGER = PULSE_LOGGING_ENABLED ? new PulseLogger() : null;
-
-    /**
-     * A time in milliseconds which defines the threshold. If a pulse lasts <em>longer</em> than
-     * the threshold, then it is logged, otherwise an abbreviated representation including
-     * only the time of the pulse is logged.
-     */
-    private static long THRESHOLD = (long)
-            AccessController.doPrivileged(new PrivilegedAction<Integer>() {
-                @Override public Integer run() {
-                    return Integer.getInteger("javafx.pulseLogger.threshold", 17);
-                }
-            });
-
-    /**
-     * Optionally exit after a given number of pulses
-     */
-    private static final int EXIT_ON_PULSE =
-            AccessController.doPrivileged(new PrivilegedAction<Integer>() {
-                @Override
-                public Integer run() {
-                    return Integer.getInteger("javafx.pulseLogger.exitOnPulse", 0);
-                }
-            });
-
-    /**
-     * We have a simple counter that keeps track of the current pulse number.
-     * INTER_PULSE_DATA is used to mark data that comes between pulses.
-     */
-    private int pulseCount = 1;
-    private static final int INTER_PULSE_DATA = -1;
-
-    /**
-     * When printing the truncated form of the pulse, we just print one truncated
-     * form after another, such as:
-     * 
-     * [5][2][4]
-     * 
-     * This way we don't cause the console to scroll far vertically in the case of fast
-     * pulses. We do this so that relevant information (pulses that exceed the threshold)
-     * is easy to find and remains visible as long as possible in the console. However,
-     * we don't want to scroll too far off to the right either, so we keep track of
-     * how many "quick pulses" have happened in a row. When we've exceeded some fixed
-     * number (20, say) then we will insert a newline into the log.
-     */
-    private volatile int wrapCount = 0;
-
-    /**
-     * References to PulseData for the FX thread (fxData) and the Render thread (renderData).
-     */
-    private volatile PulseData fxData, renderData;
-
-    /**
-     * Keeps track of the start of the previous pulse, such that we can print out
-     * the time interval between the start of pulses.
-     */
-    private long lastPulseStartTime;
-
-    /**
-     * No logger activity is expected outside the renderStart..renderEnd interval.
-     * This flag is for notification when that assumption is broken.
-     */
-    private boolean nullRenderFlag = false;
-
-    /**
-     * The queue of all PulseData objects, both available and those in use.
-     * New PulseData objects are allocated from head if the state is AVAILABLE.
-     * They are re-linked at tail with the state INCOMPLETE. Once fully processed
-     * they will change their state back to AVAILABLE and will become ready for reuse.
-     */
-    private PulseData head;
-    private PulseData tail;
     
-    /**
-     * A synchronization object for printing arbitrage.
-     */
-    AtomicInteger active = new AtomicInteger(0);
+    public static final boolean PULSE_LOGGING_ENABLED;
     
-    /**
-     * PulseData object states
-     */
-    private static final int AVAILABLE = 0;
-    private static final int INCOMPLETE = 1;
-    private static final int COMPLETE = 2;
+    private static final Logger[] loggers;
     
-    /**
-     * Disallow instantiation.
-     */
-    private PulseLogger() {
-        head = new PulseData();
-        tail = new PulseData();
-        head.next = tail;
+    static {
+        List<Logger> list = new ArrayList();
+        Logger logger = PrintLogger.getInstance();
+        if (logger != null) {
+            list.add(logger);
+        }
+        logger = JFRLogger.getInstance();
+        if (logger != null) {
+            list.add(logger);
+        }
+        loggers = list.toArray(new Logger[list.size()]);
+        PULSE_LOGGING_ENABLED = loggers.length > 0;
     }
-
-    /**
-     * Allocate and initialize a PulseData object
-     */
-    private PulseData allocate(int n) {
-        PulseData res;
-        if (head != tail && head.state == AVAILABLE) {
-            res = head;
-            head = head.next;
-            res.next = null;
-        }
-        else {
-            res = new PulseData();
-        }
-        tail.next = res;
-        tail = res;
-        res.init(n);        
-        return res;
-    }
-
-    /**
-     * <strong>MUST</strong> be called at the start of every pulse.
-     * This method will initialize the fxData buffer so that subsequent
-     * calls to fxMessage will write to this buffer.
-     */
-    public void pulseStart() {
-        if (fxData != null) {
-            // Inter pulse data
-            fxData.state = COMPLETE;
-            if (active.incrementAndGet() == 1) {
-                fxData.printAndReset();
-                active.decrementAndGet();
-            }
-        }
-        fxData = allocate(pulseCount++);
-        if (lastPulseStartTime > 0) {
-            fxData.interval = fxData.startTime - lastPulseStartTime;
-        }
-        lastPulseStartTime = fxData.startTime;
-    }
-
-    /**
-     * <strong>Must</strong> be called before any set of render jobs
-     * for a given pulse begin. This method will initialize the
-     * renderData buffer so that subsequent calls to renderMessage
-     * will write to this buffer. I have found that sometimes renderMessage
-     * is called without a pulse being started. Such cases are exceptional
-     * and appear to happen only at startup, and such cases are simply not
-     * logged.
-     */
-    public void renderStart() {
-        fxData.pushedRender = true;
-        renderData = fxData;
-        active.incrementAndGet();
-    }
-
-    /**
-     * <strong>Must</strong> be called at the end of the pulse. If
-     * there was no rendering started during this pulse, then this
-     * method will cause the pulse data to be logged. Otherwise, the
-     * pulse data is logged when rendering is ended. However, as soon
-     * as pulseEnd is called, we are ready for another call to pulseStart.
-     */
-    public void pulseEnd() {
-        if (!fxData.pushedRender) {
-            fxData.state = COMPLETE;
-            if (active.incrementAndGet() == 1) {
-                fxData.printAndReset();
-                active.decrementAndGet();
-            }
-        }
-        fxData = null;
-    }
-
-    /**
-     * <strong>Must</strong> be called at the end of rendering, if a previous
-     * call to {@link #renderStart()} had been made. This will cause the pulse
-     * data to be logged.
-     */
-    public void renderEnd() {
-        renderData.state = COMPLETE;
-        for (;;) {
-            renderData.printAndReset();
-            if (active.decrementAndGet() == 0) {
-                break;
-            }
-            renderData = renderData.next;
-        }
-        renderData = null;
-    }
-
-    /**
-     * Adds a message to the log for the pulse. This method <strong>MUST ONLY
-     * BE CALLED FROM THE FX THREAD</strong>.
-     * 
-     * @param message The message to log. A newline will be added automatically.
-     */
-    public void fxMessage(String message) {
-        if (fxData == null) {
-            fxData = allocate(INTER_PULSE_DATA);
-        }
-        fxData.message
-            .append("T")
-            .append(Thread.currentThread().getId())
-            .append(" : ")
-            .append(message)
-            .append("\n");
-    }
-
-    /**
-     * Adds a timing message to the log for the pulse. This method <strong>MUST ONLY
-     * BE CALLED FROM THE FX THREAD</strong>. The milliseconds will be computed and
-     * added to the message. A newline will be added to the message.
-     * <p>
-     * This is typically used as follows:
-     * 
-     * long start = PULSE_LOGGING_ENABLED ? System.currentTimeMillis() : 0;
-     * operationToBeTimed();
-     * if (PULSE_LOGGING_ENABLED) {
-     *     PULSE_LOGGER.fxMessage(start, System.currentTimeMillis(), "Operation Identifier");
-     * }
-     * 
-     * @param start The time in milliseconds when this operation started
-     * @param end The time in milliseconds when the operation ended
-     * @param message The message to log. A newline will be added automatically.
-     */
-    public void fxMessage(long start, long end, String message) {
-        if (fxData == null) {
-            fxData = allocate(INTER_PULSE_DATA);
-        }
-        fxData.message
-            .append("T")
-            .append(Thread.currentThread().getId())
-            .append(" (").append(start-fxData.startTime).append(" +").append(end - start).append("ms): ")
-            .append(message)
-            .append("\n");
-    }
-
-    /**
-     * Increments the given named per-pulse counter. This method <strong>MUST
-     * ONLY BE CALLED ON THE FX THREAD</strong>.
-     * @param counter The name for the counter.
-     */
-    public void fxIncrementCounter(String counter) {
-        if (fxData == null) {
-            fxData = allocate(INTER_PULSE_DATA);
-        }
-        Map<String,Integer> counters = fxData.counters;
-        if (counters.containsKey(counter)) {
-            counters.put(counter, counters.get(counter) + 1);
-        } else {
-            counters.put(counter, 1);
+    
+    public static void pulseStart() {
+        for (Logger logger: loggers) {
+            logger.pulseStart();
         }
     }
     
-    /**
-     * Adds a message to the log for the pulse. This method <strong>MUST ONLY
-     * BE CALLED FROM THE RENDER THREAD</strong>.
-     * 
-     * @param message The message to log. A newline will be added automatically.
-     */
-    public void renderMessage(String message) {
-        if (renderData == null) {
-            nullRenderFlag = true;
-            return;
+    public static void pulseEnd() {
+        for (Logger logger: loggers) {
+            logger.pulseEnd();
         }
-        renderData.message
-            .append("T")
-            .append(Thread.currentThread().getId())
-            .append(" : ")
-            .append(message)
-            .append("\n");
     }
 
-    /**
-     * Adds a timing message to the log for the pulse. This method <strong>MUST ONLY
-     * BE CALLED FROM THE RENDER THREAD</strong>. The milliseconds will be computed and
-     * added to the message. A newline will be added to the message.
-     * <p>
-     * This is typically used as follows:
-     * 
-     * long start = PULSE_LOGGING_ENABLED ? System.currentTimeMillis() : 0;
-     * operationToBeTimed();
-     * if (PULSE_LOGGING_ENABLED) {
-     *     PULSE_LOGGER.renderMessage(start, System.currentTimeMillis(), "Operation Identifier");
-     * }
-     * 
-     * @param start The time in milliseconds when this operation started
-     * @param end The time in milliseconds when the operation ended
-     * @param message The message to log. A newline will be added automatically.
-     */
-    public void renderMessage(long start, long end, String message) {
-        if (renderData == null) {
-            nullRenderFlag = true;
-            return;
-        }
-        renderData.message
-            .append("T")
-            .append(Thread.currentThread().getId())
-            .append(" (").append(start-renderData.startTime).append(" +").append(end - start).append("ms): ")
-            .append(message)
-            .append("\n");
-    }
-
-    /**
-     * Increments the given named per-pulse counter. This method <strong>MUST
-     * ONLY BE CALLED ON THE RENDER THREAD</strong>.
-     * @param counter The name for the counter.
-     */
-    public void renderIncrementCounter(String counter) {
-        if (renderData == null) {
-            nullRenderFlag = true;
-            return;
-        }
-        Map<String,Integer> counters = renderData.counters;
-        if (counters.containsKey(counter)) {
-            counters.put(counter, counters.get(counter) + 1);
-        } else {
-            counters.put(counter, 1);
+    public static void renderStart() {
+        for (Logger logger: loggers) {
+            logger.renderStart();
         }
     }
     
-    /**
-     * The data we collect per pulse. We store the pulse number
-     * associated with this pulse, along with what time it
-     * started at and the interval since the previous pulse.
-     * We also maintain the message buffer and counters.
-     */
-    private final class PulseData {
-        PulseData next;
-        volatile int state = AVAILABLE;
-        long startTime;
-        long interval;
-        int pulseCount;
-        boolean pushedRender;
-        StringBuffer message = new StringBuffer();
-        Map<String,Integer> counters = new ConcurrentHashMap<String, Integer>();
+    public static void renderEnd() {
+        for (Logger logger: loggers) {
+            logger.renderEnd();
+        }
+    }
+    
+    public static void addMessage(String message) {
+        for (Logger logger: loggers) {
+            logger.addMessage(message);
+        }
+    }
+        
+    public static void incrementCounter(String counter) {
+        for (Logger logger: loggers) {
+            logger.incrementCounter(counter);
+        }
+    }
+    
+    public static void newPhase(String name) {
+        for (Logger logger: loggers) {
+            logger.newPhase(name);
+        }
+    }
 
-        void init(int n) {
-            state = INCOMPLETE;
-            pulseCount = n;
-            startTime = System.currentTimeMillis();
-            interval = 0;
-            pushedRender = false;
-        }
-        
-        void printAndReset() {
-            long endTime = System.currentTimeMillis();
-            long totalTime = endTime - startTime;
-            
-            if (nullRenderFlag) {
-                System.err.println("\nWARNING: unexpected render thread activity");
-                nullRenderFlag = false;
-            }
-            if (state != COMPLETE) {
-                System.err.println("\nWARNING: logging incomplete state");
-            }
-            
-            if (totalTime <= THRESHOLD) {
-                // Don't print inter pulse data
-                if (pulseCount != INTER_PULSE_DATA) {
-                    System.err.print((wrapCount++ % 10 == 0 ? "\n[" : "[") + pulseCount+ " " + interval + "ms:" + totalTime + "ms]");
-                }
-            }
-            else {
-                if (pulseCount == INTER_PULSE_DATA) {
-                    System.err.println("\n\nINTER PULSE LOG DATA");                
-                }
-                else {
-                    System.err.print("\n\nPULSE: " + pulseCount +
-                            " [" + interval + "ms:" + totalTime + "ms]");
-                    if (!pushedRender) {
-                        System.err.print(" Required No Rendering");
-                    }
-                    System.err.println();
-                }
-                System.err.print(message);
-                if (!counters.isEmpty()) {
-                    System.err.println("Counters:");
-                    List<Map.Entry<String,Integer>> entries = new ArrayList<Map.Entry<String,Integer>>(counters.entrySet());
-                    Collections.sort(entries, new Comparator<Map.Entry<String,Integer>>() {
-                        public int compare(Map.Entry<String,Integer> a, Map.Entry<String,Integer> b) {
-                            return a.getKey().compareTo(b.getKey());
-                        }
-                    });
-                    for (Map.Entry<String, Integer> entry : entries) {
-                        System.err.println("\t" + entry.getKey() + ": " + entry.getValue());
-                    }
-                }
-                wrapCount = 0;
-            }
-            
-            // Reset the state
-            message.setLength(0);
-            counters.clear();
-            state = AVAILABLE;
-            if (EXIT_ON_PULSE > 0 && pulseCount >= EXIT_ON_PULSE) {
-                System.err.println("Exiting after pulse #" + pulseCount);
-                System.exit(0);
-            }
+    public static void newInput(String name) {
+        for (Logger logger: loggers) {
+            logger.newInput(name);
         }
     }
 }
--- a/modules/graphics/src/main/java/com/sun/javafx/sg/prism/CacheFilter.java	Mon Feb 24 10:32:51 2014 +1300
+++ b/modules/graphics/src/main/java/com/sun/javafx/sg/prism/CacheFilter.java	Mon Feb 24 17:31:59 2014 -0800
@@ -526,7 +526,9 @@
             }
         }
         if (needToRenderCache(xform, xformInfo)) {
-            if (PulseLogger.PULSE_LOGGING_ENABLED) PulseLogger.PULSE_LOGGER.renderIncrementCounter("CacheFilter rebuilding");
+            if (PulseLogger.PULSE_LOGGING_ENABLED) {
+                PulseLogger.incrementCounter("CacheFilter rebuilding");
+            }
             if (cachedImageData != null) {
                 Filterable implImage = cachedImageData.getUntransformedImage();
                 if (implImage != null) {
@@ -599,7 +601,9 @@
 
         Filterable implImage = cachedImageData.getUntransformedImage();
         if (implImage == null) {
-            if (PulseLogger.PULSE_LOGGING_ENABLED) PulseLogger.PULSE_LOGGER.renderIncrementCounter("CacheFilter not used");
+            if (PulseLogger.PULSE_LOGGING_ENABLED) {
+                PulseLogger.incrementCounter("CacheFilter not used");
+            }
             impl_renderNodeToScreen(g);
         } else {
             double mxt = xform.getMxt();
--- a/modules/graphics/src/main/java/com/sun/javafx/sg/prism/NGNode.java	Mon Feb 24 10:32:51 2014 +1300
+++ b/modules/graphics/src/main/java/com/sun/javafx/sg/prism/NGNode.java	Mon Feb 24 17:31:59 2014 -0800
@@ -53,7 +53,7 @@
 import com.sun.scenario.effect.impl.prism.PrDrawable;
 import com.sun.scenario.effect.impl.prism.PrEffectHelper;
 import com.sun.scenario.effect.impl.prism.PrFilterContext;
-import static com.sun.javafx.logging.PulseLogger.PULSE_LOGGER;
+import com.sun.javafx.logging.PulseLogger;
 import static com.sun.javafx.logging.PulseLogger.PULSE_LOGGING_ENABLED;
 
 /**
@@ -1945,7 +1945,9 @@
      * @param g The graphics object we're rendering to. This must never be null.
      */
     public final void render(Graphics g) {
-        if (PULSE_LOGGING_ENABLED) PULSE_LOGGER.renderIncrementCounter("Nodes visited during render");
+        if (PULSE_LOGGING_ENABLED) {
+            PulseLogger.incrementCounter("Nodes visited during render");
+        }
         // Clear the visuals changed flag
         clearDirty();
         // If it isn't visible, then punt
@@ -2064,7 +2066,9 @@
         // restore previous depth test state
         g.setDepthTest(prevDepthTest);
 
-        if (PULSE_LOGGING_ENABLED) PULSE_LOGGER.renderIncrementCounter("Nodes rendered");
+        if (PULSE_LOGGING_ENABLED) {
+            PulseLogger.incrementCounter("Nodes rendered");
+        }
 
         // Used for debug purposes. This is not entirely accurate, as it doesn't measure the
         // number of times this node drew to the pixels, and in some cases reports a node as
--- a/modules/graphics/src/main/java/com/sun/javafx/sg/prism/NGRegion.java	Mon Feb 24 10:32:51 2014 +1300
+++ b/modules/graphics/src/main/java/com/sun/javafx/sg/prism/NGRegion.java	Mon Feb 24 17:31:59 2014 -0800
@@ -61,6 +61,7 @@
 import com.sun.javafx.geom.transform.BaseTransform;
 import com.sun.javafx.geom.transform.GeneralTransform3D;
 import com.sun.javafx.logging.PulseLogger;
+import static com.sun.javafx.logging.PulseLogger.PULSE_LOGGING_ENABLED;
 import com.sun.javafx.tk.Toolkit;
 import com.sun.prism.BasicStroke;
 import com.sun.prism.Graphics;
@@ -628,8 +629,8 @@
                         cachedGraphics.translate(rect.x - outsetShapeBounds.getMinX(),
                                                  rect.y - outsetShapeBounds.getMinY());
                         renderBackgroundShape(cachedGraphics);
-                        if (PulseLogger.PULSE_LOGGING_ENABLED) {
-                            PulseLogger.PULSE_LOGGER.renderIncrementCounter("Rendering region shape image to cache");
+                        if (PULSE_LOGGING_ENABLED) {
+                            PulseLogger.incrementCounter("Rendering region shape image to cache");
                         }
                     }
                 }
@@ -650,8 +651,8 @@
                 final float srcY2 = srcY1 + textureHeight;
 
                 g.drawTexture(cached, dstX1, dstY1, dstX2, dstY2, srcX1, srcY1, srcX2, srcY2);
-                if (PulseLogger.PULSE_LOGGING_ENABLED) {
-                    PulseLogger.PULSE_LOGGER.renderIncrementCounter("Cached region shape image used");
+                if (PULSE_LOGGING_ENABLED) {
+                    PulseLogger.incrementCounter("Cached region shape image used");
                 }
             } else {
                 // no cache, rendering backgrounds directly to graphics
@@ -684,9 +685,9 @@
     }
 
     private void renderBackgroundShape(Graphics g) {
-        if (PulseLogger.PULSE_LOGGING_ENABLED) {
-            PulseLogger.PULSE_LOGGER.renderIncrementCounter("NGRegion renderBackgroundShape slow path");
-            PulseLogger.PULSE_LOGGER.renderMessage("Slow shape path for " + getName());
+        if (PULSE_LOGGING_ENABLED) {
+            PulseLogger.incrementCounter("NGRegion renderBackgroundShape slow path");
+            PulseLogger.addMessage("Slow shape path for " + getName());
         }
 
         // We first need to draw each background fill. We don't pay any attention
@@ -828,8 +829,8 @@
                     // Rendering backgrounds to the cache
                     renderBackgroundRectanglesDirectly(cacheGraphics, cacheWidth, cacheHeight);
 
-                    if (PulseLogger.PULSE_LOGGING_ENABLED) {
-                        PulseLogger.PULSE_LOGGER.renderIncrementCounter("Rendering region background image to cache");
+                    if (PULSE_LOGGING_ENABLED) {
+                        PulseLogger.incrementCounter("Rendering region background image to cache");
                     }
                 }
             }
@@ -1061,8 +1062,8 @@
                                 srcLeftX, srcTopY, srcRightX, srcBottomY);
         }
 
-        if (PulseLogger.PULSE_LOGGING_ENABLED) {
-            PulseLogger.PULSE_LOGGER.renderIncrementCounter("Cached region background image used");
+        if (PULSE_LOGGING_ENABLED) {
+            PulseLogger.incrementCounter("Cached region background image used");
         }
     }
 
@@ -1110,9 +1111,9 @@
                         g.fillRoundRect(l, t, w, h, arcWidth, arcHeight);
                     }
                 } else {
-                    if (PulseLogger.PULSE_LOGGING_ENABLED) {
-                        PulseLogger.PULSE_LOGGER.renderIncrementCounter("NGRegion renderBackgrounds slow path");
-                        PulseLogger.PULSE_LOGGER.renderMessage("Slow background path for " + getName());
+                    if (PULSE_LOGGING_ENABLED) {
+                        PulseLogger.incrementCounter("NGRegion renderBackgrounds slow path");
+                        PulseLogger.addMessage("Slow background path for " + getName());
                     }
                     // The edges are not uniform, so we have to render each edge independently
                     // TODO document the issue number which will give us a fast path for rendering
--- a/modules/graphics/src/main/java/com/sun/javafx/sg/prism/RegionImageCache.java	Mon Feb 24 10:32:51 2014 +1300
+++ b/modules/graphics/src/main/java/com/sun/javafx/sg/prism/RegionImageCache.java	Mon Feb 24 17:31:59 2014 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2014, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -30,6 +30,7 @@
 import com.sun.javafx.geom.Rectangle;
 import com.sun.javafx.geom.Shape;
 import com.sun.javafx.logging.PulseLogger;
+import static com.sun.javafx.logging.PulseLogger.PULSE_LOGGING_ENABLED;
 import com.sun.prism.Graphics;
 import com.sun.prism.RTTexture;
 import com.sun.prism.ResourceFactory;
@@ -126,8 +127,8 @@
             imageMap.clear();
             packer.add(rect);
             backingStore.createGraphics().clear();
-            if (PulseLogger.PULSE_LOGGING_ENABLED) {
-                PulseLogger.PULSE_LOGGER.renderIncrementCounter("Region image cache flushed");
+            if (PULSE_LOGGING_ENABLED) {
+                PulseLogger.incrementCounter("Region image cache flushed");
             }
         }
         imageMap.put(key, new CachedImage(rect, background, shape));
--- a/modules/graphics/src/main/java/com/sun/javafx/tk/quantum/GlassViewEventHandler.java	Mon Feb 24 10:32:51 2014 +1300
+++ b/modules/graphics/src/main/java/com/sun/javafx/tk/quantum/GlassViewEventHandler.java	Mon Feb 24 17:31:59 2014 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -37,6 +37,8 @@
 import com.sun.glass.ui.Window;
 import com.sun.javafx.PlatformUtil;
 import com.sun.javafx.collections.TrackableObservableList;
+import com.sun.javafx.logging.PulseLogger;
+import static com.sun.javafx.logging.PulseLogger.PULSE_LOGGING_ENABLED;
 import com.sun.javafx.scene.input.KeyCodeMap;
 
 import javafx.collections.ListChangeListener;
@@ -130,6 +132,9 @@
 
         @Override
         public Void run() {
+            if (PULSE_LOGGING_ENABLED) {
+                PulseLogger.newInput(keyEventType(type).toString());
+            }
             WindowStage stage = scene.getWindowStage();
             try {
                 if (stage != null) {
@@ -179,6 +184,9 @@
                 if (stage != null) {
                     stage.setInEventHandler(false);
                 }
+                if (PULSE_LOGGING_ENABLED) {
+                    PulseLogger.newInput(null);
+                }
             }
             return null;
         }
@@ -252,6 +260,10 @@
 
         @Override
         public Void run() {
+            if (PULSE_LOGGING_ENABLED) {
+                PulseLogger.newInput(mouseEventType(type).toString());
+            }
+                    
             int buttonMask;
             switch (button) {
                 case MouseEvent.BUTTON_LEFT:
@@ -320,6 +332,9 @@
                 if (stage != null) {
                     stage.setInEventHandler(false);
                 }
+                if (PULSE_LOGGING_ENABLED) {
+                    PulseLogger.newInput(null);
+                }
             }
             return null;
         }
@@ -349,6 +364,9 @@
                                           final int x, final int y, final int xAbs, final int yAbs,
                                           final boolean isKeyboardTrigger)
     {
+        if (PULSE_LOGGING_ENABLED) {
+            PulseLogger.newInput("MENU_EVENT");
+        }
         WindowStage stage = scene.getWindowStage();
         try {
             if (stage != null) {
@@ -367,6 +385,9 @@
             if (stage != null) {
                 stage.setInEventHandler(false);
             }
+            if (PULSE_LOGGING_ENABLED) {
+                PulseLogger.newInput(null);
+            }
         }
     }
 
@@ -377,6 +398,9 @@
                                             final int defaultLines, final int defaultChars,
                                             final double xMultiplier, final double yMultiplier)
     {
+        if (PULSE_LOGGING_ENABLED) {
+            PulseLogger.newInput("SCROLL_EVENT");
+        }
         WindowStage stage = scene.getWindowStage();
         try {
             if (stage != null) {
@@ -406,6 +430,9 @@
             if (stage != null) {
                 stage.setInEventHandler(false);
             }
+            if (PULSE_LOGGING_ENABLED) {
+                PulseLogger.newInput(null);
+            }
         }
     }
 
@@ -481,6 +508,9 @@
                                                  final int[] attrBoundary, final byte[] attrValue,
                                                  final int commitCount, final int cursorPos)
     {
+        if (PULSE_LOGGING_ENABLED) {
+            PulseLogger.newInput("INPUT_METHOD_EVENT");
+        }
         WindowStage stage = scene.getWindowStage();
         try {
             if (stage != null) {
@@ -505,6 +535,9 @@
             if (stage != null) {
                 stage.setInEventHandler(false);
             }
+            if (PULSE_LOGGING_ENABLED) {
+                PulseLogger.newInput(null);
+            }
         }
     }
 
@@ -566,15 +599,33 @@
                                          final int recommendedDropAction,
                                          final ClipboardAssistance dropTargetAssistant)
     {
-        TransferMode action =
-            dndHandler.handleDragEnter(x, y, xAbs, yAbs,
-                actionToTransferMode(recommendedDropAction),
-                dropTargetAssistant);
+        if (PULSE_LOGGING_ENABLED) {
+            PulseLogger.newInput("DRAG_ENTER");
+        }
+        TransferMode action;
+        try {
+            action = dndHandler.handleDragEnter(x, y, xAbs, yAbs,
+                    actionToTransferMode(recommendedDropAction),
+                    dropTargetAssistant);
+        } finally {
+            if (PULSE_LOGGING_ENABLED) {
+                PulseLogger.newInput(null);
+            }
+        }
         return transferModeToAction(action);
     }
 
     @Override public void handleDragLeave(View view, final ClipboardAssistance dropTargetAssistant) {
-        dndHandler.handleDragLeave(dropTargetAssistant);
+        if (PULSE_LOGGING_ENABLED) {
+            PulseLogger.newInput("DRAG_LEAVE");
+        }
+        try {
+            dndHandler.handleDragLeave(dropTargetAssistant);
+        } finally {
+            if (PULSE_LOGGING_ENABLED) {
+                PulseLogger.newInput(null);
+            }
+        }
     }
 
     @Override public int handleDragDrop(View view,
@@ -582,10 +633,19 @@
                                         final int recommendedDropAction,
                                         final ClipboardAssistance dropTargetAssistant)
     {
-        TransferMode action =
-            dndHandler.handleDragDrop(x, y, xAbs, yAbs,
-                actionToTransferMode(recommendedDropAction),
-                dropTargetAssistant);
+        if (PULSE_LOGGING_ENABLED) {
+            PulseLogger.newInput("DRAG_DROP");
+        }
+        TransferMode action;
+        try {
+            action = dndHandler.handleDragDrop(x, y, xAbs, yAbs,
+                    actionToTransferMode(recommendedDropAction),
+                    dropTargetAssistant);
+        } finally {
+            if (PULSE_LOGGING_ENABLED) {
+                PulseLogger.newInput(null);
+            }
+        }
         return transferModeToAction(action);
     }
 
@@ -594,10 +654,19 @@
                                         final int recommendedDropAction,
                                         final ClipboardAssistance dropTargetAssistant)
     {
-        final TransferMode action =
-            dndHandler.handleDragOver(x, y, xAbs, yAbs,
+        if (PULSE_LOGGING_ENABLED) {
+            PulseLogger.newInput("DRAG_OVER");
+        }
+        TransferMode action;
+        try {
+            action = dndHandler.handleDragOver(x, y, xAbs, yAbs,
                 actionToTransferMode(recommendedDropAction),
                 dropTargetAssistant);
+        } finally {
+            if (PULSE_LOGGING_ENABLED) {
+                PulseLogger.newInput(null);
+            }
+        }
         return transferModeToAction(action);
     }
 
@@ -607,12 +676,30 @@
                                           final int x, final int y, final int xAbs, final int yAbs,
                                           final ClipboardAssistance assistant)
     {
+        if (PULSE_LOGGING_ENABLED) {
+            PulseLogger.newInput("DRAG_START");
+        }
         dropSourceAssistant = assistant;
-        dndHandler.handleDragStart(button, x, y, xAbs, yAbs, assistant);
+        try {
+            dndHandler.handleDragStart(button, x, y, xAbs, yAbs, assistant);
+        } finally {
+            if (PULSE_LOGGING_ENABLED) {
+                PulseLogger.newInput(null);
+            }
+        }
     }
 
     @Override public void handleDragEnd(View view, final int performedAction) {
-        dndHandler.handleDragEnd(actionToTransferMode(performedAction), dropSourceAssistant);
+        if (PULSE_LOGGING_ENABLED) {
+            PulseLogger.newInput("DRAG_END");
+        }
+        try {
+            dndHandler.handleDragEnd(actionToTransferMode(performedAction), dropSourceAssistant);
+        } finally {
+            if (PULSE_LOGGING_ENABLED) {
+                PulseLogger.newInput(null);
+            }
+        }
     }
 
     // TODO - dropTargetListener.dropActionChanged
@@ -684,11 +771,20 @@
     }
 
     @Override public void handleViewEvent(View view, long time, final int type) {
+        if (PULSE_LOGGING_ENABLED) {
+            PulseLogger.newInput("VIEW_EVENT: "+ViewEvent.getTypeString(type));
+        }
         viewNotification.view = view;
         viewNotification.time = time;
         viewNotification.type = type;
-
-        AccessController.doPrivileged(viewNotification, scene.getAccessControlContext());
+        try {
+            AccessController.doPrivileged(viewNotification, scene.getAccessControlContext());
+        }
+        finally {
+            if (PULSE_LOGGING_ENABLED) {
+                PulseLogger.newInput(null);
+            }
+        }
     }
 
     @Override public void handleScrollGestureEvent(
@@ -697,6 +793,9 @@
             final int x, final int y, final int xAbs, final int yAbs, final double dx, final double dy,
             final double totaldx, final double totaldy, final double multiplierX, final double multiplierY)
     {
+        if (PULSE_LOGGING_ENABLED) {
+            PulseLogger.newInput("SCROLL_GESTURE_EVENT");
+        }
         WindowStage stage = scene.getWindowStage();
         try {
             if (stage != null) {
@@ -741,6 +840,9 @@
             if (stage != null) {
                 stage.setInEventHandler(false);
             }
+            if (PULSE_LOGGING_ENABLED) {
+                PulseLogger.newInput(null);
+            }
         }
     }
 
@@ -752,6 +854,9 @@
             final double scale, double expansion,
             final double totalscale, double totalexpansion)
     {
+        if (PULSE_LOGGING_ENABLED) {
+            PulseLogger.newInput("ZOOM_GESTURE_EVENT");
+        }
         WindowStage stage = scene.getWindowStage();
         try {
             if (stage != null) {
@@ -793,6 +898,9 @@
             if (stage != null) {
                 stage.setInEventHandler(false);
             }
+            if (PULSE_LOGGING_ENABLED) {
+                PulseLogger.newInput(null);
+            }
         }
     }
 
@@ -803,6 +911,9 @@
             final int originxAbs, final int originyAbs,
             final double dangle, final double totalangle)
     {
+        if (PULSE_LOGGING_ENABLED) {
+            PulseLogger.newInput("ROTATE_GESTURE_EVENT");
+        }
         WindowStage stage = scene.getWindowStage();
         try {
             if (stage != null) {
@@ -844,6 +955,9 @@
             if (stage != null) {
                 stage.setInEventHandler(false);
             }
+            if (PULSE_LOGGING_ENABLED) {
+                PulseLogger.newInput(null);
+            }
         }
     }
 
@@ -853,6 +967,9 @@
             boolean isInertia, final int touchCount,
             final int dir, final int x, final int y, final int xAbs, final int yAbs)
     {
+        if (PULSE_LOGGING_ENABLED) {
+            PulseLogger.newInput("SWIPE_EVENT");
+        }
         WindowStage stage = scene.getWindowStage();
         try {
             if (stage != null) {
@@ -897,6 +1014,9 @@
             if (stage != null) {
                 stage.setInEventHandler(false);
             }
+            if (PULSE_LOGGING_ENABLED) {
+                PulseLogger.newInput(null);
+            }
         }
     }
 
@@ -904,6 +1024,9 @@
             View view, final long time, final int modifiers,
             final boolean isDirect, final int touchEventCount)
     {
+        if (PULSE_LOGGING_ENABLED) {
+            PulseLogger.newInput("BEGIN_TOUCH_EVENT");
+        }
         WindowStage stage = scene.getWindowStage();
         try {
             if (stage != null) {
@@ -927,6 +1050,9 @@
             if (stage != null) {
                 stage.setInEventHandler(false);
             }
+            if (PULSE_LOGGING_ENABLED) {
+                PulseLogger.newInput(null);
+            }
         }
 
         gestures.notifyBeginTouchEvent(time, modifiers, isDirect, touchEventCount);
@@ -936,6 +1062,9 @@
             View view, final long time, final int type, final long touchId,
             final int x, final int y, final int xAbs, final int yAbs)
     {
+        if (PULSE_LOGGING_ENABLED) {
+            PulseLogger.newInput("NEXT_TOUCH_EVENT");
+        }
         WindowStage stage = scene.getWindowStage();
         try {
             if (stage != null) {
@@ -971,12 +1100,18 @@
             if (stage != null) {
                 stage.setInEventHandler(false);
             }
+            if (PULSE_LOGGING_ENABLED) {
+                PulseLogger.newInput(null);
+            }
         }
 
         gestures.notifyNextTouchEvent(time, type, touchId, x, y, xAbs, yAbs);
     }
 
     @Override public void handleEndTouchEvent(View view, long time) {
+        if (PULSE_LOGGING_ENABLED) {
+            PulseLogger.newInput("END_TOUCH_EVENT");
+        }
         WindowStage stage = scene.getWindowStage();
         try {
             if (stage != null) {
@@ -995,6 +1130,9 @@
             if (stage != null) {
                 stage.setInEventHandler(false);
             }
+            if (PULSE_LOGGING_ENABLED) {
+                PulseLogger.newInput(null);
+            }
         }
 
         gestures.notifyEndTouchEvent(time);
--- a/modules/graphics/src/main/java/com/sun/javafx/tk/quantum/PaintCollector.java	Mon Feb 24 10:32:51 2014 +1300
+++ b/modules/graphics/src/main/java/com/sun/javafx/tk/quantum/PaintCollector.java	Mon Feb 24 17:31:59 2014 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -38,7 +38,7 @@
 import com.sun.javafx.tk.RenderJob;
 
 import static com.sun.javafx.logging.PulseLogger.PULSE_LOGGING_ENABLED;
-import static com.sun.javafx.logging.PulseLogger.PULSE_LOGGER;
+import com.sun.javafx.logging.PulseLogger;
 
 /**
  * Manages the collection and rendering of dirty scenes. This class has
@@ -304,7 +304,7 @@
             // If pulse logging is enabled, then we must call renderEnd now
             // that we know that all of the scene's being rendered are finished
             if (PULSE_LOGGING_ENABLED) {
-                PULSE_LOGGER.renderEnd();
+                PulseLogger.renderEnd();
             }
         }
 
@@ -380,7 +380,7 @@
         // If pulse logging is enabled, then we must call renderStart
         // BEFORE we actually call repaint on any of the dirty scenes.
         if (PULSE_LOGGING_ENABLED) {
-            PULSE_LOGGER.renderStart();
+            PulseLogger.renderStart();
         }
 
         // This part needs to be handled a bit differently depending on whether our platform has a native
--- a/modules/graphics/src/main/java/com/sun/javafx/tk/quantum/PresentingPainter.java	Mon Feb 24 10:32:51 2014 +1300
+++ b/modules/graphics/src/main/java/com/sun/javafx/tk/quantum/PresentingPainter.java	Mon Feb 24 17:31:59 2014 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2014, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -26,6 +26,7 @@
 package com.sun.javafx.tk.quantum;
 
 import com.sun.javafx.logging.PulseLogger;
+import static com.sun.javafx.logging.PulseLogger.PULSE_LOGGING_ENABLED;
 import com.sun.prism.Graphics;
 import com.sun.prism.GraphicsPipeline;
 import com.sun.prism.impl.Disposer;
@@ -89,26 +90,21 @@
                     paintImpl(g);
                 }
 
-                long start = PulseLogger.PULSE_LOGGING_ENABLED ? System.currentTimeMillis() : 0;
+                if (PULSE_LOGGING_ENABLED) {
+                    PulseLogger.newPhase("Presenting");
+                }
                 if (!presentable.prepare(null)) {
                     disposePresentable();
                     sceneState.getScene().entireSceneNeedsRepaint();
-                    if (PulseLogger.PULSE_LOGGING_ENABLED) {
-                        PulseLogger.PULSE_LOGGER.renderMessage(start, System.currentTimeMillis(), "Presentable.prepare");
-                    }
                     return;
                 }
                 
                 /* present for vsync buffer swap */
-                start = PulseLogger.PULSE_LOGGING_ENABLED ? System.currentTimeMillis() : 0;
                 if (vs.getDoPresent()) {
                     if (!presentable.present()) {
                         disposePresentable();
                         sceneState.getScene().entireSceneNeedsRepaint();
                     }
-                    if (PulseLogger.PULSE_LOGGING_ENABLED) {
-                        PulseLogger.PULSE_LOGGER.renderMessage(start, System.currentTimeMillis(), "Presentable.present");
-                    }
                 }
             }
         } catch (Throwable th) {
@@ -128,9 +124,6 @@
             ManagedResource.freeDisposalRequestedAndCheckResources(errored);
 
             renderLock.unlock();
-            if (PulseLogger.PULSE_LOGGING_ENABLED) {
-                PulseLogger.PULSE_LOGGER.renderMessage(System.currentTimeMillis(), System.currentTimeMillis(), "Finished Presenting Painter");
-            }
         }
     }
 }
--- a/modules/graphics/src/main/java/com/sun/javafx/tk/quantum/QuantumToolkit.java	Mon Feb 24 10:32:51 2014 +1300
+++ b/modules/graphics/src/main/java/com/sun/javafx/tk/quantum/QuantumToolkit.java	Mon Feb 24 17:31:59 2014 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -126,7 +126,7 @@
 import com.sun.scenario.effect.Filterable;
 import com.sun.scenario.effect.impl.prism.PrFilterContext;
 import com.sun.scenario.effect.impl.prism.PrImage;
-import static com.sun.javafx.logging.PulseLogger.PULSE_LOGGER;
+import com.sun.javafx.logging.PulseLogger;
 import static com.sun.javafx.logging.PulseLogger.PULSE_LOGGING_ENABLED;
 import com.sun.prism.impl.ManagedResource;
 
@@ -465,7 +465,7 @@
     void pulse(boolean collect) {
         try {
             if (PULSE_LOGGING_ENABLED) {
-                PULSE_LOGGER.pulseStart();
+                PulseLogger.pulseStart();
             }
 
             if (!toolkitRunning.get()) {
@@ -483,7 +483,7 @@
         } finally {
             endPulseRunning();
             if (PULSE_LOGGING_ENABLED) {
-                PULSE_LOGGER.pulseEnd();
+                PulseLogger.pulseEnd();
             }
         }
     }
--- a/modules/graphics/src/main/java/com/sun/javafx/tk/quantum/ViewPainter.java	Mon Feb 24 10:32:51 2014 +1300
+++ b/modules/graphics/src/main/java/com/sun/javafx/tk/quantum/ViewPainter.java	Mon Feb 24 17:31:59 2014 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2014, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -49,7 +49,7 @@
 import com.sun.prism.impl.PrismSettings;
 import com.sun.prism.paint.Color;
 import com.sun.prism.paint.Paint;
-import static com.sun.javafx.logging.PulseLogger.PULSE_LOGGER;
+import com.sun.javafx.logging.PulseLogger;
 import static com.sun.javafx.logging.PulseLogger.PULSE_LOGGING_ENABLED;
 
 /**
@@ -226,7 +226,9 @@
         // If we're rendering with dirty regions, then we'll call the root node to accumulate
         // the dirty regions and then again to do the pre culling.
         if (!renderEverything) {
-            long start = PULSE_LOGGING_ENABLED ? System.currentTimeMillis() : 0;
+            if (PULSE_LOGGING_ENABLED) {
+                PulseLogger.newPhase("Dirty Opts Computed");
+            }
             clip.setBounds(0, 0, width, height);
             dirtyRegionTemp.makeEmpty();
             dirtyRegionContainer.reset();
@@ -240,9 +242,6 @@
             if (status == DirtyRegionContainer.DTR_OK) {
                 root.doPreCulling(dirtyRegionContainer, tx, projTx);
             }
-            if (PULSE_LOGGING_ENABLED) {
-                PULSE_LOGGER.renderMessage(start, System.currentTimeMillis(), "Dirty Opts Computed");
-            }
         }
 
         // We're going to need to iterate over the dirty region container a lot, so we
@@ -256,23 +255,22 @@
             g.setHasPreCullingBits(true);
 
             // Find the render roots. There is a different render root for each dirty region
-            long start = PULSE_LOGGING_ENABLED ? System.currentTimeMillis() : 0;
+            if (PULSE_LOGGING_ENABLED) {
+                PulseLogger.newPhase("Render Roots Discovered");
+            }
             for (int i = 0; i < dirtyRegionSize; ++i) {
                 NodePath path = getRootPath(i);
                 path.clear();
                 root.getRenderRoot(getRootPath(i), dirtyRegionContainer.getDirtyRegion(i), i, tx, projTx);
             }
-            if (PULSE_LOGGING_ENABLED) {
-                PULSE_LOGGER.renderMessage(start, System.currentTimeMillis(), "Render Roots Discovered");
-            }
 
             // For debug purposes, write out to the pulse logger the number and size of the dirty
             // regions that are being used to render this pulse.
             if (PULSE_LOGGING_ENABLED) {
-                PULSE_LOGGER.renderMessage(dirtyRegionSize + " different dirty regions to render");
+                PulseLogger.addMessage(dirtyRegionSize + " different dirty regions to render");
                 for (int i=0; i<dirtyRegionSize; i++) {
-                    PULSE_LOGGER.renderMessage("Dirty Region " + i + ": " + dirtyRegionContainer.getDirtyRegion(i));
-                    PULSE_LOGGER.renderMessage("Render Root Path " + i + ": " + getRootPath(i));
+                    PulseLogger.addMessage("Dirty Region " + i + ": " + dirtyRegionContainer.getDirtyRegion(i));
+                    PulseLogger.addMessage("Render Root Path " + i + ": " + getRootPath(i));
                 }
             }
 
@@ -292,7 +290,7 @@
                     }
                 }
                 root.printDirtyOpts(s, roots);
-                PULSE_LOGGER.renderMessage(s.toString());
+                PulseLogger.addMessage(s.toString());
             }
 
             // Paint each dirty region
@@ -446,31 +444,27 @@
             // If the path is not empty, the first node must be the root node
             assert(renderRootPath.getCurrentNode() == root);
         }
-        long start = PULSE_LOGGING_ENABLED ? System.currentTimeMillis() : 0;
-        try {
-            GlassScene scene = sceneState.getScene();
-            scene.clearEntireSceneDirty();
-            g.setLights(scene.getLights());
-            g.setDepthBuffer(scene.getDepthBuffer());
-            Color clearColor = sceneState.getClearColor();
-            if (clearColor != null) {
-                g.clear(clearColor);
+        if (PULSE_LOGGING_ENABLED) {
+            PulseLogger.newPhase("Painting");
+        }
+        GlassScene scene = sceneState.getScene();
+        scene.clearEntireSceneDirty();
+        g.setLights(scene.getLights());
+        g.setDepthBuffer(scene.getDepthBuffer());
+        Color clearColor = sceneState.getClearColor();
+        if (clearColor != null) {
+            g.clear(clearColor);
+        }
+        Paint curPaint = sceneState.getCurrentPaint();
+        if (curPaint != null) {
+            if (curPaint.getType() != com.sun.prism.paint.Paint.Type.COLOR) {
+                g.getRenderTarget().setOpaque(curPaint.isOpaque());
             }
-            Paint curPaint = sceneState.getCurrentPaint();
-            if (curPaint != null) {
-                if (curPaint.getType() != com.sun.prism.paint.Paint.Type.COLOR) {
-                    g.getRenderTarget().setOpaque(curPaint.isOpaque());
-                }
-                g.setPaint(curPaint);
-                g.fillQuad(0, 0, width, height);
-            }
-            g.setCamera(sceneState.getCamera());
-            g.setRenderRoot(renderRootPath);
-            root.render(g);
-        } finally {
-            if (PULSE_LOGGING_ENABLED) {
-                PULSE_LOGGER.renderMessage(start, System.currentTimeMillis(), "Painted");
-            }
+            g.setPaint(curPaint);
+            g.fillQuad(0, 0, width, height);
         }
+        g.setCamera(sceneState.getCamera());
+        g.setRenderRoot(renderRootPath);
+        root.render(g);
     }
 }
--- a/modules/graphics/src/main/java/com/sun/prism/impl/GlyphCache.java	Mon Feb 24 10:32:51 2014 +1300
+++ b/modules/graphics/src/main/java/com/sun/prism/impl/GlyphCache.java	Mon Feb 24 17:31:59 2014 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2009, 2014, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -45,7 +45,7 @@
 import java.util.WeakHashMap;
 
 import static com.sun.javafx.logging.PulseLogger.PULSE_LOGGING_ENABLED;
-import static com.sun.javafx.logging.PulseLogger.PULSE_LOGGER;
+import com.sun.javafx.logging.PulseLogger;
 
 import com.sun.prism.ResourceFactory;
 import com.sun.prism.Texture.WrapMode;
@@ -285,7 +285,9 @@
                                      rect);
 
                 if (!packer.add(rect)) {
-                    if (PULSE_LOGGING_ENABLED) PULSE_LOGGER.renderIncrementCounter("Font Glyph Cache Cleared");
+                    if (PULSE_LOGGING_ENABLED) {
+                        PulseLogger.incrementCounter("Font Glyph Cache Cleared");
+                    }
                     // If add fails,clear up the cache. Try add again.
                     clearAll();
                     packer.add(rect);
--- a/modules/graphics/src/main/java/javafx/scene/Scene.java	Mon Feb 24 10:32:51 2014 +1300
+++ b/modules/graphics/src/main/java/javafx/scene/Scene.java	Mon Feb 24 17:31:59 2014 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -86,7 +86,7 @@
 import java.security.PrivilegedAction;
 import java.util.*;
 
-import static com.sun.javafx.logging.PulseLogger.PULSE_LOGGER;
+import com.sun.javafx.logging.PulseLogger;
 import static com.sun.javafx.logging.PulseLogger.PULSE_LOGGING_ENABLED;
 
 /**
@@ -2347,31 +2347,32 @@
             focusCleanup();
 
             if (PULSE_LOGGING_ENABLED) {
-                long start = System.currentTimeMillis();
-                Scene.this.doCSSPass();
-                PULSE_LOGGER.fxMessage(start, System.currentTimeMillis(), "CSS Pass");
-
-                start = System.currentTimeMillis();
-                Scene.this.doLayoutPass();
-                PULSE_LOGGER.fxMessage(start, System.currentTimeMillis(), "Layout Pass");
-            } else {
-                Scene.this.doCSSPass();
-                Scene.this.doLayoutPass();
+                PulseLogger.newPhase("CSS Pass");
             }
+            Scene.this.doCSSPass();
+
+            if (PULSE_LOGGING_ENABLED) {
+                PulseLogger.newPhase("Layout Pass");
+            }
+            Scene.this.doLayoutPass();
 
             boolean dirty = dirtyNodes == null || dirtyNodesSize != 0 || !isDirtyEmpty();
             if (dirty) {
+                if (PULSE_LOGGING_ENABLED) {
+                    PulseLogger.newPhase("Update bounds");
+                }
                 getRoot().updateBounds();
                 if (impl_peer != null) {
                     try {
-                        long start = PULSE_LOGGING_ENABLED ? System.currentTimeMillis() : 0;
+                        if (PULSE_LOGGING_ENABLED) {
+                            PulseLogger.newPhase("Waiting for previous rendering");
+                        }
                         impl_peer.waitForRenderingToComplete();
                         impl_peer.waitForSynchronization();
+                        // synchronize scene properties
                         if (PULSE_LOGGING_ENABLED) {
-                            PULSE_LOGGER.fxMessage(start, System.currentTimeMillis(), "Waiting for previous rendering");
+                            PulseLogger.newPhase("Copy state to render graph");
                         }
-                        start = PULSE_LOGGING_ENABLED ? System.currentTimeMillis() : 0;
-                        // synchronize scene properties
                         syncLights();
                         synchronizeSceneProperties();
                         // Run the synchronizer
@@ -2379,21 +2380,16 @@
                         Scene.this.mouseHandler.pulse();
                         // Tell the scene peer that it needs to repaint
                         impl_peer.markDirty();
-                        if (PULSE_LOGGING_ENABLED) {
-                            PULSE_LOGGER.fxMessage(start, System.currentTimeMillis(), "Copy state to render graph");
-                        }
                     } finally {
                         impl_peer.releaseSynchronization(true);
                     }
                 } else {
-                    long start = PULSE_LOGGING_ENABLED ? System.currentTimeMillis() : 0;
+                    if (PULSE_LOGGING_ENABLED) {
+                        PulseLogger.newPhase("Synchronize with null peer");
+                    }
                     synchronizeSceneProperties();
                     synchronizeSceneNodes();
                     Scene.this.mouseHandler.pulse();
-                    if (PULSE_LOGGING_ENABLED) {
-                        PULSE_LOGGER.fxMessage(start, System.currentTimeMillis(), "Synchronize with null peer");
-                    }
-
                 }
 
                 if (Scene.this.getRoot().cssFlag != CssFlags.CLEAN) {