Skip to main content

Network Inspection

Nitro Fetch ships with a built-in NetworkInspector that records HTTP and WebSocket activity at the JS level, and native Perfetto / Instruments tracing for zero-overhead profiling in production.

JS Network Inspector

Setup

import { NetworkInspector } from 'react-native-nitro-fetch';

// Start recording
NetworkInspector.enable();

// Optionally configure limits
NetworkInspector.enable({
maxEntries: 500, // ring-buffer size (default 500)
maxBodyCapture: 4096, // max bytes captured per body (default 4096)
});

// Stop recording
NetworkInspector.disable();

Once enabled, all fetch() calls are automatically recorded. WebSocket tracking is also automatic if react-native-nitro-websockets is installed alongside react-native-nitro-fetch.

Reading entries

// All entries (HTTP + WebSocket), ordered by creation time
const all = NetworkInspector.getEntries();

// Filter by type
const httpOnly = NetworkInspector.getHttpEntries();
const wsOnly = NetworkInspector.getWebSocketEntries();

// Look up a single entry
const entry = NetworkInspector.getEntry(id);

// Clear the buffer
NetworkInspector.clear();

Live listener

Subscribe to new or updated entries in real time:

const unsubscribe = NetworkInspector.onEntry((entry) => {
if (entry.type === 'http') {
console.log(`${entry.method} ${entry.url}${entry.status} (${entry.duration.toFixed(0)}ms)`);
} else {
console.log(`WS ${entry.url}${entry.messagesSent + entry.messagesReceived} messages`);
}
});

// Later
unsubscribe();

HTTP entry shape

Each HTTP request creates a NetworkEntry:

FieldTypeDescription
idstringUnique request identifier
type'http'Discriminator
urlstringRequest URL
methodstringHTTP method
requestHeadersArray<{ key, value }>Request headers
requestBodystring | undefinedCaptured request body (truncated to maxBodyCapture)
requestBodySizenumberFull body size in bytes
statusnumberHTTP status code
statusTextstringStatus text
responseHeadersArray<{ key, value }>Response headers
responseBodystring | undefinedCaptured response body (truncated to maxBodyCapture)
responseBodySizenumberFull response body size
startTimenumberperformance.now() at request start
endTimenumberperformance.now() at response
durationnumberTotal time in ms
curlstringAuto-generated curl command
errorstring | undefinedError message if failed

WebSocket entry shape

Each WebSocket connection creates a WebSocketEntry:

FieldTypeDescription
idstringUnique connection identifier
type'websocket'Discriminator
urlstringWebSocket URL
protocolsstring[]Requested subprotocols
requestHeadersArray<{ key, value }>Upgrade request headers
readyStatestringCONNECTING, OPEN, or CLOSED
messagesWebSocketMessage[]All recorded messages
messagesSentnumberTotal messages sent
messagesReceivednumberTotal messages received
bytesSentnumberTotal bytes sent
bytesReceivednumberTotal bytes received
closeCodenumber | undefinedWebSocket close code
closeReasonstring | undefinedClose reason
errorstring | undefinedError message if failed
startTime / endTime / durationnumberTiming (ms)

Each WebSocketMessage contains:

FieldTypeDescription
direction'sent' | 'received'Message direction
datastringMessage content (truncated to maxBodyCapture)
sizenumberFull message size in bytes
isBinarybooleanWhether the frame was binary
timestampnumberperformance.now() when recorded

Building a custom UI

You can use the inspector data to build your own network debugging screen. Here's a minimal example:

import React, { useState, useEffect } from 'react';
import { View, Text, FlatList, Pressable } from 'react-native';
import { NetworkInspector } from 'react-native-nitro-fetch';
import type { InspectorEntry } from 'react-native-nitro-fetch';

export function NetworkDebugger() {
const [entries, setEntries] = useState<readonly InspectorEntry[]>([]);

useEffect(() => {
NetworkInspector.enable();
const unsub = NetworkInspector.onEntry(() => {
setEntries([...NetworkInspector.getEntries()]);
});
return () => {
unsub();
NetworkInspector.disable();
};
}, []);

return (
<FlatList
data={entries}
keyExtractor={(e) => e.id}
renderItem={({ item }) => (
<View style={{ padding: 8, borderBottomWidth: 1, borderColor: '#eee' }}>
{item.type === 'http' ? (
<Text>
{item.method} {item.url}{item.status} ({item.duration.toFixed(0)}ms)
</Text>
) : (
<Text>
WS {item.url}{item.messagesSent + item.messagesReceived} msgs
</Text>
)}
</View>
)}
/>
);
}
tip

The example app includes a full-featured inspector screen with filter tabs (All / HTTP / WS), detail views, curl export, and a live log console. See example/src/screens/NetworkInspectorScreen.tsx for the complete implementation.


Native Tracing (Perfetto & Instruments)

Both HTTP fetch and WebSocket operations can emit native trace events, giving you flame-chart visibility in Perfetto (Android) and Instruments (iOS) with zero JS overhead. Both are opt-in via build flags.

Enabling tracing

Tracing is controlled separately for HTTP fetch and WebSockets. You can enable one or both.

Android

Add to your app's gradle.properties:

# HTTP fetch tracing (android.os.Trace async sections)
NitroFetch_enableTracing=true

# WebSocket tracing (ATrace sync sections in C++)
NitroFetchWebsockets_enableTracing=true

Rebuild your app after changing these flags.

iOS

Set environment variables before running pod install:

# HTTP fetch tracing (os_signpost intervals in Swift)
NITROFETCH_TRACING=1 bundle exec pod install

# WebSocket tracing (os_signpost events/intervals in C++)
NITRO_WS_TRACING=1 bundle exec pod install

# Both at once
NITROFETCH_TRACING=1 NITRO_WS_TRACING=1 bundle exec pod install

Then rebuild your app. The env vars inject compile-time flags (NITROFETCH_TRACING Swift condition and -DNITRO_WS_TRACING=1 C++ define) into the pod build — they have no runtime cost when disabled.

What gets traced

HTTP fetch

When NitroFetch_enableTracing=true (Android) or NITROFETCH_TRACING=1 (iOS) is set:

  • Android: Each fetch() call emits an async section via android.os.Trace.beginAsyncSection / endAsyncSection. The label is "NitroFetch <METHOD> <path>" (e.g., NitroFetch GET /api/users). The section spans from request start to response completion (including success, failure, and cancellation).

  • iOS: Each fetch() call emits an os_signpost interval under subsystem com.margelo.nitrofetch, category network, name "NitroFetch". The begin event includes "<METHOD> <path>" and the end event includes "status=<code> bytes=<count>".

WebSocket

When NitroFetchWebsockets_enableTracing=true (Android) or NITRO_WS_TRACING=1 (iOS) is set, the native layer emits trace events for each WebSocket lifecycle event:

  • Android: Uses ATrace_beginSection / ATrace_endSection (synchronous sections from <android/trace.h> in C++). These appear as slices on the thread where the event occurred.

  • iOS: Uses os_signpost under subsystem com.margelo.nitro.websockets, category NitroWS. The connectconnected pair uses an interval (begin/end with a shared signpost ID). All other events (send, receive, close, error) are point events via os_signpost_event_emit.

Capturing a Perfetto trace (Android)

  1. Connect your Android device via USB (with USB debugging enabled).

  2. Open ui.perfetto.dev in Chrome.

  3. Click "Record new trace" and select your device.

  4. Under "Probes", enable "Atrace userspace annotations".

  5. Critical step: In the Atrace configuration, set atrace_apps to your app's package name (e.g., com.example.myapp) or * for all apps. Without this, android.os.Trace and ATrace_beginSection events from your app will not be captured.

  6. Optionally add "Scheduling details" for CPU context.

  7. Click "Start recording", use your app (make fetch requests, open WebSocket connections), then click "Stop".

  8. In the trace viewer, look for your app's process. HTTP traces appear as async slices labeled NitroFetch GET /path, NitroFetch POST /path, etc. WebSocket traces appear as sync slices labeled NitroWS connect <url>, NitroWS established, NitroWS send text, NitroWS receive, etc.

Option 2: Command-line with config file

Create a file trace_config.pbtx:

buffers {
size_kb: 65536
}
data_sources {
config {
name: "linux.ftrace"
ftrace_config {
atrace_categories: "view"
atrace_apps: "com.example.myapp"
}
}
}
duration_ms: 15000
caution

Replace com.example.myapp with your actual package name. Using * captures all apps but produces larger traces.

Then record:

# Push config and start trace
adb push trace_config.pbtx /data/local/tmp/
adb shell perfetto --config /data/local/tmp/trace_config.pbtx --out /data/local/tmp/trace.perfetto-trace

# After using your app, pull the trace
adb pull /data/local/tmp/trace.perfetto-trace .

Open the resulting .perfetto-trace file at ui.perfetto.dev.

Capturing an Instruments trace (iOS)

  1. Open Instruments (Xcode menu → Open Developer Tool → Instruments).

  2. Choose the os_signpost template (or create a custom template and add the "os_signpost" instrument).

  3. Select your app process as the target.

  4. Click the record button, use your app, then stop.

  5. In the timeline, look for these subsystems:

    SubsystemCategoryWhat it traces
    com.margelo.nitrofetchnetworkHTTP fetch requests
    com.margelo.nitro.websocketsNitroWSWebSocket lifecycle
    • HTTP fetch: Appears as intervals (begin/end pair) under name "NitroFetch". The begin annotation shows the method and path (e.g., GET /api/users), the end annotation shows status=200 bytes=1234.
    • WebSocket connect: Appears as an interval from connect (begin, annotated with the URL) to connected (end). This measures the handshake duration.
    • WebSocket send/receive/close/error: Appear as point events under name "NitroWS" with details like send text 42 bytes, receive text, close code=1000 clean=1, error <message>.

Trace events reference

HTTP fetch events

EventAndroidiOS
Request startTrace.beginAsyncSection("NitroFetch GET /path", cookie)os_signpost(.begin, "NitroFetch", "GET /path")
Request end (success)Trace.endAsyncSection("NitroFetch GET /path", cookie)os_signpost(.end, "NitroFetch", "status=200 bytes=N")
Request end (failure)Trace.endAsyncSection(...)os_signpost(.end, ...)
Request end (cancelled)Trace.endAsyncSection(...)os_signpost(.end, ...)

WebSocket events

EventAndroid (ATrace)iOS (os_signpost)
Connect startATrace_beginSection("NitroWS connect wss://...")interval begin: "NitroWS" with URL
ConnectedATrace_beginSection("NitroWS established")interval end: "NitroWS" "connected"
Send textATrace_beginSection("NitroWS send text")event: "send text <N> bytes"
Send binaryATrace_beginSection("NitroWS send binary")event: "send binary <N> bytes"
ReceiveATrace_beginSection("NitroWS receive")event: "receive text" or "receive binary"
CloseATrace_beginSection("NitroWS close")event: "close code=<N> clean=<0|1>"
ErrorATrace_beginSection("NitroWS error")event: "error <message>"
note

On Android, WebSocket events use synchronous ATrace_beginSection / ATrace_endSection pairs (not async sections) because ATrace_beginAsyncSection requires API 29+ while the library supports API 24+. HTTP fetch uses the Java android.os.Trace.beginAsyncSection API which is available on all supported API levels.

See also