WebSocket API Integration
Integrate with the socktop agent’s WebSocket API to build custom monitoring tools. If you’re writing Rust, prefer the socktop_connector library, which wraps all of this.
WebSocket Endpoint
ws://HOST:PORT/ws # Without TLS
wss://HOST:PORT/ws # With TLS
With authentication token (if configured):
ws://HOST:PORT/ws?token=YOUR_TOKEN
wss://HOST:PORT/ws?token=YOUR_TOKEN
The agent also serves GET /healthz over plain HTTP, returning 200 OK — useful for liveness probes.
Request Types
Requests are plain text WebSocket messages (not JSON). The agent replies with one message per request:
| Request | Response |
|---|---|
get_metrics | JSON — fast-changing metrics (CPU, memory, network, GPU) |
get_disks | JSON — array of disk/partition entries |
get_processes | Binary — protobuf process list, gzip-compressed above ~768 bytes |
get_process_metrics:<PID> | JSON — detailed metrics for one process |
get_journal_entries:<PID> | JSON — recent journal entries for one process |
Unknown messages are ignored. The agent is fully request-driven: it collects nothing until you ask, and short TTL caches (metrics 250 ms, disks 1 s, processes 1.5 s) mean multiple clients share collection work.
Response Formats
get_metrics (JSON)
{
"sampled_at_ms": 1755900000000,
"cpu_total": 12.4,
"cpu_per_core": [11.2, 15.7],
"mem_total": 33554432,
"mem_used": 18321408,
"swap_total": 0,
"swap_used": 0,
"hostname": "myserver",
"cpu_temp_c": 42.5,
"disks": [],
"networks": [{"name":"eth0","received":12345678,"transmitted":87654321}],
"top_processes": [],
"gpus": [{"name":"NVIDIA GeForce RTX 5080","utilization_gpu_pct":56,"mem_used_bytes":1073741824,"mem_total_bytes":8589934592}]
}
Notes:
sampled_at_ms(added in 1.60) is the epoch-milliseconds timestamp of when the snapshot was actually collected on the agent. Because responses can be served from the TTL cache, compute rates (e.g. network KB/s) from deltas ofsampled_at_ms, not from your own receive times.disksandtop_processesare always empty here — request them separately withget_disks/get_processes.cpu_temp_cisnullwhen no sensor is available;gpusisnullwhen there is no GPU (or GPU collection is disabled).received/transmittedare cumulative byte counters since agent start.
get_disks (JSON)
[
{"name":"nvme0n1","total":512000000000,"available":320000000000,"temperature":38.5,"is_partition":false},
{"name":"nvme0n1p2","total":511000000000,"available":320000000000,"temperature":null,"is_partition":true}
]
is_partition distinguishes partitions from whole disks (exact on Linux via /sys/block).
get_processes (Protocol Buffers)
Returned as a binary WebSocket message. If the encoded payload exceeds ~768 bytes (nearly always), it is gzip-compressed. Schema:
syntax = "proto3";
package socktop;
message Processes {
uint64 process_count = 1; // total processes in the system
repeated Process rows = 2; // all processes (sorting is client-side)
}
message Process {
uint32 pid = 1;
string name = 2;
float cpu_usage = 3; // 0..100
uint64 mem_bytes = 4; // RSS bytes
}
To decode: check for the gzip magic bytes (0x1f 0x8b), decompress if present, then parse with any protobuf library.
get_process_metrics:<PID> and get_journal_entries:<PID> (JSON)
Added for the process-details view: per-process detail (command line, executable, working directory, per-thread CPU times in microseconds, and more) and recent journal entries. Journal entries carry both a display timestamp (RFC 3339 UTC) and a numeric timestamp_us (epoch microseconds, added in 1.60); the response’s notice field, when present, explains empty results caused by journal access restrictions rather than absence of logs. These responses are cached per PID for 250 ms / 1 s respectively.
Example: JavaScript/Node.js
const WebSocket = require('ws');
const ws = new WebSocket('ws://localhost:3000/ws');
ws.on('open', () => {
console.log('Connected to socktop_agent');
// Requests are plain text messages
setInterval(() => ws.send('get_metrics'), 1000);
setInterval(() => ws.send('get_processes'), 3000);
});
ws.on('message', (data, isBinary) => {
if (isBinary) {
// get_processes reply: gzip'd protobuf (see schema above)
console.log('Binary process list, length:', data.length);
} else {
const metrics = JSON.parse(data.toString());
console.log(`CPU: ${metrics.cpu_total}%`);
}
});
Example: Python
import json
import asyncio
import websockets
async def monitor_system():
uri = "ws://localhost:3000/ws"
async with websockets.connect(uri) as websocket:
print("Connected to socktop_agent")
while True:
await websocket.send("get_metrics") # plain text request
response = await websocket.recv()
if isinstance(response, str):
data = json.loads(response)
print(f"CPU: {data['cpu_total']}%, "
f"Memory: {data['mem_used']/data['mem_total']*100:.1f}%")
else:
print(f"Binary response, length: {len(response)}")
await asyncio.sleep(1)
asyncio.run(monitor_system())
Recommended Intervals
- Metrics: ≥ 500 ms
- Processes: ≥ 2000 ms
- Disks: ≥ 5000 ms
Polling faster than the agent’s TTL caches (250 ms / 1.5 s / 1 s) just returns cached snapshots.
Error Handling
Send each request and await its reply before sending the next of the same kind — replies carry no request ID and are matched by order. Wrap requests in a timeout and treat a timeout as a dead connection: reconnect rather than continuing on a stream that may now be misaligned.
function connect() {
const ws = new WebSocket('ws://localhost:3000/ws');
ws.on('open', () => {
// Start polling
});
ws.on('close', () => {
console.log('Connection lost, reconnecting...');
setTimeout(connect, 1000);
});
}
connect();
Compatibility
Wire changes are additive: new fields (like sampled_at_ms and timestamp_us) appear alongside old ones, so integrations built against older agents keep working against newer ones and vice versa.