diff --git a/interface/resources/qml/Stats.qml b/interface/resources/qml/Stats.qml
index 895055618b..086c1d15d2 100644
--- a/interface/resources/qml/Stats.qml
+++ b/interface/resources/qml/Stats.qml
@@ -83,6 +83,18 @@ Item {
                         text: "Missed Frame Count: " + root.appdropped;
                         visible: root.appdropped > 0;
                     }
+                    StatText {
+                        text: "Long Render Count: " + root.longrenders;
+                        visible: root.longrenders > 0;
+                    }
+                    StatText {
+                        text: "Long Submit Count: " + root.longsubmits;
+                        visible: root.longsubmits > 0;
+                    }
+                    StatText {
+                        text: "Long Frame Count: " + root.longframes;
+                        visible: root.longframes > 0;
+                    }
                     StatText {
                         text: "Packets In/Out: " + root.packetInCount + "/" + root.packetOutCount
                     }
diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp
index 95f31283d0..ff0028322c 100644
--- a/interface/src/ui/Stats.cpp
+++ b/interface/src/ui/Stats.cpp
@@ -127,12 +127,18 @@ void Stats::updateStats(bool force) {
         auto displayPlugin = qApp->getActiveDisplayPlugin();
         auto stats = displayPlugin->getHardwareStats();
         STAT_UPDATE(appdropped, stats["app_dropped_frame_count"].toInt());
+        STAT_UPDATE(longrenders, stats["long_render_count"].toInt());
+        STAT_UPDATE(longsubmits, stats["long_submit_count"].toInt());
+        STAT_UPDATE(longframes, stats["long_frame_count"].toInt());
         STAT_UPDATE(renderrate, displayPlugin->renderRate());
         STAT_UPDATE(presentrate, displayPlugin->presentRate());
         STAT_UPDATE(presentnewrate, displayPlugin->newFramePresentRate());
         STAT_UPDATE(presentdroprate, displayPlugin->droppedFrameRate());
         STAT_UPDATE(stutterrate, displayPlugin->stutterRate());
     } else {
+        STAT_UPDATE(appdropped, -1);
+        STAT_UPDATE(longrenders, -1);
+        STAT_UPDATE(longsubmits, -1);
         STAT_UPDATE(presentrate, -1);
         STAT_UPDATE(presentnewrate, -1);
         STAT_UPDATE(presentdroprate, -1);
diff --git a/interface/src/ui/Stats.h b/interface/src/ui/Stats.h
index f0bd1f8ab7..3fe851494c 100644
--- a/interface/src/ui/Stats.h
+++ b/interface/src/ui/Stats.h
@@ -40,6 +40,9 @@ class Stats : public QQuickItem {
     STATS_PROPERTY(float, stutterrate, 0)
 
     STATS_PROPERTY(int, appdropped, 0)
+    STATS_PROPERTY(int, longsubmits, 0)
+    STATS_PROPERTY(int, longrenders, 0)
+    STATS_PROPERTY(int, longframes, 0)
 
     STATS_PROPERTY(float, presentnewrate, 0)
     STATS_PROPERTY(float, presentdroprate, 0)
@@ -137,6 +140,9 @@ public slots:
     void forceUpdateStats() { updateStats(true); }
 
 signals:
+    void longsubmitsChanged();
+    void longrendersChanged();
+    void longframesChanged();
     void appdroppedChanged();
     void framerateChanged();
     void expandedChanged();
diff --git a/plugins/oculus/src/OculusDisplayPlugin.cpp b/plugins/oculus/src/OculusDisplayPlugin.cpp
index 060823a748..b076170ae5 100644
--- a/plugins/oculus/src/OculusDisplayPlugin.cpp
+++ b/plugins/oculus/src/OculusDisplayPlugin.cpp
@@ -31,6 +31,10 @@ OculusDisplayPlugin::OculusDisplayPlugin() {
 
 bool OculusDisplayPlugin::internalActivate() {
     bool result = Parent::internalActivate();
+    _longSubmits = 0;
+    _longRenders = 0;
+    _longFrames = 0;
+
     currentDebugMode = ovrPerfHud_Off;
     if (result && _session) {
         ovr_SetInt(_session, OVR_PERF_HUD_MODE, currentDebugMode);
@@ -112,35 +116,43 @@ void OculusDisplayPlugin::uncustomizeContext() {
     Parent::uncustomizeContext();
 }
 
+static const uint64_t FRAME_BUDGET = (11 * USECS_PER_MSEC);
+static const uint64_t FRAME_OVER_BUDGET = (15 * USECS_PER_MSEC);
+
 void OculusDisplayPlugin::hmdPresent() {
+    static uint64_t lastSubmitEnd = 0;
+
     if (!_customized) {
         return;
     }
 
     PROFILE_RANGE_EX(render, __FUNCTION__, 0xff00ff00, (uint64_t)_currentFrame->frameIndex)
 
-    int curIndex;
-    ovr_GetTextureSwapChainCurrentIndex(_session, _textureSwapChain, &curIndex);
-    GLuint curTexId;
-    ovr_GetTextureSwapChainBufferGL(_session, _textureSwapChain, curIndex, &curTexId);
+    {
+        PROFILE_RANGE_EX(render, "Oculus Blit", 0xff00ff00, (uint64_t)_currentFrame->frameIndex)
+        int curIndex;
+        ovr_GetTextureSwapChainCurrentIndex(_session, _textureSwapChain, &curIndex);
+        GLuint curTexId;
+        ovr_GetTextureSwapChainBufferGL(_session, _textureSwapChain, curIndex, &curTexId);
 
-    // Manually bind the texture to the FBO
-    // FIXME we should have a way of wrapping raw GL ids in GPU objects without 
-    // taking ownership of the object
-    auto fbo = getGLBackend()->getFramebufferID(_outputFramebuffer);
-    glNamedFramebufferTexture(fbo, GL_COLOR_ATTACHMENT0, curTexId, 0);
-    render([&](gpu::Batch& batch) {
-        batch.enableStereo(false);
-        batch.setFramebuffer(_outputFramebuffer);
-        batch.setViewportTransform(ivec4(uvec2(), _outputFramebuffer->getSize()));
-        batch.setStateScissorRect(ivec4(uvec2(), _outputFramebuffer->getSize()));
-        batch.resetViewTransform();
-        batch.setProjectionTransform(mat4());
-        batch.setPipeline(_presentPipeline);
-        batch.setResourceTexture(0, _compositeFramebuffer->getRenderBuffer(0));
-        batch.draw(gpu::TRIANGLE_STRIP, 4);
-    });
-    glNamedFramebufferTexture(fbo, GL_COLOR_ATTACHMENT0, 0, 0);
+        // Manually bind the texture to the FBO
+        // FIXME we should have a way of wrapping raw GL ids in GPU objects without 
+        // taking ownership of the object
+        auto fbo = getGLBackend()->getFramebufferID(_outputFramebuffer);
+        glNamedFramebufferTexture(fbo, GL_COLOR_ATTACHMENT0, curTexId, 0);
+        render([&](gpu::Batch& batch) {
+            batch.enableStereo(false);
+            batch.setFramebuffer(_outputFramebuffer);
+            batch.setViewportTransform(ivec4(uvec2(), _outputFramebuffer->getSize()));
+            batch.setStateScissorRect(ivec4(uvec2(), _outputFramebuffer->getSize()));
+            batch.resetViewTransform();
+            batch.setProjectionTransform(mat4());
+            batch.setPipeline(_presentPipeline);
+            batch.setResourceTexture(0, _compositeFramebuffer->getRenderBuffer(0));
+            batch.draw(gpu::TRIANGLE_STRIP, 4);
+        });
+        glNamedFramebufferTexture(fbo, GL_COLOR_ATTACHMENT0, 0, 0);
+    }
 
     {
         auto result = ovr_CommitTextureSwapChain(_session, _textureSwapChain);
@@ -148,8 +160,33 @@ void OculusDisplayPlugin::hmdPresent() {
         _sceneLayer.SensorSampleTime = _currentPresentFrameInfo.sensorSampleTime;
         _sceneLayer.RenderPose[ovrEyeType::ovrEye_Left] = ovrPoseFromGlm(_currentPresentFrameInfo.renderPose);
         _sceneLayer.RenderPose[ovrEyeType::ovrEye_Right] = ovrPoseFromGlm(_currentPresentFrameInfo.renderPose);
+
+        auto submitStart = usecTimestampNow();
+        uint64_t nonSubmitInterval = 0;
+        if (lastSubmitEnd != 0) {
+            nonSubmitInterval = submitStart - lastSubmitEnd;
+            if (nonSubmitInterval > FRAME_BUDGET) {
+                ++_longRenders;
+            }
+        }
         ovrLayerHeader* layers = &_sceneLayer.Header;
-        result = ovr_SubmitFrame(_session, _currentFrame->frameIndex, &_viewScaleDesc, &layers, 1);
+        {
+            PROFILE_RANGE_EX(render, "Oculus Submit", 0xff00ff00, (uint64_t)_currentFrame->frameIndex)
+            result = ovr_SubmitFrame(_session, _currentFrame->frameIndex, &_viewScaleDesc, &layers, 1);
+        }
+        lastSubmitEnd = usecTimestampNow();
+        if (nonSubmitInterval != 0) {
+            auto submitInterval = lastSubmitEnd - submitStart;
+            if (nonSubmitInterval < FRAME_BUDGET && submitInterval > FRAME_BUDGET) {
+                ++_longSubmits;
+            }
+            if ((nonSubmitInterval + submitInterval) > FRAME_OVER_BUDGET) {
+                ++_longFrames;
+            }
+        }
+
+
+
         if (!OVR_SUCCESS(result)) {
             logWarning("Failed to present");
         }
@@ -168,6 +205,7 @@ void OculusDisplayPlugin::hmdPresent() {
         _appDroppedFrames.store(appDroppedFrames);
         _compositorDroppedFrames.store(compositorDroppedFrames);
     }
+
     _presentRate.increment();
 }
 
@@ -176,6 +214,9 @@ QJsonObject OculusDisplayPlugin::getHardwareStats() const {
     QJsonObject hardwareStats;
     hardwareStats["app_dropped_frame_count"] = _appDroppedFrames.load();
     hardwareStats["compositor_dropped_frame_count"] = _compositorDroppedFrames.load();
+    hardwareStats["long_render_count"] = _longRenders.load();
+    hardwareStats["long_submit_count"] = _longSubmits.load();
+    hardwareStats["long_frame_count"] = _longFrames.load();
     return hardwareStats;
 }
 
diff --git a/plugins/oculus/src/OculusDisplayPlugin.h b/plugins/oculus/src/OculusDisplayPlugin.h
index ccab31785b..6fc50b829f 100644
--- a/plugins/oculus/src/OculusDisplayPlugin.h
+++ b/plugins/oculus/src/OculusDisplayPlugin.h
@@ -41,5 +41,8 @@ private:
 
     std::atomic_int _compositorDroppedFrames;
     std::atomic_int _appDroppedFrames;
+    std::atomic_int _longSubmits;
+    std::atomic_int _longRenders;
+    std::atomic_int _longFrames;
 };