THIS VERSION OF THE API (v1) IS OUTDATED AND DOES NOT SUPPORT CONCURRENT SESSIONS!
**You can find the most recent version of the HTTP API here.
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 tunnelled 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, Java, JavaScript).
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 sh, curl/wget & coreutils
# GET requests using wget
msg_get() {
wget -qO- "http://$HOST/$*"
}
# GET requests using curl
msg_get() {
curl "http://$HOST/$*"
}
# POST requests using wget (stdin stored to a file)
msg_post() {
FILE="$(mktemp)"
cat > "$FILE"
wget -qO- --post-file "$FILE" "http://$HOST/$*"
rm "$FILE"
}
# POST requests using curl
msg_post() {
curl -d@- "http://$HOST/$*"
}
# POST request using wget (send a file)
msg_post_file() {
wget -qO- --post-file "$1" http://$HOST/$2
}
# POST request using curl (send a file)
msg_post_file() {
curl --data-binary "@$1" http://$HOST/$2
}
# --- Examples ---
# PowerGoblin server
export HOST=localhost:8080
# GET examples
msg_get api/v1/session/meters
msg_get api/v1/measurement/start/TestScenario1
# POST examples
export DATA='{ "triggerType":"Run", "unit":"SUT", "message":"foo", "type":"trigger" }'
echo $DATA | msg_post api/v1/trigger
echo foobar | msg_post api/v1/session/rename
msg_post_file collectd.zip api/v1/import/collectd
# Requires Python 3+, python-requests, python-simplejson
import requests
import json
class GoblinClient:
def __init__(self, host):
self.host = "http://" + host
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"]
# --- Examples ---
# PowerGoblin server
c = GoblinClient("localhost:8080")
# GET examples
meters = c.get("api/v1/session/meters")
print(c.interpret(meters))
meters = c.get("api/v1/measurement/start/TestScenario1")
# POST examples
data = { "triggerType":"Run", "unit":"SUT", "message":"foo", "type":"trigger" }
c.post_json("api/v1/trigger", json = data)
c.post_text("api/v1/session/rename", text = "foobar")
c.post_file("api/v1/import/collectd", "collectd.zip")
// Requires Java 21+
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));
}
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)));
}
}
// --- Examples ---
void main() {
var c = new GoblinClient("localhost:8080");
// GET examples
c.get("api/v1/session/meters");
c.get("api/v1/measurement/start/TestScenario1");
// POST examples
var data = "{ \"triggerType\":\"Run\", \"unit\":\"SUT\", \"message\":\"foo\", \"type\":\"trigger\" }";
c.post("api/v1/trigger", data);
c.post("api/v1/session/rename", "foobar");
c.post("api/v1/import/collectd", Path.of("collectd.zip"));
}
const request = require('request');
const host = "localhost:8080";
function get(api) {
request(
"http://" + host + "/" + api,
(e, r, body) => {
if (!e && r.statusCode == 200)
console.log(body);
}
);
}
function post_json(api, data) {
request.post(
{
url: "http://" + host + "/" + api,
json: data,
},
(e, r, body) => {
if (!e && r.statusCode == 200)
console.log(body)
}
);
}
// GET examples
get("api/v1/session/meters");
get("api/v1/measurement/start/TestScenario1");
// POST examples
var data = { "triggerType" : "Run", "unit" : "SUT", "message": "foo", "type": "trigger" };
post_json("api/v1/trigger", data);
post_json("api/v1/session/rename", "foobar");
// Requires Java 21+
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")))
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)))
}
// --- Examples ---
var c = GoblinClient("localhost:8080")
// GET examples
c.get("api/v1/session/meters")
c.get("api/v1/measurement/start/TestScenario1")
// POST examples
var data = "{ \"triggerType\":\"Run\", \"unit\":\"SUT\", \"message\":\"foo\", \"type\":\"trigger\" }"
c.post("api/v1/trigger", data)
c.post("api/v1/session/rename", "foobar")
c.post("api/v1/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
The following control commands encode the command's name and possible parameters as part of the HTTP POST query. The following table describes the meaning of the URLs:
| Command | Type | Description |
|---|---|---|
api/v1/session |
GET Query | Return a hierarchical structure of the session state |
api/v1/session |
GET Query | Return a hierarchical structure of the session state |
api/v1/session/meters |
GET Query | Return the list of active meters in the session |
api/v1/session/store |
GET Cmd | Store the session data |
api/v1/session/close |
GET Cmd | Close the session (also stores the data) |
api/v1/session/reset |
GET Cmd | Reset the session data (dangerous, deletes everything) |
api/v1/session/reconfigure |
GET Cmd | Add/remove meters after a hot plug event. |
api/v1/session/rename/:name |
GET Event | Rename the session to 'name' |
api/v1/session/sync/:timestamp |
GET Event | Synchronize the session clocks between the SUT and PowerGoblin instance (*). |
api/v1/measurement |
GET Query | Return the status of the current measurement |
api/v1/measurement/rename/:name |
GET Event | Rename the measurement to 'name' |
api/v1/measurement/start/:unit |
GET Event | Start a new measurement, initiated by 'unit' |
api/v1/measurement/stop/:unit |
GET Event | Stop the measurement, initiated by 'unit' |
api/v1/run |
GET Query | Return the status of the current run |
api/v1/run/start/:unit |
GET Event | Start a new run, initiated by 'unit' |
api/v1/run/stop/:unit |
GET Event | Stop the run, initiated by 'unit' |
api/v1/meter |
GET Query | Return the status of all the meters in the session |
api/v1/meter/:id |
GET Query | Return the status of the 'id' |
api/v1/meter/rename/:id/:name |
GET Event | Rename the meter 'id' to 'name' |
api/v1/trigger/:name/:unit/:msg |
GET Event | Create a new trigger event name, unit unit, msg msg |
api/v1/cmd/exit |
GET Cmd | Shut down the application. |
api/v1/cmd/deleteLogs |
GET Cmd | Delete all logs in the log directory. |
api/v1/cmd/deletePlots |
GET Cmd | Delete all plots in the plot directory. |
api/v1/benchmark/http |
GET Cmd | Start a HTTP benchmark. The perf result can be read from the logs. |
api/v1/benchmark/telnet |
GET Cmd | Start a Telnet benchmark. The perf result can be read from the logs. |
api/v1/session |
POST Query | Return a hierarchical structure of the session state |
api/v1/session/meters |
POST Query | Return the list of active meters in the session |
api/v1/session/store |
POST Cmd | Store the session data |
api/v1/session/close |
POST Cmd | Close the session (also stores the data) |
api/v1/session/reset |
POST Cmd | Reset the session data (dangerous, deletes everything) |
api/v1/session/reconfigure |
POST Cmd | Add/remove meters after a hot plug event. |
api/v1/session/rename |
POST Event | Rename the session. Body of the request = name. |
api/v1/session/sync |
POST Event | Synchronize the session clocks. Body of the request = timestamp |
api/v1/measurement |
POST Query | Return the status of the current measurement |
api/v1/measurement/rename |
POST Event | Rename the measurement. Body of the request = name. |
api/v1/measurement/start |
POST Event | Start a new measurement. Body of the request = initiating unit. |
api/v1/measurement/stop |
POST Event | Stop the measurement. Body of the request = initiating unit. |
api/v1/run |
POST Query | Return the status of the current run |
api/v1/run/start |
POST Event | Start a new run. Body of the request = initiating unit. |
api/v1/run/stop |
POST Event | Stop the run. Body of the request = initiating unit. |
api/v1/meter |
POST Query | Return the status of all the meters in the session |
api/v1/meter/:id |
POST Query | Return the status of the 'id' |
api/v1/meter/rename/:id |
POST Event | Rename the meter 'id'. Body of the request = name. |
api/v1/trigger |
POST Event | Create a new trigger. Body of the request = serialized event. |
api/v1/cmd |
POST Cmd | Execute a command. Body of the request = serialized command. |
api/v1/import/collectd |
POST Event | Send |
api/v1/benchmark/http |
POST Cmd | Start a HTTP benchmark. The perf result can be read from the logs. |
api/v1/benchmark/telnet |
POST Cmd | Start a Telnet benchmark. The perf result can be read from the logs. |
(*) 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.
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 is currently limited to Linux as it uses the io_uring backend for even higher performance and lower latency.
Control commands
The following examples demonstrate the use of the PowerGoblin Telnet API in common programming languages (Shell, Python, Java, JavaScript).
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 sh, coreutils & gnu-netcat or busybox
msg() {
echo $* | nc $HOST $PORT
}
# --- Examples ---
# PowerGoblin server
export HOST=localhost
export PORT=9000
msg SESSION METERS
msg MEASUREMENT START starting
msg SESSION SYNC $(date +%s%N)
msg TRIGGER Run,SUT,start
msg SESSION RENAME foobar
# Requires Python 3+
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("localhost", 9000)
c.send("SESSION METERS")
c.send("MEASUREMENT START starting")
c.send("SESSION SYNC " + milli_time())
c.send("TRIGGER Run,SUT,start")
c.send("SESSION RENAME foobar")
// requires Java 21+
import java.net.*;
import java.io.*;
public record GoblinClient(String host, int port) {
void send(String msg) throws Exception {
try (
var s = new Socket(host, port);
var w = new PrintWriter(s.getOutputStream())
) {
w.println(msg);
}
}
}
// --- Examples ---
void main() {
var c = new GoblinClient("localhost", 9000);
c.send("SESSION METERS");
c.send("MEASUREMENT START starting");
c.send("SESSION SYNC " + System.currentTimeMillis());
c.send("TRIGGER Run,SUT,start");
c.send("SESSION RENAME foobar");
}
var net = require('net');
const host = "127.0.0.1";
const port = 9000;
function 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();
});
}
send("SESSION METERS");
send("MEASUREMENT START starting");
send("SESSION SYNC " + System.currentTimeMillis());
send("TRIGGER Run,SUT,start");
send("SESSION RENAME foobar");
// requires Java 21+
import java.net.*
import java.io.*
class GoblinClient(val host: String, val port: int) {
fun send(msg: String) {
Socket(host, port).use {
PrintWriter(it.outputStream).use {
it.println(msg)
}
}
}
// --- Examples ---
var c = GoblinClient("localhost", 9000)
c.send("SESSION METERS")
c.send("MEASUREMENT START starting")
c.send("SESSION SYNC " + System.currentTimeMillis())
c.send("TRIGGER Run,SUT,start")
c.send("SESSION RENAME foobar")
The shell scripts assume that the environment variable $HOST points to the host and 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 comma separated. The following table describes the meaning of the commands:
| Command | Type | Description |
|---|---|---|
| SESSION | Query | Return a hierarchical structure of the session state |
| SESSION METERS | Query | Return the list of active meters in the session |
| SESSION STORE | Cmd | Store the session data |
| SESSION CLOSE | Cmd | Close the session (also stores the data) |
| SESSION RESET | Cmd | Reset the session data (dangerous, deletes everything) |
| SESSION RECONFIGURE | Cmd | Add/remove meters after a hot plug event. |
| SESSION RENAME :name | Event | Rename the session to 'name' |
| SESSION SYNC :timestamp | Event | Synchronize the session clocks between the SUT and PowerGoblin instance (*). |
| MEASUREMENT | Query | Return the status of the current measurement |
| MEASUREMENT RENAME :name | Event | Rename the measurement to 'name' |
| MEASUREMENT START :unit | Event | Start a new measurement, initiated by 'unit' |
| MEASUREMENT STOP :unit | Event | Stop the measurement, initiated by 'unit' |
| RUN | Query | Return the status of the current run |
| RUN START :unit | Event | Start a new run, initiated by 'unit' |
| RUN STOP :unit | Event | Stop the run, initiated by 'unit' |
| METER | Query | Return the status of all the meters in the session |
| METER :id | Query | Return the status of the 'id' |
| METER RENAME :id,:name | Event | Rename the meter 'id' to 'name' |
| TRIGGER :name,:unit,:msg | Event | Create a new trigger event name, unit unit, msg msg |
| CMD exit | Cmd | Shut down the application. |
| CMD deleteLogs | Cmd | Delete all logs in the log directory. |
| CMD deletePlots | Cmd | Delete all plots in the plot directory. |
| BENCH HTTP | Cmd | Start a HTTP benchmark. The perf result can be read from the logs. |
| BENCH TELNET | Cmd | Start a Telnet benchmark. The perf result can be read from the logs. |
(*) 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.


