feat: implement real-time performance overlay (#168) - #182
Conversation
🤖 CodeAnt AI — Review Status
|
Thanks for using CodeAnt! 🎉We're free for open-source projects. if you're enjoying it, help us grow by sharing. Share on X · |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
💤 Files with no reviewable changes (1)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughThe frontend adds a Zustand performance store, Cesium and network instrumentation, and a configurable diagnostics overlay. The overlay is integrated into the main layout with FPS, memory, network, Cesium, and bottleneck views. ChangesPerformance diagnostics
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant CesiumViewer
participant useCesiumPerformance
participant usePerformanceStore
participant PerformanceOverlay
CesiumViewer->>useCesiumPerformance: emit render events
useCesiumPerformance->>usePerformanceStore: publish Cesium metrics
PerformanceOverlay->>usePerformanceStore: read metrics and alerts
usePerformanceStore-->>PerformanceOverlay: return diagnostic data
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
|
||
| </div> | ||
|
|
||
| <PerformanceOverlay /> |
There was a problem hiding this comment.
Suggestion: PerformanceOverlay is mounted permanently, and its effects start the animation-frame loop, memory polling, keyboard listener, and network interception before checking isEnabled; closing or never opening the overlay only returns null from its render and does not stop those activities. This imposes continuous monitoring overhead on every dashboard session even when the developer overlay is disabled. Gate the monitoring effects on enabled state or mount the monitoring lifecycle only while enabled. [performance]
Severity Level: Major ⚠️
- ⚠️ Every dashboard session performs hidden frame monitoring.
- ⚠️ All fetches incur interceptor and store-update overhead.
- ⚠️ Disabled overlay still polls browser memory telemetry.Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** frontend/src/components/layouts/MainLayout.tsx
**Line:** 414:414
**Comment:**
*Performance: `PerformanceOverlay` is mounted permanently, and its effects start the animation-frame loop, memory polling, keyboard listener, and network interception before checking `isEnabled`; closing or never opening the overlay only returns `null` from its render and does not stop those activities. This imposes continuous monitoring overhead on every dashboard session even when the developer overlay is disabled. Gate the monitoring effects on enabled state or mount the monitoring lifecycle only while enabled.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix| const entityCount = viewer.entities.values.length; | ||
| let satelliteCount = 0; | ||
|
|
||
| // Count entities marked as catalog satellites/objects | ||
| for (const entity of viewer.entities.values) { | ||
| if (entity.properties?.catalogData) { | ||
| satelliteCount++; | ||
| } |
There was a problem hiding this comment.
Suggestion: Scanning every entity on every Cesium postRender event adds an O(n) operation to each rendered frame. Since useCesiumPerformance is installed for EarthTwin regardless of whether the overlay is enabled, large catalogs and collision sets can cause the diagnostic instrumentation itself to reduce rendering performance and distort the measured scene timings. Maintain these counts incrementally when entities are added or removed, or sample the scan less frequently. [performance]
Severity Level: Major ⚠️
- ⚠️ Cesium performs an entity scan on every rendered frame.
- ⚠️ Large catalog views receive unnecessary instrumentation work.
- ⚠️ Diagnostic overhead can distort reported scene timings.Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** frontend/src/hooks/useCesiumPerformance.ts
**Line:** 25:32
**Comment:**
*Performance: Scanning every entity on every Cesium `postRender` event adds an O(n) operation to each rendered frame. Since `useCesiumPerformance` is installed for `EarthTwin` regardless of whether the overlay is enabled, large catalogs and collision sets can cause the diagnostic instrumentation itself to reduce rendering performance and distort the measured scene timings. Maintain these counts incrementally when entities are added or removed, or sample the scan less frequently.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix| if (sceneTimeMs > 35) { | ||
| const alert: BottleneckAlert = { | ||
| id: `cesium-${Date.now()}`, | ||
| type: 'SLOW_SCENE_UPDATE', | ||
| severity: sceneTimeMs > 60 ? 'CRITICAL' : 'WARNING', | ||
| message: `Cesium scene update took ${sceneTimeMs.toFixed(1)}ms (${entityCount} active entities)`, | ||
| timestamp: Date.now(), | ||
| }; | ||
| newBottlenecks = [alert, ...state.bottlenecks.slice(0, MAX_BOTTLENECK_LOGS - 1)]; | ||
| } |
There was a problem hiding this comment.
Suggestion: Every frame whose measured scene time exceeds 35 ms appends another SLOW_SCENE_UPDATE alert, with no duplicate suppression or cooldown. A sustained slowdown therefore fills the 15-entry log with identical samples within a fraction of a second, hides other alert types, and keeps the warning indicator active even when the slowdown has ended. Suppress repeated alerts for the same condition or only add one after a recovery transition/cooldown. [logic error]
Severity Level: Major ⚠️
- ⚠️ Bottleneck history fills with identical scene alerts.
- ⚠️ Earlier memory and network alerts are evicted.
- ⚠️ Developers lose a useful diagnosis timeline.Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** frontend/src/store/performanceStore.ts
**Line:** 157:166
**Comment:**
*Logic Error: Every frame whose measured scene time exceeds 35 ms appends another `SLOW_SCENE_UPDATE` alert, with no duplicate suppression or cooldown. A sustained slowdown therefore fills the 15-entry log with identical samples within a fraction of a second, hides other alert types, and keeps the warning indicator active even when the slowdown has ended. Suppress repeated alerts for the same condition or only add one after a recovery transition/cooldown.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix| const currentFps = Math.min(60, Math.round((frameCount * 1000) / (now - lastFpsUpdate))); | ||
| const avgFrameTime = delta; | ||
| updateFrameMetrics(currentFps, avgFrameTime, isDropped); |
There was a problem hiding this comment.
Suggestion: avgFrameTime is populated with only the final frame's delta, despite being displayed as the frame time for the entire 500 ms FPS sample and named as an average. A single scheduling stall or unusually short final frame can make the displayed frame time contradict the reported FPS and bottleneck message. Accumulate frame deltas during the sample and report their average, or rename the metric to indicate that it is the last-frame duration. [incorrect variable usage]
Severity Level: Major ⚠️
- ⚠️ Overview frame-time metric contradicts sampled FPS.
- ⚠️ Low-FPS alerts report misleading frame durations.
- ⚠️ Developers may misdiagnose rendering stalls.Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** frontend/src/components/PerformanceOverlay/PerformanceOverlay.tsx
**Line:** 61:63
**Comment:**
*Incorrect Variable Usage: `avgFrameTime` is populated with only the final frame's `delta`, despite being displayed as the frame time for the entire 500 ms FPS sample and named as an average. A single scheduling stall or unusually short final frame can make the displayed frame time contradict the reported FPS and bottleneck message. Accumulate frame deltas during the sample and report their average, or rename the metric to indicate that it is the last-frame duration.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fixThere was a problem hiding this comment.
Actionable comments posted: 15
🧹 Nitpick comments (3)
frontend/src/utils/networkInterceptor.ts (1)
3-13: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winThe
fetchpatch is permanent and cannot be removed.
initNetworkInterceptorreplaceswindow.fetchand provides no way to restoreoriginalFetch. Two consequences follow.First, the wrapper stays active for the whole page lifetime even after the overlay unmounts. Second, if Vite hot-replaces this module during development,
isInterceptorInitializedresets tofalseand the already-wrappedfetchis wrapped again. Requests are then counted twice.Return a teardown function and call it from the overlay effect cleanup.
♻️ Proposed fix: return a teardown function
-export function initNetworkInterceptor(): void { - if (isInterceptorInitialized || typeof window === 'undefined') return; +export function initNetworkInterceptor(): () => void { + if (isInterceptorInitialized || typeof window === 'undefined') return () => {}; isInterceptorInitialized = true; const originalFetch = window.fetch;}; + + return () => { + window.fetch = originalFetch; + isInterceptorInitialized = false; + }; }Also applies to: 66-69
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/utils/networkInterceptor.ts` around lines 3 - 13, Update initNetworkInterceptor to return a teardown function that restores the original window.fetch and resets initialization state; ensure repeated initialization does not layer wrappers, including after hot replacement. Update the overlay’s effect cleanup to invoke this teardown when the overlay unmounts.frontend/src/store/performanceStore.ts (2)
205-209: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value
avgResponseTimeMsbecomes insensitive to recent latency.The average is cumulative over
totalRequestsfor the whole session. After a few hundred requests, a new slow call barely moves the value. A live diagnostics tool benefits more from a recent-window average. Consider averaging overrecentCallsinstead, or keeping both values.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/store/performanceStore.ts` around lines 205 - 209, Update the performance metrics calculation in the state update containing newAvgResponse so avgResponseTimeMs reflects a recent-call window rather than the cumulative session total. Reuse recentCalls to calculate the windowed average, ensuring the current call is included and the window remains bounded; preserve cumulative totals separately if they are still needed for overall statistics.
84-84: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
MAX_HISTORY_LENGTHis dead code; the history length is pinned to 20.
updateFrameMetricsusesstate.fpsHistory.slice(1), so the history keeps the initial array length of 20.MAX_HISTORY_LENGTHat Line 84 is never read. Either seed the history from the constant, or bound the array with the constant.♻️ Proposed fix to use the constant
- fpsHistory: Array(20).fill(60), + fpsHistory: Array(MAX_HISTORY_LENGTH).fill(60),- const newFpsHistory = [...state.fpsHistory.slice(1), fps]; + const newFpsHistory = [...state.fpsHistory, fps].slice(-MAX_HISTORY_LENGTH);Also applies to: 98-98, 128-130
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/store/performanceStore.ts` at line 84, Make MAX_HISTORY_LENGTH authoritative in the performance history flow: update the initial fpsHistory seed and the updateFrameMetrics trimming logic to use this constant, ensuring the array remains capped at 30 entries and eliminating the dead constant.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@frontend/src/components/layouts/MainLayout.tsx`:
- Around line 413-414: The developer performance tooling must be
development-only and collect telemetry only while enabled. In
frontend/src/components/layouts/MainLayout.tsx lines 413-414 and the toggle at
line 244, use import.meta.env.DEV gates and React.lazy for both
PerformanceOverlay and PerformanceToggleButton imports; in
frontend/src/components/PerformanceOverlay/PerformanceOverlay.tsx lines 42-44,
initialize the network interceptor only when isEnabled and restore fetch during
cleanup; in lines 47-104, depend on isEnabled in the frame and memory effects
and return early when disabled; in frontend/src/hooks/useCesiumPerformance.ts
lines 12-13, subscribe to isEnabled and attach or remove Cesium
preRender/postRender listeners according to its value.
In `@frontend/src/components/PerformanceOverlay/PerformanceOverlay.tsx`:
- Around line 7-35: Update
frontend/src/components/PerformanceOverlay/PerformanceOverlay.tsx lines 7-35 by
replacing the whole-store usePerformanceStore() destructure with individual
field selectors, and move metric subscriptions into a child component mounted
only when isEnabled is true; the overlay visibility controls may remain
subscribed in the parent. Update
frontend/src/components/PerformanceOverlay/PerformanceToggleButton.tsx line 6 to
select isEnabled, toggleOverlay, fps, and the alert count individually, avoiding
a whole-store subscription.
- Around line 96-98: Update the unsupported-memory branch in the performance
metrics interval to detect `perfObj.memory` being unavailable once, call
`updateMemoryMetrics({ isSupported: false })` only once, and stop or clear the
recurring interval afterward. Preserve the existing supported-memory update
flow.
- Around line 436-438: Update the status-color condition in the
PerformanceOverlay render around call.status so all HTTP 2xx statuses use the
success color, while non-2xx statuses—including 3xx responses—retain the error
color. Keep the existing displayed status text and ERR fallback unchanged.
- Around line 108-115: Update handleKeyDown so the overlay toggles only for an
unmodified Shift+P press: reject events with ctrlKey, metaKey, altKey, or other
unintended modifiers before preventDefault. Extend the editable-target guard
beyond INPUT, TEXTAREA, and SELECT to also ignore contentEditable elements,
while preserving the existing toggleOverlay behavior for valid shortcuts.
- Around line 249-266: Update the dock-position menu around the
PerformanceOverlay component’s position trigger and setPosition handler to use
open state: toggle the menu from the trigger’s onClick, keep it visible while
open as well as on hover, and ensure the trigger exposes its expanded state.
Preserve the existing position options and selection behavior.
- Around line 136-139: Update the drag coordinate clamping in the pointer-move
handler around setCustomCoords to derive the panel’s current width and height
from containerRef instead of fixed 300 and 200 pixel values. Use the measured
dimensions for the right and bottom bounds so both expanded and minimized states
remain fully within the viewport while preserving the existing minimum margins.
- Around line 53-67: Update measureFrame to report uncapped measured FPS,
compute avgFrameTime as (now - lastFpsUpdate) / frameCount, and accumulate
dropped frames throughout each sampling window rather than forwarding only the
boundary frame’s boolean. Change updateFrameMetrics and its performanceStore
implementation to accept and add the dropped-frame count, resetting the
accumulator after each update.
In `@frontend/src/components/PerformanceOverlay/PerformanceToggleButton.tsx`:
- Line 7: Update the warningCount calculation in PerformanceToggleButton to use
bottlenecks.length directly if all typed alert severities should be counted;
otherwise change the predicate to count only CRITICAL alerts, matching the
intended UI behavior and avoiding unnecessary array allocation.
In `@frontend/src/hooks/useCesiumPerformance.ts`:
- Around line 21-40: Update handlePostRender to throttle metric sampling to a
fixed interval rather than processing and writing metrics on every frame. Cache
satelliteCount and recompute it only when viewer.entities.values.length changes,
preserving the existing catalogData detection logic; only call
updateCesiumMetrics when the sampling interval elapses.
- Around line 45-51: Update the cleanup returned by useCesiumPerformance to
always invoke the removePre and removePost callbacks, removing the
scene.isDestroyed() guard around them. Preserve the existing listener
registration and ensure cleanup releases the handlers and viewer reference even
when the scene has already been destroyed.
In `@frontend/src/store/performanceStore.ts`:
- Line 136: Update all BottleneckAlert id assignments in updateCesiumMetrics and
addNetworkCall to guarantee uniqueness beyond Date.now(), reusing the existing
NetworkCallMetric random-suffix pattern or a module-level monotonic counter.
Apply the change to each alert id literal, including the assignments near the
referenced locations, while preserving their existing id prefixes.
- Around line 154-174: In performanceStore.ts, update both updateCesiumMetrics
(lines 154-174) and updateMemoryMetrics (lines 176-196) to prevent sustained
conditions from appending alerts on every call: apply the same time-based
cooldown used by updateFrameMetrics before adding SLOW_SCENE_UPDATE or
HIGH_MEMORY alerts, or skip insertion when the newest bottlenecks entry has the
same type. Preserve metric updates and the existing bounded alert buffer
behavior.
In `@frontend/src/utils/networkInterceptor.ts`:
- Around line 20-30: Update the URL capture logic in initNetworkInterceptor to
remove query strings before assigning request URLs to url, covering string, URL,
and Request inputs. Preserve the URL origin and path while ensuring recentCalls
never receives query parameters.
- Around line 32-34: Update the method-detection logic in the network
interceptor to fall back to the method on args[0] when args[1] is absent and
args[0] is a Request object. Preserve the existing args[1].method handling for
fetch(url, options), and ensure Request-based calls such as POST are recorded
with their actual method instead of defaulting to GET.
---
Nitpick comments:
In `@frontend/src/store/performanceStore.ts`:
- Around line 205-209: Update the performance metrics calculation in the state
update containing newAvgResponse so avgResponseTimeMs reflects a recent-call
window rather than the cumulative session total. Reuse recentCalls to calculate
the windowed average, ensuring the current call is included and the window
remains bounded; preserve cumulative totals separately if they are still needed
for overall statistics.
- Line 84: Make MAX_HISTORY_LENGTH authoritative in the performance history
flow: update the initial fpsHistory seed and the updateFrameMetrics trimming
logic to use this constant, ensuring the array remains capped at 30 entries and
eliminating the dead constant.
In `@frontend/src/utils/networkInterceptor.ts`:
- Around line 3-13: Update initNetworkInterceptor to return a teardown function
that restores the original window.fetch and resets initialization state; ensure
repeated initialization does not layer wrappers, including after hot
replacement. Update the overlay’s effect cleanup to invoke this teardown when
the overlay unmounts.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: ee88bf55-a56c-44f8-8918-90392f44521b
⛔ Files ignored due to path filters (45)
backend/__pycache__/debug_log.cpython-314.pycis excluded by!**/*.pycbackend/ai/__pycache__/__init__.cpython-314.pycis excluded by!**/*.pycbackend/ai/__pycache__/openai_service.cpython-314.pycis excluded by!**/*.pycbackend/ai/__pycache__/workflow.cpython-314.pycis excluded by!**/*.pycbackend/api/__pycache__/__init__.cpython-314.pycis excluded by!**/*.pycbackend/api/__pycache__/router.cpython-314.pycis excluded by!**/*.pycbackend/api/v1/__pycache__/__init__.cpython-314.pycis excluded by!**/*.pycbackend/api/v1/endpoints/__pycache__/__init__.cpython-314.pycis excluded by!**/*.pycbackend/api/v1/endpoints/__pycache__/agents.cpython-314.pycis excluded by!**/*.pycbackend/api/v1/endpoints/__pycache__/auth.cpython-314.pycis excluded by!**/*.pycbackend/api/v1/endpoints/__pycache__/catalog.cpython-314.pycis excluded by!**/*.pycbackend/api/v1/endpoints/__pycache__/collisions.cpython-314.pycis excluded by!**/*.pycbackend/api/v1/endpoints/__pycache__/dashboard.cpython-314.pycis excluded by!**/*.pycbackend/api/v1/endpoints/__pycache__/satellites.cpython-314.pycis excluded by!**/*.pycbackend/api/v1/endpoints/__pycache__/weather.cpython-314.pycis excluded by!**/*.pycbackend/app/__pycache__/__init__.cpython-314.pycis excluded by!**/*.pycbackend/app/__pycache__/main.cpython-314.pycis excluded by!**/*.pycbackend/app/core/__pycache__/__init__.cpython-314.pycis excluded by!**/*.pycbackend/app/core/__pycache__/config.cpython-314.pycis excluded by!**/*.pycbackend/app/core/__pycache__/error_handlers.cpython-314.pycis excluded by!**/*.pycbackend/app/core/__pycache__/exceptions.cpython-314.pycis excluded by!**/*.pycbackend/app/core/__pycache__/scheduler.cpython-314.pycis excluded by!**/*.pycbackend/app/core/__pycache__/security.cpython-314.pycis excluded by!**/*.pycbackend/database/__pycache__/__init__.cpython-314.pycis excluded by!**/*.pycbackend/database/__pycache__/session.cpython-314.pycis excluded by!**/*.pycbackend/models/__pycache__/__init__.cpython-314.pycis excluded by!**/*.pycbackend/models/__pycache__/db_models.cpython-314.pycis excluded by!**/*.pycbackend/orbital/__pycache__/__init__.cpython-314.pycis excluded by!**/*.pycbackend/orbital/__pycache__/collision_engine.cpython-314.pycis excluded by!**/*.pycbackend/orbital/__pycache__/orbit_engine.cpython-314.pycis excluded by!**/*.pycbackend/orbital/__pycache__/spacetrack.cpython-314.pycis excluded by!**/*.pycbackend/orbital/providers/__pycache__/__init__.cpython-314.pycis excluded by!**/*.pycbackend/orbital/providers/__pycache__/base.cpython-314.pycis excluded by!**/*.pycbackend/orbital/providers/__pycache__/cache.cpython-314.pycis excluded by!**/*.pycbackend/orbital/providers/__pycache__/celestrak.cpython-314.pycis excluded by!**/*.pycbackend/orbital/providers/__pycache__/chain.cpython-314.pycis excluded by!**/*.pycbackend/orbital/providers/__pycache__/spacetrack.cpython-314.pycis excluded by!**/*.pycbackend/schemas/__pycache__/__init__.cpython-314.pycis excluded by!**/*.pycbackend/schemas/__pycache__/api_schemas.cpython-314.pycis excluded by!**/*.pycbackend/services/__pycache__/__init__.cpython-314.pycis excluded by!**/*.pycbackend/services/__pycache__/risk_service.cpython-314.pycis excluded by!**/*.pycbackend/services/__pycache__/simulation_service.cpython-314.pycis excluded by!**/*.pycbackend/services/__pycache__/weather_service.cpython-314.pycis excluded by!**/*.pycbackend/websocket/__pycache__/__init__.cpython-314.pycis excluded by!**/*.pycbackend/websocket/__pycache__/manager.cpython-314.pycis excluded by!**/*.pyc
📒 Files selected for processing (9)
frontend/src/components/EarthTwin.tsxfrontend/src/components/PerformanceOverlay/PerformanceOverlay.tsxfrontend/src/components/PerformanceOverlay/PerformanceToggleButton.tsxfrontend/src/components/layouts/MainLayout.tsxfrontend/src/hooks/useCesiumPerformance.tsfrontend/src/store/performanceStore.tsfrontend/src/utils/networkInterceptor.tsfrontend/tsconfig.app.jsonfrontend/vite.config.ts
| const { | ||
| isEnabled, | ||
| isMinimized, | ||
| position, | ||
| customCoords, | ||
| activeTab, | ||
| fps, | ||
| frameTimeMs, | ||
| droppedFrames, | ||
| fpsHistory, | ||
| renderedSatellites, | ||
| activeEntities, | ||
| cesiumSceneUpdateTimeMs, | ||
| memory, | ||
| activeRequests, | ||
| avgResponseTimeMs, | ||
| totalRequests, | ||
| slowRequestsCount, | ||
| recentCalls, | ||
| bottlenecks, | ||
| toggleOverlay, | ||
| toggleMinimized, | ||
| setPosition, | ||
| setCustomCoords, | ||
| setActiveTab, | ||
| updateFrameMetrics, | ||
| updateMemoryMetrics, | ||
| clearBottlenecks, | ||
| } = usePerformanceStore(); |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win
Both components subscribe to the entire performance store and re-render on every metric write.
Each component calls usePerformanceStore() with no selector. In Zustand v5 that subscribes to the whole state object, and every set() produces a new object identity. useCesiumPerformance writes once per rendered Cesium frame, so both components re-render at the frame rate.
frontend/src/components/PerformanceOverlay/PerformanceOverlay.tsx#L7-L35: replace the single destructure with one selector per field. Move the metric selectors into a child component that mounts only whenisEnabledistrue, so no metric subscription exists while the overlay is closed.frontend/src/components/PerformanceOverlay/PerformanceToggleButton.tsx#L6-L6: selectisEnabled,toggleOverlay,fps, and the alert count individually. This component stays mounted in theMainLayoutheader at all times, so the whole-store subscription re-renders the header on every frame.
📍 Affects 2 files
frontend/src/components/PerformanceOverlay/PerformanceOverlay.tsx#L7-L35(this comment)frontend/src/components/PerformanceOverlay/PerformanceToggleButton.tsx#L6-L6
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/components/PerformanceOverlay/PerformanceOverlay.tsx` around
lines 7 - 35, Update
frontend/src/components/PerformanceOverlay/PerformanceOverlay.tsx lines 7-35 by
replacing the whole-store usePerformanceStore() destructure with individual
field selectors, and move metric subscriptions into a child component mounted
only when isEnabled is true; the overlay visibility controls may remain
subscribed in the parent. Update
frontend/src/components/PerformanceOverlay/PerformanceToggleButton.tsx line 6 to
select isEnabled, toggleOverlay, fps, and the alert count individually, avoiding
a whole-store subscription.
| const measureFrame = (now: number) => { | ||
| const delta = now - lastTime; | ||
| lastTime = now; | ||
| frameCount++; | ||
|
|
||
| const isDropped = delta > 33.3; // Dropped frame threshold (> 30fps baseline) | ||
|
|
||
| if (now - lastFpsUpdate >= 500) { // Update store twice per second | ||
| const currentFps = Math.min(60, Math.round((frameCount * 1000) / (now - lastFpsUpdate))); | ||
| const avgFrameTime = delta; | ||
| updateFrameMetrics(currentFps, avgFrameTime, isDropped); | ||
|
|
||
| frameCount = 0; | ||
| lastFpsUpdate = now; | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Three measurement defects in the frame sampler: the 60 FPS clamp, the mislabeled average, and the dropped-frame undercount.
Math.min(60, ...) at Line 61 caps the reported rate. On a 120 Hz or 144 Hz display the overlay always reports 60 and hides the real frame rate. A performance tool must report the measured value.
avgFrameTime at Line 62 is assigned delta, which is the duration of the single frame that crossed the 500 ms boundary. It is not an average. The true average is available as (now - lastFpsUpdate) / frameCount.
isDropped at Line 58 is computed on every frame but only forwarded at the sampling boundary. droppedFrames therefore increments at most twice per second and ignores every dropped frame between samples. Accumulate the count across the window instead.
🐛 Proposed fix
let frameCount = 0;
let lastFpsUpdate = performance.now();
+ let droppedInWindow = 0;
const measureFrame = (now: number) => {
const delta = now - lastTime;
lastTime = now;
frameCount++;
- const isDropped = delta > 33.3; // Dropped frame threshold (> 30fps baseline)
+ if (delta > 33.3) droppedInWindow++; // Dropped frame threshold (> 30fps baseline)
if (now - lastFpsUpdate >= 500) { // Update store twice per second
- const currentFps = Math.min(60, Math.round((frameCount * 1000) / (now - lastFpsUpdate)));
- const avgFrameTime = delta;
- updateFrameMetrics(currentFps, avgFrameTime, isDropped);
-
+ const elapsed = now - lastFpsUpdate;
+ const currentFps = Math.round((frameCount * 1000) / elapsed);
+ const avgFrameTime = elapsed / frameCount;
+ updateFrameMetrics(currentFps, avgFrameTime, droppedInWindow);
+
+ droppedInWindow = 0;
frameCount = 0;
lastFpsUpdate = now;
}This changes the third argument of updateFrameMetrics from a boolean to a count. Update the signature in performanceStore.ts at Line 74 and the accumulation at Line 131 accordingly:
- updateFrameMetrics: (fps: number, frameTimeMs: number, isDroppedFrame: boolean) => void;
+ updateFrameMetrics: (fps: number, frameTimeMs: number, droppedFrameCount: number) => void;- const newDropped = isDroppedFrame ? state.droppedFrames + 1 : state.droppedFrames;
+ const newDropped = state.droppedFrames + droppedFrameCount;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/components/PerformanceOverlay/PerformanceOverlay.tsx` around
lines 53 - 67, Update measureFrame to report uncapped measured FPS, compute
avgFrameTime as (now - lastFpsUpdate) / frameCount, and accumulate dropped
frames throughout each sampling window rather than forwarding only the boundary
frame’s boolean. Change updateFrameMetrics and its performanceStore
implementation to accept and add the dropped-frame count, resetting the
accumulator after each update.
| } else { | ||
| updateMemoryMetrics({ isSupported: false }); | ||
| } |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win
Avoid the repeated store write when performance.memory is unavailable.
On Firefox and Safari, perfObj.memory is always undefined. This branch then calls updateMemoryMetrics({ isSupported: false }) every 2 seconds for the lifetime of the page. Each call produces a new state object and re-renders every store subscriber, including PerformanceToggleButton, which stays mounted at all times.
Detect the lack of support once and stop the interval.
♻️ Proposed fix
useEffect(() => {
+ const perfObj = window.performance as unknown as {
+ memory?: { usedJSHeapSize: number; totalJSHeapSize: number; jsHeapSizeLimit: number };
+ };
+
+ if (!perfObj.memory) {
+ updateMemoryMetrics({ isSupported: false });
+ return;
+ }
+
const updateMem = () => {
- const perfObj = window.performance as unknown as {
- memory?: { usedJSHeapSize: number; totalJSHeapSize: number; jsHeapSizeLimit: number };
- };
-
- if (perfObj.memory) {
- ...
- } else {
- updateMemoryMetrics({ isSupported: false });
- }
+ const m = perfObj.memory!;
+ // ...existing calculation
};🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/components/PerformanceOverlay/PerformanceOverlay.tsx` around
lines 96 - 98, Update the unsupported-memory branch in the performance metrics
interval to detect `perfObj.memory` being unavailable once, call
`updateMemoryMetrics({ isSupported: false })` only once, and stop or clear the
recurring interval afterward. Preserve the existing supported-memory update
flow.
| const handleKeyDown = (e: KeyboardEvent) => { | ||
| if (e.shiftKey && (e.key === 'P' || e.key === 'p')) { | ||
| if (['INPUT', 'TEXTAREA', 'SELECT'].includes((e.target as HTMLElement)?.tagName)) { | ||
| return; | ||
| } | ||
| e.preventDefault(); | ||
| toggleOverlay(); | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Shift + P also fires with additional modifiers and inside editable elements.
The condition checks only e.shiftKey. Ctrl + Shift + P and Cmd + Shift + P therefore also toggle the overlay, and e.preventDefault() runs. Those combinations open the command menu in Chrome DevTools and in VS Code, so the collision affects the intended developer audience.
The guard at Line 110 also misses contentEditable regions.
🐛 Proposed fix
const handleKeyDown = (e: KeyboardEvent) => {
- if (e.shiftKey && (e.key === 'P' || e.key === 'p')) {
- if (['INPUT', 'TEXTAREA', 'SELECT'].includes((e.target as HTMLElement)?.tagName)) {
+ if (e.shiftKey && !e.ctrlKey && !e.metaKey && !e.altKey && (e.key === 'P' || e.key === 'p')) {
+ const target = e.target as HTMLElement | null;
+ if (
+ ['INPUT', 'TEXTAREA', 'SELECT'].includes(target?.tagName ?? '') ||
+ target?.isContentEditable
+ ) {
return;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const handleKeyDown = (e: KeyboardEvent) => { | |
| if (e.shiftKey && (e.key === 'P' || e.key === 'p')) { | |
| if (['INPUT', 'TEXTAREA', 'SELECT'].includes((e.target as HTMLElement)?.tagName)) { | |
| return; | |
| } | |
| e.preventDefault(); | |
| toggleOverlay(); | |
| } | |
| const handleKeyDown = (e: KeyboardEvent) => { | |
| if (e.shiftKey && !e.ctrlKey && !e.metaKey && !e.altKey && (e.key === 'P' || e.key === 'p')) { | |
| const target = e.target as HTMLElement | null; | |
| if ( | |
| ['INPUT', 'TEXTAREA', 'SELECT'].includes(target?.tagName ?? '') || | |
| target?.isContentEditable | |
| ) { | |
| return; | |
| } | |
| e.preventDefault(); | |
| toggleOverlay(); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/components/PerformanceOverlay/PerformanceOverlay.tsx` around
lines 108 - 115, Update handleKeyDown so the overlay toggles only for an
unmodified Shift+P press: reject events with ctrlKey, metaKey, altKey, or other
unintended modifiers before preventDefault. Extend the editable-target guard
beyond INPUT, TEXTAREA, and SELECT to also ignore contentEditable elements,
while preserving the existing toggleOverlay behavior for valid shortcuts.
| return () => { | ||
| if (!scene.isDestroyed()) { | ||
| removePre(); | ||
| removePost(); | ||
| } | ||
| }; | ||
| }, [viewer]); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Inspect Cesium Event/Scene destroy semantics in the installed package.
set -euo pipefail
fd -t f 'Event.js' node_modules/cesium/Source/Core 2>/dev/null | head -5
fd -t f 'package.json' node_modules/cesium --max-depth 2 2>/dev/null | head -1 | xargs -r jq '.version'
# Show how other hooks in this repo detach Cesium scene event listeners.
rg -n -C 5 'preRender|postRender|removeEventListener' --glob 'frontend/src/**/*.ts*'Repository: 7-Blocks/Kepler
Length of output: 153
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Tracked ts files matching hook/EarthTwin/overlay names:"
git ls-files | rg '(^|/)useCesiumPerformance\.ts$|EarthTwin\.tsx$|PerformanceOverlay\.tsx$' || true
echo
echo "useCesiumPerformance outline/content:"
if [ -f frontend/src/hooks/useCesiumPerformance.ts ]; then
wc -l frontend/src/hooks/useCesiumPerformance.ts
cat -n frontend/src/hooks/useCesiumPerformance.ts
fi
echo
echo "EarthTwin relevant references:"
if [ -f frontend/src/components/EarthTwin.tsx ]; then
rg -n -C 6 'useEffect|createViewer|viewer|setViewerInstance|destroy|scene' frontend/src/components/EarthTwin.tsx
else
rg -n -C 6 'EarthTwin|viewer|setViewerInstance|destroy|scene' -g '*.tsx' -g '*.ts'
fi
echo
echo "PerformanceOverlay relevant references:"
if [ -f frontend/src/components/PerformanceOverlay.tsx ]; then
wc -l frontend/src/components/PerformanceOverlay.tsx
rg -n -C 6 'usePerformanceStore|isEnabled|requestAnimationFrame|setInterval|preRender|postRender' frontend/src/components/PerformanceOverlay.tsx
fi
echo
echo "Cesium package presence/version:"
if [ -f frontend/package.json ]; then
jq -r '.dependencies.["cesium"], .devDependencies.["cesium"]' frontend/package.json
fi
if [ -f package.json ]; then
jq -r '.dependencies.["cesium"]' package.json
fiRepository: 7-Blocks/Kepler
Length of output: 18906
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Package files:"
git ls-files '*package.json' '*package-lock.json' '*yarn.lock' '*pnpm-lock.yaml' | sed -n '1,80p'
echo
echo "Frontend package version refs:"
if [ -f frontend/package.json ]; then
jq -r '
.dependencies.cesium // _,
.devDependencies.cesium // _,
.peerDependencies.cesium // _
' frontend/package.json
fi
echo
echo "Locate installed Cesium Event/Scene files if any:"
fd -t f 'Event|Scene' node_modules/cesium 2>/dev/null | sed -n '1,80p' || true
echo
echo "Search repository for scene destroy/event cleanup patterns:"
rg -n -C 3 'isDestroyed|addEventListener\(|removeEventListener\(|preRender|postRender|scene.*destroy|renderLoop' frontend/src --glob '*.ts' --glob '*.tsx'Repository: 7-Blocks/Kepler
Length of output: 685
🌐 Web query:
Cesium Viewer destroy Scene preRender postRender event listeners removed
💡 Result:
When you call viewer.destroy in CesiumJS, it performs a comprehensive cleanup of the viewer, including its associated widgets and the scene [1][2]. While viewer.destroy handles internal components, it does not automatically remove custom event listeners that you have manually attached to the scene's lifecycle events (such as preRender or postRender) or other external objects [3]. To ensure there are no memory leaks or unexpected behavior after destroying the viewer, you should manually remove any event listeners you added. Key points for managing these events: 1. Lifecycle Events (preRender/postRender): If you have attached functions to scene events, you must remove them explicitly before or during the cleanup process. The addEventListener method on these events returns a remove function that you should store and call [3]. Example: const removePreRender = viewer.scene.preRender.addEventListener(myCallback); // Later, when cleaning up: removePreRender; viewer.destroy; 2. ScreenSpaceEventHandler: If you created a custom ScreenSpaceEventHandler, you must call its.destroy method manually. The viewer's own internal screenSpaceEventHandler is managed by viewer.destroy, though updates in recent versions have improved robustness in checking its state [4][5][6]. 3. Viewer Cleanup: Calling viewer.destroy is the correct and necessary step to release WebGL resources and clear the scene, but it assumes responsibility only for the components the viewer itself instantiated [1][2]. Any custom references or listeners created outside of the viewer's direct scope remain your responsibility to clean up [7]. If you are experiencing memory issues, ensure that you are not holding onto references to the viewer or its objects in closures, as these will prevent the garbage collector from reclaiming that memory even after destroy is called [7].
Citations:
- 1: https://cesium.com/learn/cesiumjs/ref-doc/Viewer.html
- 2: https://community.cesium.com/t/viewer-and-scene-destroy/16430
- 3: https://community.cesium.com/t/scene-pre-postrender-event/4994
- 4: Destroy event handler CesiumGS/cesium#11840
- 5: Viewer.destroy not checking if ScreenSpaceEventHandler is defined CesiumGS/cesium#10576
- 6: Fix issue #10576 Viewer.destroy not checking if ScreenSpaceEventHandl… CesiumGS/cesium#11509
- 7: when we use viewer.destroy after viewer.flyTo(some entity or dataSource),will cause memory leaks CesiumGS/cesium#8378
Always remove the Cesium performance listeners on cleanup.
EarthTwin destroys the viewer once and then calls setViewerInstance(null), which can make useCesiumPerformance()’s cleanup receive a destroyed scene without removing preRender/postRender handlers. Store the remove callbacks outside the scene.isDestroyed() check, so destroyed scenes do not leave the closure handlers and viewer reference registered.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/hooks/useCesiumPerformance.ts` around lines 45 - 51, Update the
cleanup returned by useCesiumPerformance to always invoke the removePre and
removePost callbacks, removing the scene.isDestroyed() guard around them.
Preserve the existing listener registration and ensure cleanup releases the
handlers and viewer reference even when the scene has already been destroyed.
| let newBottlenecks = state.bottlenecks; | ||
| if (fps < 30 && (!state.bottlenecks.length || state.bottlenecks[0]?.type !== 'LOW_FPS')) { | ||
| const alert: BottleneckAlert = { | ||
| id: `fps-${Date.now()}`, |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Alert id values can collide within the same millisecond.
Every BottleneckAlert id is derived only from Date.now(). updateCesiumMetrics runs per frame and addNetworkCall can complete several requests in the same millisecond. Two alerts then share an id. PerformanceOverlay.tsx at Line 515 uses alert.id as the React key, so React reports duplicate keys and can reuse the wrong element.
Use the same random-suffix pattern already applied to NetworkCallMetric at Line 202, or a module-level counter.
🐛 Proposed fix: monotonic id helper
+let alertSeq = 0;
+const nextAlertId = (prefix: string) => `${prefix}-${Date.now()}-${++alertSeq}`;Then replace each literal, for example:
- id: `cesium-${Date.now()}`,
+ id: nextAlertId('cesium'),Also applies to: 159-159, 183-183, 215-215, 241-241
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/store/performanceStore.ts` at line 136, Update all
BottleneckAlert id assignments in updateCesiumMetrics and addNetworkCall to
guarantee uniqueness beyond Date.now(), reusing the existing NetworkCallMetric
random-suffix pattern or a module-level monotonic counter. Apply the change to
each alert id literal, including the assignments near the referenced locations,
while preserving their existing id prefixes.
| updateCesiumMetrics: (sceneTimeMs, entityCount, satelliteCount) => | ||
| set((state) => { | ||
| let newBottlenecks = state.bottlenecks; | ||
| if (sceneTimeMs > 35) { | ||
| const alert: BottleneckAlert = { | ||
| id: `cesium-${Date.now()}`, | ||
| type: 'SLOW_SCENE_UPDATE', | ||
| severity: sceneTimeMs > 60 ? 'CRITICAL' : 'WARNING', | ||
| message: `Cesium scene update took ${sceneTimeMs.toFixed(1)}ms (${entityCount} active entities)`, | ||
| timestamp: Date.now(), | ||
| }; | ||
| newBottlenecks = [alert, ...state.bottlenecks.slice(0, MAX_BOTTLENECK_LOGS - 1)]; | ||
| } | ||
|
|
||
| return { | ||
| cesiumSceneUpdateTimeMs: sceneTimeMs, | ||
| activeEntities: entityCount, | ||
| renderedSatellites: satelliteCount, | ||
| bottlenecks: newBottlenecks, | ||
| }; | ||
| }), |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win
Two alert paths have no de-duplication, so a sustained condition floods the alert buffer.
updateFrameMetrics guards against repeating a LOW_FPS alert by inspecting state.bottlenecks[0]. The scene and memory paths have no equivalent guard. A condition that persists therefore appends a new alert on every call, evicts all other alert types from the 15-slot buffer, and notifies every store subscriber.
frontend/src/store/performanceStore.ts#L154-L174:updateCesiumMetricsruns once per rendered frame fromuseCesiumPerformance. Add a time-based cooldown before appending aSLOW_SCENE_UPDATEalert.frontend/src/store/performanceStore.ts#L176-L196:updateMemoryMetricsruns every 2 seconds from the overlay. Add the same cooldown before appending aHIGH_MEMORYalert, or skip the alert when the newest entry is alreadyHIGH_MEMORY.
📍 Affects 1 file
frontend/src/store/performanceStore.ts#L154-L174(this comment)frontend/src/store/performanceStore.ts#L176-L196
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/store/performanceStore.ts` around lines 154 - 174, In
performanceStore.ts, update both updateCesiumMetrics (lines 154-174) and
updateMemoryMetrics (lines 176-196) to prevent sustained conditions from
appending alerts on every call: apply the same time-based cooldown used by
updateFrameMetrics before adding SLOW_SCENE_UPDATE or HIGH_MEMORY alerts, or
skip insertion when the newest bottlenecks entry has the same type. Preserve
metric updates and the existing bounded alert buffer behavior.
| let url = 'unknown'; | ||
| let method = 'GET'; | ||
|
|
||
| try { | ||
| if (typeof args[0] === 'string') { | ||
| url = args[0]; | ||
| } else if (args[0] instanceof URL) { | ||
| url = args[0].toString(); | ||
| } else if (args[0] && typeof args[0] === 'object' && 'url' in args[0]) { | ||
| url = (args[0] as Request).url; | ||
| } |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Full request URLs including query strings are retained and rendered.
url keeps the complete request URL with its query string. It is stored in recentCalls in the performance store and rendered in PerformanceOverlay.tsx at Line 432, which strips only the origin. Query strings in this application can carry access tokens, catalog identifiers, or search terms.
performanceStore.ts at Line 218 already strips the query for alert messages, so the intent to avoid leaking query data exists. Apply the same treatment at the capture point.
MainLayout.tsx mounts PerformanceOverlay, and PerformanceOverlay calls initNetworkInterceptor unconditionally, so this applies to every user, not only developers.
🛡️ Proposed fix: strip the query string at capture
let url = 'unknown';
let method = 'GET';
try {
+ let rawUrl = '';
if (typeof args[0] === 'string') {
- url = args[0];
+ rawUrl = args[0];
} else if (args[0] instanceof URL) {
- url = args[0].toString();
+ rawUrl = args[0].toString();
} else if (args[0] && typeof args[0] === 'object' && 'url' in args[0]) {
- url = (args[0] as Request).url;
+ rawUrl = (args[0] as Request).url;
}
+ // Drop query strings and fragments; they can carry tokens or PII.
+ url = rawUrl.split(/[?#]/)[0] || 'unknown';Also applies to: 43-50
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/utils/networkInterceptor.ts` around lines 20 - 30, Update the
URL capture logic in initNetworkInterceptor to remove query strings before
assigning request URLs to url, covering string, URL, and Request inputs.
Preserve the URL origin and path while ensuring recentCalls never receives query
parameters.
|
Hello @Pranitrane, Please attach screenshots of the updates/changes and also add related issue no. along with your PR. Thanks! |


User description
Summary
This PR implements a real-time developer performance overlay for the KEPLER platform. The overlay monitors application rendering frame rates, 3D Cesium scene update timings, browser memory usage, and API network request latencies in real time. It assists developers in quickly identifying performance bottlenecks and optimizing satellite visualizations during development and testing.
Key Changes
performanceStore.ts): Built a Zustand store to manage real-time FPS, frame update times, Cesium scene update metrics, JS Heap memory telemetry, API call logs, and performance bottleneck alerts.networkInterceptor.ts): Non-intrusively wrapswindow.fetchto track active network request counters, average API latency, status codes, and flags slow endpoints (> 500ms).useCesiumPerformance.ts): Hooks intoCesium.Viewer.scene.preRenderandpostRenderevents to track scene update durations, total active entities, and rendered satellite counts onEarthTwin.tsx.PerformanceOverlay.tsx):top-right,top-left,bottom-right,bottom-left).Shift + Ptoggles overlay visibility.PerformanceToggleButton.tsx): Compact status button mounted in the header ofMainLayout.tsxshowing live FPS and active bottleneck warnings.Related Issue
Fixes #168
Type of Change
Screenshots / Screen Recordings
1. Expanded Diagnostics Panel
2. Minimized Compact Pill Mode
Testing Performed
Test Verification Summary:
npm run build.Shift + Pkeyboard shortcut toggle.Breaking Changes
None. This feature is strictly additive and non-breaking.
Checklist
ECSoC26 Submission
ECSoC26-L1– BeginnerECSoC26-L2– IntermediateECSoC26-L3– AdvancedCodeAnt-AI Description
Add a real-time performance overlay for monitoring the 3D globe and application activity
What Changed
Shift + Pvisibility shortcutImpact
✅ Faster identification of slow globe rendering✅ Clearer API and memory diagnostics✅ Immediate visibility into performance bottlenecks💡 Usage Guide
Checking Your Pull Request
Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.
Talking to CodeAnt AI
Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:
This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.
Example
Preserve Org Learnings with CodeAnt
You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:
This helps CodeAnt AI learn and adapt to your team's coding style and standards.
Example
Retrigger review
Ask CodeAnt AI to review the PR again, by typing:
Check Your Repository Health
To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.
Summary by CodeRabbit