234 lines
4.6 KiB
Markdown
234 lines
4.6 KiB
Markdown
---
|
|
title: "probe"
|
|
description: "Runtime observability for actors"
|
|
weight: 90
|
|
type: "docs"
|
|
---
|
|
|
|
Runtime observability for actors. Register named probe functions on any actor and query them over HTTP while the program runs.
|
|
|
|
```javascript
|
|
var probe = use('probe')
|
|
```
|
|
|
|
The probe server starts automatically on the first `register()` call, listening on `127.0.0.1:9000`.
|
|
|
|
## Registering Probes
|
|
|
|
### probe.register(target, probes)
|
|
|
|
Register a group of probe functions under a target name. Each probe is a function that receives an `args` record and returns a value.
|
|
|
|
```javascript
|
|
var probe = use('probe')
|
|
|
|
var world = {
|
|
entities: [
|
|
{id: 1, name: "player", x: 10, y: 20, hp: 100},
|
|
{id: 2, name: "goblin", x: 55, y: 30, hp: 40}
|
|
],
|
|
tick: 0
|
|
}
|
|
|
|
probe.register("game", {
|
|
state: function(args) {
|
|
return world
|
|
},
|
|
entities: function(args) {
|
|
return world.entities
|
|
},
|
|
entity: function(args) {
|
|
return find(world.entities, function(e) {
|
|
return e.id == args.id
|
|
})
|
|
}
|
|
})
|
|
|
|
probe.register("render", {
|
|
info: function(args) {
|
|
return {fps: 60, draw_calls: 128, batches: 12}
|
|
}
|
|
})
|
|
```
|
|
|
|
A target is just a namespace — group related probes under the same target name. Register as many targets as you like; the server starts once and serves them all.
|
|
|
|
## HTTP Endpoints
|
|
|
|
All responses are JSON with an `ok` field.
|
|
|
|
### GET /discover
|
|
|
|
Lists all registered targets and their probe names. Designed for tooling — an LLM or dashboard can call this first to learn what's available, then query specific probes.
|
|
|
|
```
|
|
$ curl http://127.0.0.1:9000/discover
|
|
```
|
|
|
|
```json
|
|
{
|
|
"ok": true,
|
|
"targets": {
|
|
"game": ["state", "entities", "entity"],
|
|
"render": ["info"]
|
|
}
|
|
}
|
|
```
|
|
|
|
### POST /probe
|
|
|
|
Call a single probe function by target and name. Optionally pass arguments.
|
|
|
|
```
|
|
$ curl -X POST -H "Content-Type: application/json" \
|
|
-d '{"target":"game","name":"state"}' \
|
|
http://127.0.0.1:9000/probe
|
|
```
|
|
|
|
```json
|
|
{
|
|
"ok": true,
|
|
"result": {
|
|
"entities": [
|
|
{"id": 1, "name": "player", "x": 10, "y": 20, "hp": 100},
|
|
{"id": 2, "name": "goblin", "x": 55, "y": 30, "hp": 40}
|
|
],
|
|
"tick": 4821
|
|
}
|
|
}
|
|
```
|
|
|
|
With arguments:
|
|
|
|
```
|
|
$ curl -X POST -H "Content-Type: application/json" \
|
|
-d '{"target":"game","name":"entity","args":{"id":1}}' \
|
|
http://127.0.0.1:9000/probe
|
|
```
|
|
|
|
```json
|
|
{
|
|
"ok": true,
|
|
"result": {"id": 1, "name": "player", "x": 10, "y": 20, "hp": 100}
|
|
}
|
|
```
|
|
|
|
### POST /snapshot
|
|
|
|
Call multiple probes in one request. Returns all results keyed by `target/name`.
|
|
|
|
```
|
|
$ curl -X POST -H "Content-Type: application/json" \
|
|
-d '{"probes":[{"target":"game","name":"state"},{"target":"render","name":"info"}]}' \
|
|
http://127.0.0.1:9000/snapshot
|
|
```
|
|
|
|
```json
|
|
{
|
|
"ok": true,
|
|
"results": {
|
|
"game/state": {
|
|
"entities": [
|
|
{"id": 1, "name": "player", "x": 10, "y": 20, "hp": 100},
|
|
{"id": 2, "name": "goblin", "x": 55, "y": 30, "hp": 40}
|
|
],
|
|
"tick": 4821
|
|
},
|
|
"render/info": {
|
|
"fps": 60,
|
|
"draw_calls": 128,
|
|
"batches": 12
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Errors
|
|
|
|
Unknown paths return 404:
|
|
|
|
```json
|
|
{"ok": false, "error": "not found"}
|
|
```
|
|
|
|
Unknown targets or probe names:
|
|
|
|
```json
|
|
{"ok": false, "error": "unknown probe: game/nonexistent"}
|
|
```
|
|
|
|
If a probe function disrupts:
|
|
|
|
```json
|
|
{"ok": false, "error": "probe failed"}
|
|
```
|
|
|
|
## Example
|
|
|
|
A game actor with a simulation loop and probe observability:
|
|
|
|
```javascript
|
|
// game.ce
|
|
var probe = use('probe')
|
|
|
|
var state = {
|
|
entities: [
|
|
{id: 1, name: "player", x: 0, y: 0, hp: 100},
|
|
{id: 2, name: "enemy", x: 50, y: 50, hp: 60}
|
|
],
|
|
frame: 0,
|
|
paused: false
|
|
}
|
|
|
|
probe.register("game", {
|
|
state: function(args) {
|
|
return state
|
|
},
|
|
entities: function(args) {
|
|
return state.entities
|
|
},
|
|
entity: function(args) {
|
|
return find(state.entities, function(e) {
|
|
return e.id == args.id
|
|
})
|
|
}
|
|
})
|
|
|
|
// game loop
|
|
def tick = function(_) {
|
|
if (!state.paused) {
|
|
state.frame = state.frame + 1
|
|
// ... update entities, physics, AI ...
|
|
}
|
|
$delay(tick, 0.016)
|
|
}
|
|
$delay(tick, 0.016)
|
|
```
|
|
|
|
While the game runs, query it from a terminal:
|
|
|
|
```
|
|
$ curl -s http://127.0.0.1:9000/discover | jq .targets
|
|
{
|
|
"game": ["state", "entities", "entity"]
|
|
}
|
|
|
|
$ curl -s -X POST -d '{"target":"game","name":"state"}' \
|
|
-H "Content-Type: application/json" \
|
|
http://127.0.0.1:9000/probe | jq .result.frame
|
|
7834
|
|
|
|
$ curl -s -X POST -d '{"target":"game","name":"entity","args":{"id":1}}' \
|
|
-H "Content-Type: application/json" \
|
|
http://127.0.0.1:9000/probe | jq .result
|
|
{
|
|
"id": 1,
|
|
"name": "player",
|
|
"x": 142,
|
|
"y": 87,
|
|
"hp": 100
|
|
}
|
|
```
|
|
|
|
Probes run inside the actor's normal turn, so the values are always consistent — never a half-updated frame.
|