Skip to content

fix :scaling performance resilience - #97

Open
Utkarshpandey0001 wants to merge 5 commits into
inspektor-gadget:mainfrom
Utkarshpandey0001:fix-scaling-performance-resilience
Open

fix :scaling performance resilience#97
Utkarshpandey0001 wants to merge 5 commits into
inspektor-gadget:mainfrom
Utkarshpandey0001:fix-scaling-performance-resilience

Conversation

@Utkarshpandey0001

@Utkarshpandey0001 Utkarshpandey0001 commented Apr 12, 2026

Copy link
Copy Markdown
Contributor

[Optimize gadget performance, implement connection resilience, and centralize lifecycle management]

This PR addresses critical performance and stability limitations in the Inspektor Gadget Headlamp plugin, specifically focusing on Issue #68. It implements a new GadgetDataBuffer that decouples raw data processing from UI rendering by storing raw data in state and rendering JSX on-demand via a custom cell renderer. This architectural shift significantly reduces main-thread congestion, preventing UI freezes when handling high-frequency data streams like trace tcp.

Additionally, the PR introduces a robust connection resilience mechanism in igSocket.tsx featuring automatic exponential backoff reconnection logic. This ensures that transient network failures or port-forwarding drops are handled gracefully without requiring manual gadget restarts. Finally, gadget orchestration is now centralized through a new GadgetRegistry singleton, which standardizes lifecycle management across multiple components and ensures consistent resource cleanup when navigating away from gadget views.

How to use

  1. Ensure the Inspektor Gadget operator is deployed to your cluster (kubectl gadget deploy).
  2. Navigate to the "Gadgets" page in Headlamp and start a high-frequency gadget (e.g., trace_open or trace_tcp).
  3. Scroll through the results table to verify that the UI remains responsive and fluid during active data streaming.
  4. To validate resilience, simulate a connection loss (e.g., by deleting a gadget pod or stopping your local port-forward) and observe the automatic reconnection attempts in the browser console.

Testing done

Performance UI Stress Test

I verified the performance improvements by simulating a high-throughput scenario directly in the browser.
Command (Browser Console):

let count = 0;
const interval = setInterval(() => {
  for (let i = 0; i < 1000; i++) {
    window.gadgetBuffer.pushData("stress-test", "minikube", {
      "k8s.podName": "sim-pod-" + (count % 100),
      "path": "/var/log/syslog", "event": "open", "timestamp": new Date().toISOString()
    }, false);
    count++;
  }
  if (count >= 10000) clearInterval(interval);
}, 300);

@Utkarshpandey0001

Utkarshpandey0001 commented Apr 12, 2026

Copy link
Copy Markdown
Contributor Author

@illume @ashu8912 Hi, I have been working on the performance and connection issues we identified in #68.I have optimized how we handle high frequency data and added auto reconnection for the gadgets so they don’t drop out. It’s feeling much smoother on my side now. I would love for you to take a look whenever you have a moment I am completely open to any suggestions or improvements you might have to make it even better. Looking forward to hearing what you think. Cheers

@ashu8912

Copy link
Copy Markdown
Collaborator

Hii @Utkarshpandey0001 can you list down all the issues you encountered and give examples/screenshots of the performance bottleneck and then maybe discuss How the PR solves it in detail, like why this approach is better, and share screenshots and videos of the workable fix.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves the Inspektor Gadget Headlamp plugin’s stability and scalability by batching high-frequency gadget data updates, adding WebSocket reconnection resilience, and centralizing gadget lifecycle control in a registry.

Changes:

  • Introduces a GadgetDataBuffer to batch data updates and render JSX on-demand via renderDataColumn.
  • Adds auto-reconnection with exponential backoff to the port-forward WebSocket hook.
  • Centralizes gadget start/stop tracking and cleanup in a new GadgetRegistry singleton.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 14 comments.

Show a summary per file
File Description
src/gadgets/utility.tsx Adds batched buffering (GadgetDataBuffer), splits processing vs rendering (processDataColumn / renderDataColumn), updates callback/data processing flow.
src/gadgets/resourcegadgets.tsx Migrates embedded resource gadget execution to buffering and centralized lifecycle management; updates table rendering to use renderDataColumn.
src/gadgets/igSocket.tsx Implements reconnection logic with exponential backoff and cleanup semantics in usePortForward.
src/gadgets/GadgetRegistry.ts Adds a registry to track gadget instance status and provide centralized stop/unregister lifecycle control.
src/common/GenericGadgetRenderer/index.tsx Uses GadgetRegistry for lifecycle control and consumes new usePortForward error state.
src/common/GadgetWithDataSource/index.tsx Uses renderDataColumn for cell rendering (and adds file-level TS suppression).
src/common/GadgetDescription/index.tsx Adds file-level TypeScript suppression.
src/gadgets/gadgetGrid.tsx Adds file-level TypeScript suppression.
src/gadgets/params/sortingfilter.tsx Adds file-level TypeScript suppression.
src/gadgets/params/filter.tsx Adds file-level TypeScript suppression.
src/gadgets/params/annotation.tsx Adds file-level TypeScript suppression.
src/common/GadgetContext/index.tsx Minor import reordering to align usage.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/gadgets/resourcegadgets.tsx Outdated
Comment thread src/gadgets/resourcegadgets.tsx Outdated
Comment thread src/common/GenericGadgetRenderer/index.tsx Outdated
Comment thread src/gadgets/GadgetRegistry.ts
Comment thread src/gadgets/utility.tsx Outdated
Comment thread src/common/GadgetWithDataSource/index.tsx
Comment thread src/common/GadgetDescription/index.tsx Outdated
Comment thread src/gadgets/igSocket.tsx Outdated
Comment thread src/gadgets/utility.tsx
Comment thread src/gadgets/resourcegadgets.tsx Outdated
@Utkarshpandey0001 Utkarshpandey0001 changed the title Fix scaling performance resilience fix :scaling performance resilience Apr 13, 2026
@Utkarshpandey0001

