Documentation Index
Fetch the complete documentation index at: https://mintlify.com/pinchtab/pinchtab/llms.txt
Use this file to discover all available pages before exploring further.
PinchTab monitors Chrome’s memory usage at the OS level, providing accurate real-world memory consumption for each browser instance.
Enabling Memory Metrics
Memory monitoring is off by default to minimize overhead.
Open Dashboard
Navigate to http://localhost:<port>/dashboard
Go to Settings
Click Settings → 📈 Monitoring
Enable Metrics
Toggle Tab Memory Metrics on (marked as experimental)
Apply Changes
Click Apply Settings to save
How Memory is Calculated
PinchTab uses a process tree approach to measure memory:
Identify Browser Process
Gets the main Chrome PID from chromedp
Walk Process Tree
Finds all child processes (renderers, GPU, utilities)
Sum RSS Memory
Adds up Resident Set Size across all Chrome processes
Filter Renderers
Counts processes with --type=renderer for tab count
This approach ensures memory is isolated per instance — you only see memory for that specific Chrome profile, not other Chrome windows on your system.
Memory Fields
| Field | Description |
|---|
memoryMB | Real OS-level memory (RSS) across all Chrome processes |
jsHeapUsedMB | Estimated JS heap (~40% of memoryMB) |
jsHeapTotalMB | Estimated total heap (~50% of memoryMB) |
renderers | Number of renderer processes (≈ tabs) |
JS heap values are estimates based on typical Chrome memory distribution. For exact JS heap metrics, use Chrome DevTools directly.
API Endpoints
Per-Tab Metrics
GET /tabs/{tabId}/metrics
Returns memory metrics (aggregated, since we can’t reliably map tabs to PIDs):
{
"memoryMB": 850.5,
"jsHeapUsedMB": 340.2,
"jsHeapTotalMB": 425.25,
"renderers": 11,
"documents": 0,
"frames": 0,
"nodes": 0,
"listeners": 0
}
curl http://localhost:9867/tabs/tab_abc123/metrics | jq .
Instance Metrics
Returns server metrics and browser memory:
{
"metrics": {
"goHeapAllocMB": 12.5,
"goHeapSysMB": 24.0,
"goNumGoroutine": 15
},
"memory": {
"memoryMB": 850.5,
"jsHeapUsedMB": 340.2,
"jsHeapTotalMB": 425.25,
"renderers": 11
}
}
curl http://localhost:9868/metrics | jq .
All Instances (Orchestrator)
Returns memory metrics for all running instances:
[
{
"instanceId": "inst_abc123",
"profileName": "MyProfile",
"jsHeapUsedMB": 340.2,
"jsHeapTotalMB": 425.25,
"documents": 0,
"frames": 0,
"nodes": 0,
"listeners": 0
}
]
curl http://localhost:9867/instances/metrics | jq .
Dashboard Visualization
When enabled, the Monitoring page shows:
- Chart: Solid lines for tab counts (left axis), dashed lines for memory in MB (right axis)
- Instance List: Shows memory alongside tab count (e.g.,
:9869 · 11 tabs · 1382MB)
- Real-time updates: Data polled every 30 seconds, chart retains ~30 minutes of history
The dashboard provides a visual timeline of memory usage and tab counts across all instances.
- Memory metrics use gopsutil for OS-level process info
- Minimal overhead: just reads
/proc (Linux) or uses sysctl (macOS)
- No CDP calls required — works reliably even with many tabs
- 5-second grace period for new instances to stabilize before polling
Memory monitoring has negligible performance impact since it reads OS-level statistics rather than querying Chrome directly.
Monitoring Scripts
Basic Memory Monitor
#!/bin/bash
while true; do
echo "=== $(date) ==="
# Get metrics for all instances
METRICS=$(curl -s http://localhost:9867/instances/metrics)
# Calculate totals
TOTAL_HEAP=$(echo "$METRICS" | jq '[.[].jsHeapUsedMB] | add')
INSTANCE_COUNT=$(echo "$METRICS" | jq 'length')
echo "Instances: $INSTANCE_COUNT"
echo "Total JS Heap: ${TOTAL_HEAP} MB"
# Per-instance breakdown
echo "$METRICS" | jq -r '.[] | "\(.profileName): \(.jsHeapUsedMB) MB"'
sleep 30
done
Alert on High Memory
import requests
import time
THRESHOLD_MB = 2000 # Alert if any instance exceeds 2GB
while True:
try:
response = requests.get('http://localhost:9867/instances/metrics')
instances = response.json()
for inst in instances:
memory = inst.get('jsHeapUsedMB', 0)
if memory > THRESHOLD_MB:
print(f"⚠️ HIGH MEMORY: {inst['profileName']} using {memory:.1f} MB")
print(f" Instance ID: {inst['instanceId']}")
total = sum(i.get('jsHeapUsedMB', 0) for i in instances)
print(f"Total: {total:.1f} MB across {len(instances)} instances")
except Exception as e:
print(f"Error: {e}")
time.sleep(30)
Export Metrics to CSV
import { writeFileSync } from 'fs';
interface InstanceMetrics {
instanceId: string;
profileName: string;
jsHeapUsedMB: number;
jsHeapTotalMB: number;
}
async function exportMetrics() {
const response = await fetch('http://localhost:9867/instances/metrics');
const instances: InstanceMetrics[] = await response.json();
const timestamp = new Date().toISOString();
const csv = [
'timestamp,instanceId,profileName,jsHeapUsedMB,jsHeapTotalMB',
...instances.map(i =>
`${timestamp},${i.instanceId},${i.profileName},${i.jsHeapUsedMB},${i.jsHeapTotalMB}`
)
].join('\n');
const filename = `metrics-${Date.now()}.csv`;
writeFileSync(filename, csv);
console.log(`Exported to ${filename}`);
}
// Run every minute
setInterval(exportMetrics, 60000);
exportMetrics();
Troubleshooting
Memory shows 0
- Chrome may not have started yet (check instance status)
- Browser context not initialized
- Wait 5-10 seconds after instance creation
# Check instance status
curl http://localhost:9867/instances | jq '.[] | {id, status}'
# Wait for running status
INST_ID="inst_abc123"
while [ "$(curl -s http://localhost:9867/instances/$INST_ID | jq -r '.status')" != "running" ]; do
echo "Waiting for instance to start..."
sleep 1
done
Memory seems too high
- Chrome is known to use significant memory
- Each tab runs in a separate renderer process
- Extensions and DevTools add to memory usage
- Consider closing unused tabs or instances
# List tabs per instance
curl http://localhost:9867/instances | jq '.[] | {id, profileName, port}' | while read -r line; do
INST_ID=$(echo $line | jq -r '.id')
TAB_COUNT=$(curl -s http://localhost:9867/instances/$INST_ID/tabs | jq 'length')
echo "$line has $TAB_COUNT tabs"
done
Memory doesn’t match Activity Monitor/Task Manager
- PinchTab shows RSS (Resident Set Size) for Chrome processes
- System tools may show different metrics (virtual memory, shared memory)
- Other Chrome windows are excluded — we only count this instance’s process tree
RSS (Resident Set Size) represents actual physical memory used, which is the most accurate metric for resource planning.
Memory Management Best Practices
Instance Limits
Cleanup Strategy
Pooling
Set reasonable limits based on available RAM:# Estimate memory per instance type
HEADLESS_MB=80
HEADED_MB=150
AVAILABLE_RAM_MB=8192 # 8GB
# Calculate max instances
MAX_HEADLESS=$((AVAILABLE_RAM_MB / HEADLESS_MB))
MAX_HEADED=$((AVAILABLE_RAM_MB / HEADED_MB))
echo "Can run ~$MAX_HEADLESS headless or ~$MAX_HEADED headed instances"
Automatically stop instances exceeding memory limits:import requests
MAX_MEMORY_MB = 1500
response = requests.get('http://localhost:9867/instances/metrics')
instances = response.json()
for inst in instances:
if inst.get('jsHeapUsedMB', 0) > MAX_MEMORY_MB:
print(f"Stopping {inst['profileName']} (memory: {inst['jsHeapUsedMB']:.1f} MB)")
requests.post(
f"http://localhost:9867/instances/{inst['instanceId']}/stop"
)
Reuse instances instead of creating new ones:class InstancePool {
private available: string[] = [];
async acquire(): Promise<string> {
if (this.available.length > 0) {
return this.available.pop()!;
}
// Create new instance
const response = await fetch('http://localhost:9867/instances/launch', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ mode: 'headless' })
});
const data = await response.json();
return data.id;
}
release(instanceId: string) {
this.available.push(instanceId);
}
}
const pool = new InstancePool();
const id = await pool.acquire();
// ... use instance ...
pool.release(id);