HTTP
This section documents the "REST" (*) API that can be used for initiating communication with the PowerGoblin from the SUT (system under test) or any other system. The goal of the API is to provide an easy-to-use interface for scripts to control the measurement process.
By default, the program does not offer any modern security features. For example, the interface communication is not encrypted, which means that the connection should only be used in a controlled environment or properly firewalled and tunneled using encrypted connections. On the other hand, this has an advantage because encryption would increase the latency of the communication and could even affect the measurement's results.
However, the scope of the program's operations is limited to the output file directories defined for the program. If necessary, a more restrictive sandbox can be built for the program, for example with container technologies.
(*) JSON/RPC would probably better describe how the interface works.
Control commands
The following examples demonstrate the use of the PowerGoblin HTTP API in common programming languages (Shell, Python 3, Java 21+, JavaScript, Kotlin 2.4+).
Shell scripting is probably most useful if the measurement is coordinated by external scripts that are not part of the software to be measured. This way the software can be seen as a black box. Such scripts should be compatible with most systems that support Unix shell scripts. Note that shell scripts might be problematic for high performance measurement because of the latency introduced by the script interpreters and invocation of operating system processes.
Python scripting is most useful for measurements triggered by a front-end Selenium script because the trigger points can be selected more freely and for example the initialization of the browser instance can be discarded.
Java and JavaScript examples are provided for other possible scenarios. For instance, a backend server based on Spring Boot or Node.js could be the source of trigger events.
# Requires curl/wget & coreutils
# GET requests
pg_get() {
wget -qO- "http://$HOST/api/v2/$*"
}
pg_get() {
curl "http://$HOST/api/v2/$*"
}
# POST requests (stdin stored to a file)
pg_post() {
FILE="$(mktemp)"
cat > "$FILE"
wget -qO- --post-file "$FILE" "http://$HOST/api/v2/$*"
rm "$FILE"
}
pg_post() {
curl -d@- "http://$HOST/api/v2/$*"
}
# POST request (send a file)
pg_post_file() {
wget -qO- --post-file "$1" http://$HOST/api/v2/$2
}
pg_post_file() {
curl --data-binary "@$1" http://$HOST/api/v2/$2
}
# --- Example ---
# PowerGoblin server
export HOST=localhost:8080
# GET examples
echo|pg_post session
pg_get session/latest/meter
echo Measurement|pg_post session/latest/trigger
echo name|pg_post session/latest/measurement
echo Run|pg_post session/latest/trigger
echo RunStop|pg_post session/latest/trigger
echo MeasurementStop|pg_post session/latest/trigger
pg_post_file collectd.zip session/latest/import/collectd
# Requires python-requests, python-simplejson
import requests
import json
class GoblinClient:
def __init__(self, host):
self.host = "http://" + host + "/api/v2/"
def get(self, url):
return requests.get(self.host + url)
def post_json(self, url, json):
return requests.post(self.host + url, json = json)
def post_text(self, url, text):
h = {'Content-Type': 'text/plain'}
return requests.post(self.host + url, data = text, headers=h)
def post_file(self, url, file):
with open(file, 'rb') as p:
h = {'content-type': 'application/x-zip'}
return requests.post(self.host + url, data=p, verify=False, headers=h)
def interpret(self, result):
return json.loads(result.content)["result"]
# --- Example ---
c = GoblinClient("localhost:8080")
c.post_text("session", "")
meters = c.get("session/latest/meter")
print(c.interpret(meters))
c.post_text("session/latest/trigger", text = "Measurement")
c.post_text("session/latest/measurement", json = "name")
c.post_text("session/latest/trigger", json = "Run")
c.post_text("session/latest/trigger", json = "RunStop")
c.post_text("session/latest/trigger", text = "MeasurementStop")
c.post_file("session/latest/import/collectd", "collectd.zip")
import java.io.IOException;
import java.nio.file.*;
import java.net.*;
import java.net.http.*;
import java.util.zip.*;
record GoblinClient(String host) {
private HttpResponse<String> run(HttpRequest.Builder b) throws Exception {
try (var client = HttpClient.newHttpClient()) {
return client.send(
b.build(),
HttpResponse.BodyHandlers.ofString()
);
}
}
private HttpRequest.Builder api(String api) {
return HttpRequest.newBuilder(URI.create("http://"+host+"/api/v2/"+api));
}
HttpResponse<String> get(String api) throws Exception {
return run(api(api));
}
HttpResponse<String> post(String api, String msg) throws Exception {
return run(api(api).POST(HttpRequest.BodyPublishers.ofString(msg)));
}
HttpResponse<String> post(String api, Path path) throws Exception {
return run(api(api).POST(HttpRequest.BodyPublishers.ofFile(path)));
}
}
// --- Example ---
void main() {
var c = new GoblinClient("localhost:8080");
c.post("session", "");
c.get("session/latest/meter");
c.post("session/latest/trigger", "Measurement");
c.post("session/latest/measurement", "name");
c.post("session/latest/trigger", "Run");
c.post("session/latest/trigger", "RunStop");
c.post("session/latest/trigger", "MeasurementStop");
c.post("session/latest/import/collectd", Path.of("collectd.zip"));
}
const request = require('request');
const host = "localhost:8080";
function get(api) {
request(
"http://" + host + "/api/v2/" + api,
(e, r, body) => {
if (!e && r.statusCode == 200)
console.log(body);
}
);
}
function post_json(api, data) {
request.post(
{
url: "http://" + host + "/api/v2/" + api,
json: data,
},
(e, r, body) => {
if (!e && r.statusCode == 200)
console.log(body)
}
);
}
// --- Example ---
post("session", "")
get("session/latest/meter")
post("session/latest/trigger", "Measurement")
post("session/latest/measurement", "name")
post("session/latest/trigger", "Run")
post("session/latest/trigger", "RunStop")
post("session/latest/trigger", "MeasurementStop")
post_json("session/latest/rename", "foobar");
import java.nio.file.*
import java.net.*
import java.net.http.*
import java.util.zip.*
class GoblinClient(val host: String) {
private fun run(b: HttpRequest.Builder) =
HttpClient.newHttpClient().use {
it.send(
b.build(),
HttpResponse.BodyHandlers.ofString()
)
}
private fun api(api: String) =
HttpRequest.newBuilder(URI.create("http://$host/api/v2/$api"))
fun get(api: String) = run(api(api))
fun post(api: String, msg: String) =
run(api(api).POST(HttpRequest.BodyPublishers.ofString(msg)))
fun post(api: String, path: Path) =
run(api(api).POST(HttpRequest.BodyPublishers.ofFile(path)))
}
// --- Example ---
var c = GoblinClient("localhost:8080")
c.post("session", "")
c.get("session/latest/meter")
c.post("session/latest/trigger", "Measurement")
c.post("session/latest/measurement", "name")
c.post("session/latest/trigger", "Run")
c.post("session/latest/trigger", "RunStop")
c.post("session/latest/trigger", "MeasurementStop")
c.post("session/latest/import/collectd", Path.of("collectd.zip"))
The shell scripts assume that the environment variable $HOST points to the host and port running PowerGoblin. The Python and Java versions use object-oriented approach where the server's address and port are provided for the constructor. Further processing of the HTTP reply data requires a JSON parser. We have included one in the Python example.
The default port for the web interface is 8080. The SUT is supposed to be a separate system to minimize the risk of interference, but the commands can be executed on any system, including the one running PowerGoblin, as well. This flexibility allows setting up the measurement in a multitude of ways.
The start & stop run / measurement commands are also available via the web UI. The commands for storing the data is there as well.
HTTP API description
THIS REPRESENTS THE NEW (v2) HTTP API! You can find the documentation for the old version (v1) here.
The following control commands encode the command's name and possible parameters as part of the HTTP query. The following table describes the meaning of the URLs:
| Method | Endpoint | Description | Context |
|---|---|---|---|
| POST | /api/v2/cmd | Execute a command. Payload: serialized Command object | global |
| GET | /api/v2/cmd/startSession | Starts a new session. (deprecated) | global |
| GET | /api/v2/instance | Return the state of all instances. | global |
| GET | /api/v2/instance/:instance | Return the instance state. | instance 'instance' |
| GET | /api/v2/instance/:instance/busy | Query whether there are active tasks or sessions writing to disk. | instance 'instance' |
| GET | /api/v2/instance/:instance/debug/apicalls | Return the list of supported API endpoints. | instance 'instance' |
| GET | /api/v2/instance/:instance/debug/ipaddr | Run 'ipaddr' and return the output. | instance 'instance' |
| GET | /api/v2/instance/:instance/debug/lspci | Run 'lspci' and return the output. | instance 'instance' |
| GET | /api/v2/instance/:instance/debug/lsusb | Run 'lsusb' and return the output. | instance 'instance' |
| GET | /api/v2/instance/:instance/debug/mqtt | Return the list of MQTT message entries. | instance 'instance' |
| GET | /api/v2/instance/:instance/log | Return the list of all compatible session logs. | instance 'instance' |
| GET | /api/v2/instance/:instance/log/busy | Return the number of sessions writing / planning to write the state to disk. | instance 'instance' |
| GET | /api/v2/instance/:instance/log/count | Return the total number of log files. | instance 'instance' |
| GET | /api/v2/instance/:instance/log/incompatible | Return the list of all incompatible session logs. | instance 'instance' |
| GET | /api/v2/instance/:instance/log/past | Return the list of all compatible session logs stored in the past. | instance 'instance' |
| GET | /api/v2/instance/:instance/log/today | Return the list of all compatible session logs stored today. | instance 'instance' |
| GET | /api/v2/instance/:instance/meter | Return the list of meter states for all meters. | instance 'instance' |
| GET | /api/v2/instance/:instance/meter//count | Return the number of meters. | instance 'instance' |
| GET | /api/v2/instance/:instance/meter//free | Return the status of all free meters. | instance 'instance' |
| GET | /api/v2/instance/:instance/meter//free-specs | Return the specs of all free meters. | instance 'instance' |
| GET | /api/v2/instance/:instance/meter//specs | Return the specs of all meters. | instance 'instance' |
| GET | /api/v2/instance/:instance/meter/:meter | Return the state of the meter. | instance 'instance', meter 'meter' |
| GET | /api/v2/instance/:instance/meter/:meter/free | Return the state of the free meter. | instance 'instance', meter 'meter' |
| GET | /api/v2/instance/:instance/meter/:meter/free-specs | Return the specs of the free meter. | instance 'instance', meter 'meter' |
| GET | /api/v2/instance/:instance/meter/:meter/specs | Return the specs of the meter. | instance 'instance', meter 'meter' |
| GET | /api/v2/instance/:instance/node | Return the list of node entries. | instance 'instance' |
| GET | /api/v2/instance/:instance/task | Return the list of asynchronous task entries. | instance 'instance' |
| GET | /api/v2/instance/local | Return the local instance state. | instance, local |
| POST | /api/v2/mqtt/:topic | Send a MQTT message. Payload: message body | topic 'topic' |
| GET | /api/v2/node | Return the list of summaries of all nodes. | global |
| POST | /api/v2/node | Register a node. Payload: serialized NodeSpecs object | global |
| GET | /api/v2/node/:node | Return the state of the node. | node 'node' |
| POST | /api/v2/node/:node/collectd-stream | Receive a collectd resource stream (http plugin). Payload: resource stream | unit 'node' |
| GET | /api/v2/node/:node/commands | Return the list of enqueued node commands. | node 'node' |
| POST | /api/v2/node/:node/enqueue | Enqueue an agent command. Payload: serialized AgentCommand object | node 'node' |
| POST | /api/v2/node/:node/execute | Enqueue a agent execute command. Payload: space separated list of command line arguments | node 'node' |
| POST | /api/v2/node/:node/message | Append a node message. Payload: message content | node 'node' |
| POST | /api/v2/node/:node/status | Update the agent status. Payload: serialized AgentStatus object | node 'node' |
| POST | /api/v2/null | A null sink that discards everything (network benchmarks). Payload: stream | global |
| GET | /api/v2/session | Return the state data for all active and inactive sessions in this instance. | global |
| POST | /api/v2/session | Start a new session. Payload: optional description | global |
| GET | /api/v2/session/:session | Return the session state. | session 'session' |
| GET | /api/v2/session/:session/author | Return the session author. | session 'session' |
| POST | /api/v2/session/:session/author | Set the session author. Payload: session author | instance 'instance' |
| GET | /api/v2/session/:session/close | Close the session (stays available). (deprecated) | session 'session' |
| POST | /api/v2/session/:session/cmd | Validate the session (validation flag). Payload: validate | instance 'instance' |
| POST | /api/v2/session/:session/cmd | Reset the session (wipes all session data). Payload: reset | instance 'instance' |
| POST | /api/v2/session/:session/cmd | Remove the session from the instance's memory. Payload: remove | instance 'instance' |
| POST | /api/v2/session/:session/cmd | Store the session to disk. Payload: store | instance 'instance' |
| POST | /api/v2/session/:session/cmd | Close the session. Payload: close | instance 'instance' |
| GET | /api/v2/session/:session/description | Return the session description. | session 'session' |
| POST | /api/v2/session/:session/description | Set the session description. Payload: session description | instance 'instance' |
| POST | /api/v2/session/:session/import/assets | Import session assets (zip archive). Payload: asset archive (zip) | instance 'instance' |
| POST | /api/v2/session/:session/import/collectd-stream/:unit | Receive a collectd resource stream (http plugin), 'unit' override is optional. Payload: resource stream | instance 'instance', unit 'unit' |
| POST | /api/v2/session/:session/import/collectd/:unit | Import collectd dump (zip archive), 'unit' override is optional. Payload: resource dump (zip) | instance 'instance', unit 'unit' |
| POST | /api/v2/session/:session/import/template | Import session description. Payload: Description document | instance 'instance' |
| POST | /api/v2/session/:session/import/variables | Import session variables. Payload: Variables structure | instance 'instance' |
| GET | /api/v2/session/:session/log | Return the session summary. | session 'session' |
| GET | /api/v2/session/:session/log/busy | Query whether the session logs are still busy. | session 'session' |
| GET | /api/v2/session/:session/log/errors | Return the error log for this session or null. | session 'session' |
| GET | /api/v2/session/:session/log/files | Return the list of relative paths of log files or null. | session 'session' |
| GET | /api/v2/session/:session/log/path | Return the associated log folder. | session 'session' |
| GET | /api/v2/session/:session/log/power/:meter/:channel | Return the (power, time) scatter data for the meter & channel & measurement. | session 'session' |
| GET | /api/v2/session/:session/log/resource/:unit/:resource | Return the resource readings for the unit/resource/measurement for doing scatter plots. | session 'session' |
| GET | /api/v2/session/:session/log/statistics | Return session statistics. | session 'session' |
| GET | /api/v2/session/:session/measurement | Return the list of summaries for past measurements. | session 'session' |
| POST | /api/v2/session/:session/measurement | Set the measurement name. Payload: measurement name | instance 'instance' |
| GET | /api/v2/session/:session/measurement/:m | Return the summary of a past measurement. | session 'session', measurement 'm' |
| GET | /api/v2/session/:session/measurement/active | Return the state of the currently active measurement. | session 'session', active measurement |
| GET | /api/v2/session/:session/measurement/start | Start a new measurement. (deprecated) | session 'session' |
| GET | /api/v2/session/:session/measurement/stop | Stop the measurement. (deprecated) | session 'session' |
| POST | /api/v2/session/:session/merge | Merge session with the other, chronologically later session. Payload: other session | instance 'instance' |
| POST | /api/v2/session/:session/message/:unit | Create a new message event, initiated by 'unit'. Payload: message body | instance 'instance' |
| POST | /api/v2/session/:session/meter | Set the set of assigned meter(s) for the session. Payload: comma separated list of meter ids | instance 'instance' |
| POST | /api/v2/session/:session/meter/add | Add meter(s) to the session. Payload: comma separated list of meter ids | instance 'instance' |
| POST | /api/v2/session/:session/meter/name/:meter/:channel | Rename the meter/channel for the session. Payload: meter name | instance 'instance', meter 'meter', channel 'channel' |
| POST | /api/v2/session/:session/meter/remove | Remove meter(s) from the session. Payload: comma separated list of meter ids | instance 'instance' |
| POST | /api/v2/session/:session/meter/toggle | Toggle the session meter(s) on/off. Payload: comma separated list of meter ids | instance 'instance' |
| GET | /api/v2/session/:session/name | Return the session name. | session 'session' |
| POST | /api/v2/session/:session/name | Set the session name. Payload: session name | instance 'instance' |
| GET | /api/v2/session/:session/remove | Removes the session. (deprecated) | session 'session' |
| GET | /api/v2/session/:session/resource | Return the session resource consumption. | session 'session' |
| POST | /api/v2/session/:session/resource/add/:unit/:resource | Add a custom resource entry for unit/resource. Payload: numeric resource value | instance 'instance' |
| POST | /api/v2/session/:session/resource/exclude | Exclude the resource filter(s). Payload: comma separated list of filters | instance 'instance' |
| POST | /api/v2/session/:session/resource/include | Include the resource filter(s). Payload: comma separated list of filters | instance 'instance' |
| GET | /api/v2/session/:session/run | Return the list of summaries for past runs. | session 'session' |
| GET | /api/v2/session/:session/run/:r | Return the summary of a past run. | session 'session', run 'r' |
| GET | /api/v2/session/:session/run/active | Return the state of the currently active run. | session 'session', active run |
| GET | /api/v2/session/:session/run/start | Start a new run. (deprecated) | session 'session' |
| GET | /api/v2/session/:session/run/stop | Stop the run. (deprecated) | session 'session' |
| GET | /api/v2/session/:session/sync | Return the session synchronization data. (*) | session 'session' |
| POST | /api/v2/session/:session/sync/:unit | Synchronize the clocks between the unit and the instance. Payload: synchronization timestamps (**) | instance 'instance', unit 'unit' |
| POST | /api/v2/session/:session/trigger | Create a new trigger event. Payload: trigger type | instance 'instance' |
| GET | /api/v2/session/latest | Return the session state. | session, most recent |
| POST | /api/v2/session/reconstruct | Reconstruct a past session. Payload: session path (***) | global |
| POST | /api/v2/session/restore | Restore a past session. Payload: session path (***) | global |
Notes
(*) Currently only a single (latest) synchronization point is supported per unit. Measuring of clock drift is not supported. In the future, support for multiple points and linear interpolation of clock drift may be added. In such a case, setting up only one synchronization point will operate in the same way as before.
(**) The supported timestamp format looks like date +%s%N, using the date utility from GNU coreutils:
$ date +%s%N
1744729470653712269
(***) For security reasons, relative paths or absolute paths pointing outside the log directory will be discarded.
Telnet
In addition to the HTTP API, PowerGoblin also provides a simple Telnet style interface for communication. Using this interface is potentially more straightforward and produces lower latency due to simpler command sending and parsing. The API has two backends, a generic socket interface and on Linux also io_uring for higher performance and lower latency.
Control commands
The following examples demonstrate the use of the PowerGoblin Telnet API in common programming languages (Shell, Python 3, Java 21+, JavaScript, Kotlin 2.4+).
Shell scripting is probably most useful if the measurement is coordinated by external scripts that are not part of the software to be measured. This way the software can be seen as a black box. Such scripts should be compatible with most systems that support Unix shell scripts. Note that shell scripts might be problematic for high performance measurement because of the latency introduced by the script interpreters and invocation of operating system processes.
Python scripting is most useful for measurements triggered by a front-end Selenium script because the trigger points can be selected more freely and for example the initialization of the browser instance can be discarded.
Java and JavaScript examples are provided for other possible scenarios. For instance, a backend server based on Spring Boot or Node.js could be the source of trigger events.
# Requires coreutils & gnu-netcat or busybox
GoblinClient() {
export HOST=$1
export PORT=$2
}
send() {
echo $* | nc $HOST $PORT
}
# --- Example ---
GoblinClient 127.0.0.1 9000
send SESSION latest METER
send SESSION latest MEASUREMENT START algo1
send SESSION latest SYNC $(date +%s%N)
send SESSION latest RUN START
send SESSION latest RUN STOP
send SESSION latest MEASUREMENT STOP
send SESSION latest CLOSE
import socket
import time
class GoblinClient:
def __init__(self, host, port):
self.host = host
self.port = port
def send(self, msg):
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.socket.connect((self.host, self.port))
self.socket.sendall(bytes(msg, "utf-8"))
self.socket.close()
def milli_time():
return str(round(time.time() * 1000))
# --- Examples ---
c = GoblinClient("127.0.0.1", 9000)
c.send("SESSION latest METER")
c.send("SESSION latest SYNC " + milli_time())
c.send("SESSION latest MEASUREMENT START algo1")
c.send("SESSION latest RUN START")
c.send("SESSION latest RUN STOP")
c.send("SESSION latest MEASUREMENT STOP")
c.send("SESSION latest CLOSE")
import java.io.PrintWriter;
public record GoblinClient(String host, int port) {
void send(String msg) throws Exception {
try (
var s = new java.net.Socket(host, port);
var w = new PrintWriter(s.getOutputStream())
) {
w.println(msg);
}
}
}
// --- Example ---
void main() {
var c = new GoblinClient("127.0.0.1", 9000);
c.send("SESSION latest METER");
c.send("SESSION latest SYNC " + System.currentTimeMillis());
c.send("SESSION latest MEASUREMENT START algo1");
c.send("SESSION latest RUN START");
c.send("SESSION latest RUN STOP");
c.send("SESSION latest MEASUREMENT STOP");
c.send("SESSION latest CLOSE");
}
var net = require('net');
const GoblinClient = (host, port) => ({
send: (msg) => {
var client = new net.Socket();
client.connect(port, host, function() {
client.write(msg);
});
client.on('data', function(data) {
console.log("RESULT: "+data);
client.destroy();
});
}
});
// --- Example ---
var c = GoblinClient("127.0.0.1", 9000);
c.send("SESSION latest METER");
c.send("SESSION latest SYNC " + Date.now());
c.send("SESSION latest MEASUREMENT START algo1");
c.send("SESSION latest RUN START");
c.send("SESSION latest RUN STOP");
c.send("SESSION latest MEASUREMENT STOP");
c.send("SESSION latest CLOSE");
class GoblinClient(val host: String, val port: Int) {
fun send(msg: String) =
java.net.Socket(host, port).use {
java.io.PrintWriter(it.outputStream).use {
it.println(msg)
}
}
}
// --- Example ---
val c = GoblinClient("127.0.0.1", 9000)
c.send("SESSION latest METER")
c.send("SESSION latest SYNC " + System.currentTimeMillis())
c.send("SESSION latest MEASUREMENT START algo1")
c.send("SESSION latest RUN START")
c.send("SESSION latest RUN STOP")
c.send("SESSION latest MEASUREMENT STOP")
c.send("SESSION latest CLOSE")
The shell scripts assume that the environment variable $HOST points to the host and $PORT to the port running PowerGoblin. The Python and Java versions use object-oriented approach where the server's address and port are provided for the constructor. Further processing of the reply data requires a JSON parser.
The default port for the Telnet interface is 9000. The SUT is supposed to be a separate system to minimize the risk of interference, but the commands can be executed on any system, including the one running PowerGoblin, as well. This flexibility allows setting up the measurement in a multitude of ways.
Telnet API description
The following control commands encode the command's name and possible parameters as part a plain text query. Multiple parameters are space separated. The following table describes the meaning of the commands:
| Command | Type | Description |
|---|---|---|
| START | Cmd | Start a new session. |
| RESTORE :id | Cmd | Restore an old session 'id'. |
| RECONSTRUCT :id | Cmd | Reconstruct an old session 'id'. |
| RECONFIGURE | Cmd | Reconfigure the meters. |
| SHUTDOWN | Cmd | Shut down the application. |
| BENCH :id | Cmd | Start a benchmark. The perf result can be read from the logs. |
| INSTANCE | Query | Return the specs of the instance. |
| TASK | Query | Return the status of the async tasks. |
| METER | Query | Return the specs of all meters. |
| NODE | Query | Return the specs of all nodes. |
| SESSION | Query | Return the entries of all sessions. |
| SESSION :id | Query | Return state of the session 'id'. |
| SESSION :id METER | Query | Return the specs of the meter(s) in session 'id'. |
| SESSION :id METER STATUS | Query | Return the status of meter(s) in session 'id'. |
| SESSION :id METER ADD :m | Cmd | Add the meters 'm' (space separated) to session 'id'. |
| SESSION :id METER REMOVE :m | Cmd | Remove the meters 'm' (space separated) from session 'id'. |
| SESSION :id METER SET :m | Cmd | Set the meters 'm' (space separated) for session 'id'. |
| SESSION :id METER RENAME :m :c :name | Event | Rename the session 'id' meter 'm' channel 'c' to 'name'. |
| SESSION :id STORE | Cmd | Store the session 'id' data. |
| SESSION :id CLOSE | Cmd | Close the session 'id'. |
| SESSION :id REMOVE | Cmd | Close the session 'id' & remove from list. |
| SESSION :id RESET | Cmd | Reset the session 'id' data (warning: resets everything). |
| SESSION :id RENAME :name | Event | Rename the session 'id' to 'name'. |
| SESSION :id SYNC :ts | Event | Synchronize the session 'id' clocks ('ts' = space separate timestamps) between SUT & PowerGoblin (*). |
| SESSION :id MEASUREMENT | Query | Return the status of the current measurement in session 'id'. |
| SESSION :id MEASUREMENT START :name | Event | Start a new measurement in session 'id', 'name' is optional. |
| SESSION :id MEASUREMENT STOP | Event | Stop the measurement in session 'id'. |
| SESSION :id RUN | Query | Return the status of the current run in session 'id'. |
| SESSION :id RUN START | Event | Start a new run in session 'id'. |
| SESSION :id RUN STOP | Event | Stop the run in session 'id'. |
| SESSION :id MESSAGE :msg | Event | Create a new 'msg' in session 'id'. |
(*) Currently only a single (latest) sync point is supported and the measuring of clock drift is not supported. In the future multiple points may be supported.
The supported timestamp format looks like date +%s%N, using the date utility from GNU coreutils:
$ date +%s%N
1744729470653712269
MQTT
PowerGoblin also provides an interface for monitoring PowerGoblin instances, their sessions and measurement using the MQTT protocol.
Since MQTT messages are routed through the MQTT broker, the protocol does not allow for as low-latency communication as direct connection via HTTP or Telnet protocols. In fact, latency can be very high with MQTT. The first version of PowerGoblin supported coordinating measurements using MQTT, but we found that the latency affected the measurement results so much that the feature has now been removed in PowerGoblin 2. We now use MQTT communication only for non-time-critical remote monitoring.
If MQTT has been enabled (see CLI options & global configuration), the PowerGoblin instance first discovers the hostname of the host system, and subscribes to the topic lab-HOSTNAME/. JSON encoded status events are submitted to the topic lab-HOSTNAME/INSTANCE_ID. The instance ID is the same as the one listed via the HTTP API, a hash of the JSON encoded instance specification structure. Since the launch time affects this structure, it is likely that even two instances running on the same host have different ID values.
The encoding of the messages is defined by the StatusEvents:
{
"type": "instanceStarted",
"nt": 53143378270195,
"ts": 1780999152930,
"id": "37060f723250d1ff74563031d0a33b5657a12998"
}
Each message has the following standard fields
- type: Type of the message (see the Type column in the table below)
- nt: Timestamp in nanoseconds
- ts: Timestamp (Unix) in milliseconds
- id: Id if the PowerGoblin instance
- sessionId: Session id if the message is associated with a session
Currently, the following status events are supported:
| Name | Type | Description |
|---|---|---|
InstanceStartedEvent |
instanceStarted |
The PowerGoblin instance was started. |
InstanceStoppedEvent |
instanceStopped |
The PowerGoblin instance was stopped. |
InstanceSessionStartedEvent |
instanceSessionStarted |
A new measurement session was started on this instance. |
InstanceSessionClosedEvent |
instanceSessionClosed |
A measurement session was closed on this instance. |
InstanceReconfigureEvent |
instanceReconfigure |
The PowerGoblin instance was reconfigured (discovery of new meters). |
InstanceBenchmarkEvent |
instanceBenchmark |
The PowerGoblin instance executed a benchmark. |
InstanceSessionEvent |
instanceSession |
A session related event was executed (measurement / run was started / stopped, meter set on / off). |
Subscribing to MQTT status events
First, check the hostname on the machine running PowerGoblin with:
$ hostname
MYHOST
Next, on any system in the same LAN, execute:
$ mosquitto_sub -h BROKER -t lab-MYHOST/#
Here, the BROKER is the IP / hostname of the machine running the MQTT broker / server (e.g. mosquitto).
You can stop the subscription by pressing Ctrl-C.