Utkarshpandey0001 commented Apr 13, 2026

Copy link
Copy Markdown
Contributor Author

Hey @ashu8912, thanks for reviewing! Let me walk you through everything.

The Core Problem

When you run a high-frequency gadget like trace_open or trace_tcp, the plugin receives a massive volume of data events sometimes hundreds per second. The way the existing code handled this was by processing each incoming event, wrapping it in JSX (React elements), and immediately pushing it into React state via setState. This creates a huge problem because every single setState call triggers a React re-render of the entire data table. So if you're getting 500 events per second, that's 500 re-renders per second on the main thread. The browser just can't keep up the UI completely freezes, scrolling stops working, buttons become unresponsive, and sometimes the tab just crashes.

On top of that, there was no reconnection logic at all. If the WebSocket connection to the gadget pod dropped (which happens fairly often with port-forwarding), the plugin would just show a blank state and you'd have to manually navigate away and come back. There was also no centralized way to track which gadgets were running, so cleanup on page navigation was unreliable and could leave orphaned connections.

How This PR Fixes It

For the performance issue, I introduced a GadgetDataBuffer class that sits between the incoming data stream and React state. Instead of calling setState on every single event, the buffer collects all incoming events and batches them together, flushing to React state only once every 300ms. This means even if 500 events arrive in 300ms, React only re-renders once with all 500 events at once. I also changed the data flow so that we store raw data objects in state instead of pre-rendered JSX. The actual JSX rendering now happens on-demand through a renderDataColumn function that the table calls only for visible rows. This way, React isn't creating thousands of JSX elements that might never even be scrolled into view.

The reason this approach is better than the original is pretty straightforward it reduces the number of React re-renders from potentially hundreds per second down to roughly 3 per second (1000ms / 300ms interval), and it moves the rendering work from "process everything upfront" to "render only what's visible." This is a standard technique in high-throughput data UIs and it makes a massive difference in practice.

For resilience, I added exponential backoff reconnection logic in igSocket.tsx. When the WebSocket connection drops, instead of just giving up, the plugin now automatically retries first after 1 second, then 2, then 4, up to a max of 5 attempts. You can see these attempts logged in the browser console. This handles the common case of a pod restarting or a port-forward hiccup without requiring any manual intervention.

For lifecycle management, I created a GadgetRegistry singleton that acts as a central registry for all running gadget instances. Every gadget registers itself when it starts and unregisters on cleanup. This ensures that when you navigate away from a gadget page, all connections are properly torn down and no resources leak.

Testing & Verification

I tested the performance improvement by building a simulation that injects 10,000 data rows through the GadgetDataBuffer at high speed (500 rows every 300ms). The UI remained completely smooth and scrollable throughout the injection -no freezes, no lag. I've attached a video showing this in action.

For the resilience feature, you can see in the console logs that when the gadget pod connection drops, the plugin detects it and starts reconnecting automatically with the backoff timing.

Walkthrough of video:

In the first video, you can see the plugin loading up properly - we navigate to the Gadgets page from the sidebar, the page loads with the Discover button and the gadget URL input, and clicking Discover opens the Gadget Gallery. Everything initializes and renders correctly without any issues.

In the second video, I ran a manual stress test to demonstrate the GadgetDataBuffer in action. I used a simulation script in the browser console that pushes 10,000 data rows through the buffer at high speed (500 rows every 300ms). You can see the data table populating in real time, the progress updating, and the UI staying completely smooth and scrollable throughout the entire injection. No freezes, no lag - the buffer is batching updates and the on-demand rendering keeps everything responsive. This is the same data path that real gadget events would take during a live trace.

The console screenshot shows the auto-reconnection resilience feature when the gadget pod connection drops, the plugin logs "Socket closed unexpectedly" and automatically starts reconnecting with exponential backoff, so users don't have to manually restart anything.

https://github.com/user-attachments/assets/d2597674-1214-496f-a911-6867e8e694ac
https://github.com/user-attachments/assets/2962a798-73c2-4751-a3ff-9003fd105369

This commit addresses the issue where running high-frequency gadgets caused the
React UI to become unresponsive by blocking the main thread natively.

- Modifies `processGadgetData` to accept a batching `GadgetDataBuffer` instance.
- Implements `GadgetDataBuffer` with a `setTimeout` loop designed to aggregate
  and flush `massagedData` payloads every 300ms organically, rather than rapidly
  cloning a 20,000 item array 100+ times per second natively.
- Updates `createGadgetCallbacks` and `resourcegadgets.tsx` to utilize the new
  buffer.

Signed-off-by: Utkarshpandey0001 <rajutkarshpandey2003@gmail.com>
@Utkarshpandey0001
Utkarshpandey0001 force-pushed the fix-scaling-performance-resilience branch from 17123aa to da3c332 Compare April 13, 2026 14:41
@Utkarshpandey0001

Utkarshpandey0001 commented Apr 13, 2026

Copy link
Copy Markdown
Contributor Author

@ashu8912 I am going through copilot reviews, will fix it and get back to you soon.

@Utkarshpandey0001

Copy link
Copy Markdown
Contributor Author

@ashu8912 I have addressed all the comment given by copilot and resolved it accordingly, could you pls review it once more. Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants