Commit 705349cf authored by Benjamin Fischer's avatar Benjamin Fischer

[test/bench] finalized testing/benchmarking script

parent 2c3ded56
...@@ -14,11 +14,17 @@ const rdx = (req, { qs = {}, ...opts } = {}) => { ...@@ -14,11 +14,17 @@ const rdx = (req, { qs = {}, ...opts } = {}) => {
return ret; return ret;
}; };
const sleep = delay => new Promise(done => setTimeout(done, delay)); const sleep = delay => new Promise(done => setTimeout(done, delay));
const dirOp = (op, a, b) => { const b2h = (b, { t = 800, s = 1024, u = "B" } = {}) => {
for (let i of " kMGTE")
if (b < t) return b.toPrecision(3) + i + u;
else b /= s;
};
const dirOp = op => (a, b) => {
const c = {}; const c = {};
for (let key of a) c[key] = op(a[key], b[key], key); for (let key in a) c[key] = op(a[key], b[key], key);
return c; return c;
} };
const dirSum = dirOp((a, b) => (a || 0) + (b || 0));
const pageFiles = {}; const pageFiles = {};
for (const key of ["login", "index", "editor"]) for (const key of ["login", "index", "editor"])
...@@ -84,7 +90,7 @@ class CodeEditor extends View { ...@@ -84,7 +90,7 @@ class CodeEditor extends View {
await new Promise(done => this.once("done", done)); await new Promise(done => this.once("done", done));
} }
async run({ payload, iter = 0, output, delay = 20e3, cmd }) { async run({ payload, iter = 0, output, delay, cmd }) {
if (output) output = `${dirname(this.path)}/${output}`; if (output) output = `${dirname(this.path)}/${output}`;
await this.list(); await this.list();
if (payload) await this.save(payload); if (payload) await this.save(payload);
...@@ -96,52 +102,80 @@ class CodeEditor extends View { ...@@ -96,52 +102,80 @@ class CodeEditor extends View {
await this.user.thumb(output); await this.user.thumb(output);
await this.user.load(output); await this.user.load(output);
} }
await sleep(delay); await sleep(delay());
} }
} }
} }
class Stats {
fill(other) {
for (let key in other) this.count(key, other[key] || 0);
}
count(key, num = 1) {
this[key] = (this[key] || 0) + (num || 0);
}
}
class User extends EventEmitter { class User extends EventEmitter {
constructor(username, { agentOptions, baseUrl } = {}) { constructor(username, { agentOptions, baseUrl, stats = new Stats() } = {}) {
super(); super();
this.stats = stats;
this.req = rdx(request, { this.req = rdx(request, {
baseUrl, baseUrl,
jar: request.jar(), jar: request.jar(),
time: true,
transform: (body, res) => { transform: (body, res) => {
this.reqs++; stats.fill({
this.recv += body ? body.length : 0; reqs: 1,
recv: body ? body.length : 0
});
if (!res.request.uri.href.includes("poll"))
stats.fill(res.timingPhases);
let status = res.statusCode; let status = res.statusCode;
if ((res.headers["content-type"] || "").includes("json")) { if ((res.headers["content-type"] || "").includes("json")) {
const json = JSON.parse(body); const json = JSON.parse(body);
status = json.code; status = json.code;
body = json.data; body = json.data;
} }
if (status != 200) this.srv =
console.error( (res.headers["x-app-server"] || "").replace(/.+\//, "") || this.srv;
"ERR[%d] URL=%s req.body=%o len(res)=%d res=%o", const lg = msg => [
status, "%s[%d@%s/%s] URL=%s time=%d req.body=%o len(res)=%d res=%o",
res.request.uri.href, msg,
res.request.body, status,
res.body && res.body.length, this.srv,
res.body this.hn,
res.request.uri.href,
res.timingPhases.total,
res.request.body,
res.body && res.body.length,
res.headers["content-type"].includes("text") ? res.body : null
];
if (status != 200) {
console.error(...lg("ERR"));
throw new Error(
`failed[${status}@${this.srv}/${this.hn}] ${lg().slice(2)}`
); );
// console.error("failed: URL=%s body=%o", res.request.uri.href, res.request.body); } else if (res.timingPhases.total > 25e3) console.log(...lg("SLO"));
// throw new Error( // else console.log(...lg("RES"));
// `failed: URL=${res.request.uri.href} body=${res.request.body}`
// );
return body; return body;
}, },
agentOptions: { agentOptions: {
maxSockets: 6, maxSockets: 6,
keepAlive: true, keepAlive: true,
keepAliveMsecs: 10, maxFreeSockets: 1,
...agentOptions ...agentOptions
} },
pool: {}
}); });
this.username = username;
this.bust = `${username}-${rando(16)}`; this.bust = `${username}-${rando(16)}`;
this.polls = 0; this.srv = "";
this.reqs = 0; this.hn = "";
this.recv = 0; this.on("data", ({ topic, data: { workspaceId } = {} }) => {
// console.log("data: %o", data);
if (topic.startsWith("workspace.")) this.emit(`${topic}:${workspaceId}`);
});
} }
async static(key) { async static(key) {
await Promise.all( await Promise.all(
...@@ -149,42 +183,54 @@ class User extends EventEmitter { ...@@ -149,42 +183,54 @@ class User extends EventEmitter {
); );
} }
async login(username, password) { async login(password, quick) {
await this.req.get("/login"); if (!quick) {
await this.static("login"); await this.req.get("/login");
await this.req.post("/ajax/login", { form: { username, password } }); await this.static("login");
this.req.qs._windowId = rando(36);
}
get poll() {
return !!this._pollStop;
}
set poll(val) {
val = !!val;
if (val == this.poll) return;
if (val) this._poll();
else {
const stop = this._pollStop;
delete this._pollStop;
stop();
} }
await this.req.post("/ajax/login", {
form: { username: this.username, password }
});
this.req.qs._windowId = rando(36);
} }
async _poll() { poll() {
let active = true; let active = true;
this._pollStop = () => { const prom = (async () => {
while (active) {
const res = await this.req.get("/bus/poll");
this.stats.count("poll");
if (res) for (const data of res) this.emit("data", JSON.parse(data));
}
})();
this.pollStop = prom.stop = async () => {
active = false; active = false;
await prom;
delete this.poll;
delete this.pollStop;
}; };
while (active) { this.poll = () => prom;
const res = await this.req.get("/bus/poll"); return prom;
this.polls++;
if (res) for (const data of res) this.emit("data", JSON.parse(data));
}
} }
pollStop() {}
async workspace(wid, password) { async workspace(wid, password) {
await this.req.post("/ajax/connectworkspace", { form: { wid, password } }); const wsInfo = (await this.req.get("/ajax/getworkspacedata"))[wid];
if (!wsInfo) throw new Error(`no such workspace: ${wid}`);
if (wsInfo.connection_status !== "connected") {
await this.req.post("/ajax/connectworkspace", {
form: { wid, password }
});
this.poll();
await new Promise(done => this.once(`workspace.connected:${wid}`, done));
}
this.req.qs._workspaceId = wid; this.req.qs._workspaceId = wid;
this.hn = (await this.load("/etc/hostname")).trim();
process.stdout.write(
`connected: ${this.username.padEnd(17)} ${this.srv.padEnd(
8
)} ${this.hn.padEnd(11)}`.padEnd(79) + "\n"
);
} }
async thumb(path, height = 100, width = height) { async thumb(path, height = 100, width = height) {
...@@ -194,7 +240,7 @@ class User extends EventEmitter { ...@@ -194,7 +240,7 @@ class User extends EventEmitter {
} }
async load(path) { async load(path) {
await this.req.get("/fs/getfile", { return await this.req.get("/fs/getfile", {
qs: { path, _mtime: Date.now() } qs: { path, _mtime: Date.now() }
}); });
} }
...@@ -204,35 +250,155 @@ class User extends EventEmitter { ...@@ -204,35 +250,155 @@ class User extends EventEmitter {
} }
async logout() { async logout() {
this.poll = false; const wid = this.req.qs._workspaceId;
if (wid !== undefined) {
await this.req.post("/ajax/disconnectworkspace", {
form: { wid }
});
delete this.req.qs._workspaceId;
}
await this.pollStop();
await this.req.get("logout"); await this.req.get("logout");
delete this.req.qs._windowId; delete this.req.qs._windowId;
} }
async runEdit(password, { wsId, path, ...opts }) {
await this.login(password);
await this.req.get("");
await this.static("index");
// this.poll();
await this.workspace(wsId, password);
await this.editor(path).run(opts);
await this.logout();
return this;
}
async runStorm(password, { wsId, ...opts } = {}) {
await this.login(password, true);
await this.workspace(wsId, password);
await this.pollStop();
await this.storm(opts);
await this.logout();
return this;
}
async storm({ iter = 1e4, conc = 5, delay } = {}) {
const edit = this.editor("$HOME/test.py");
const q = new Set();
for (let i = iter; i--; ) {
while (q.size < conc) {
const prom = edit.list();
prom.finally(() => q.delete(prom));
q.add(prom);
}
await Promise.race(Array.from(q));
if (delay) await sleep(delay());
}
await Promise.all(Array.from(q));
}
} }
// actual test const loop = async ({
const payload = readFileSync("payload.py"); conc = 10,
delay = () => 10e3 * (1 + 2 * Math.random()),
const run = async (username, password) => { total = 10000,
const user = new User(username, { genUsername = () => "testuser",
baseUrl: "http://localhost:4282/test-server/" password,
// baseUrl: "https://vispa.physik.rwth-aachen.de/server/" statusPassword,
}); wsId,
await user.login(username, password); baseUrl,
await user.req.get(""); storm,
// await user.static("index"); edit
// user.on("data", data => console.log("data: %o", data)); } = {}) => {
user.poll = true; if (!password) throw new Error(`"password" is missing`);
const base = "$HOME/test"; if (!baseUrl) throw new Error(`"baseUrl" is missing`);
await user.workspace("1", password); if (!wsId) throw new Error(`"wsId" is missing`);
await user.editor(`${base}/test.py`).run({ if (!wsId) throw new Error(`"wsId" is missing`);
payload, const users = new Set();
iter: 0, const stats = new Stats();
delay: 2e3, let bad = 0;
output: "test.png" for (let stop, i = 0, d = 0; i < total || users.size; ) {
}); const username = genUsername(i);
await user.logout(); if (
return user; i < total &&
!stop &&
users.size < conc &&
d < Date.now() &&
!Array.from(users).some(user => user.username === username)
) {
d = Date.now() + delay();
i++;
const user = new User(username, {
baseUrl,
stats
});
const prom = user[storm ? "runStorm" : "runEdit"](password, {
wsId,
...(storm || edit)
})
.catch(async err => {
console.error("[%d] failed: %s", i, err);
bad++;
if (statusPassword) {
const status = await user.req.post("status", {
form: { password: statusPassword }
});
await new Promise((done, fail) =>
writeFile(`bad/${i}.html`, status, err =>
err ? fail(err) : done()
)
);
}
})
.finally(() => users.delete(user));
users.add(user);
}
process.stdout.write(
`users=${users.size}/${conc}@${i}/${total} polls=${stats.poll} reqs=${
stats.reqs
} recv=${b2h(stats.recv)} firstByte=${(
stats.firstByte / stats.reqs
).toFixed(0)}ms bad=${bad}`.padEnd(79) + "\r"
);
if (stop && !users.size) break;
await sleep(100);
switch (process.stdin.read(1) + "") {
case "n":
d = 0;
break;
case "+":
conc++;
break;
case "-":
conc && conc--;
break;
case "0":
conc = 0;
break;
case "q":
stop = true;
break;
}
}
process.stdout.write("\n");
process.stdin.pause();
}; };
run("testuser", "testuser").catch(err => console.error(err)); loop({
delay: () => 15 * (1 + Math.random()),
conc: 15,
total: 500,
// storm: { conc: 1, iter: 1 },
// storm: { conc: 5 },
edit: {
path: "$HOME/test.py",
payload: readFileSync("payload.py"),
iter: 50,
delay: () => 1e3 * (1 + 15 * Math.random()),
output: "test.png"
}
});
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment