Skip to content

Commit

Permalink
Merge pull request #8421 from Agoric/refactor-bootstrap-test-support
Browse files Browse the repository at this point in the history
Move benchmarkerator and some test support code out of the `boot` package
  • Loading branch information
mergify[bot] authored Nov 18, 2023
2 parents 0f42fb6 + 3f9f8ba commit 6d9ae67
Show file tree
Hide file tree
Showing 23 changed files with 407 additions and 401 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/test-all-packages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,9 @@ jobs:
- name: yarn test (assert)
if: (success() || failure())
run: cd packages/assert && yarn ${{ steps.vars.outputs.test }} | $TEST_COLLECT
- name: yarn test (benchmark)
if: (success() || failure())
run: cd packages/benchmark && yarn ${{ steps.vars.outputs.test }} | $TEST_COLLECT
- name: yarn test (deployment)
if: (success() || failure())
run: cd packages/deployment && yarn ${{ steps.vars.outputs.test }} | $TEST_COLLECT
Expand Down
1 change: 1 addition & 0 deletions packages/SwingSet/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"@endo/patterns": "^0.2.5",
"@endo/promise-kit": "^0.2.59",
"@endo/ses-ava": "^0.2.43",
"@endo/stream": "^0.3.28",
"@endo/zip": "^0.2.34",
"ansi-styles": "^6.2.1",
"anylogger": "^0.21.0",
Expand Down
156 changes: 156 additions & 0 deletions packages/SwingSet/tools/run-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/* eslint-disable @jessie.js/safe-await-separator */
import { Fail, q } from '@agoric/assert';
import { kunser } from '@agoric/kmarshal';
import { makeQueue } from '@endo/stream';
import type { E } from '@endo/eventual-send';

import type { SwingsetController } from '../src/controller/controller.js';

const sink = () => {};

export const makeRunUtils = (
controller: SwingsetController,
log = (..._) => {},
) => {
let cranksRun = 0;

const mutex = makeQueue();

mutex.put(controller.run());

const runThunk = async <T extends () => any>(
thunk: T,
): Promise<ReturnType<T>> => {
try {
// this promise for the last lock may fail
await mutex.get();
} catch {
// noop because the result will resolve for the previous runMethod return
}

const thunkResult = await thunk();

const result = controller.run().then(cranks => {
cranksRun += cranks;
log(`kernel ran ${cranks} cranks`);
return thunkResult;
});
mutex.put(result.then(sink, sink));
return result;
};

const queueAndRun = async (deliveryThunk, voidResult = false) => {
log('queueAndRun at', cranksRun);

const kpid = await runThunk(deliveryThunk);

if (voidResult) {
return undefined;
}
const status = controller.kpStatus(kpid);
switch (status) {
case 'fulfilled':
return kunser(controller.kpResolution(kpid));
case 'rejected':
throw kunser(controller.kpResolution(kpid));
case 'unresolved':
throw Fail`unsettled value for ${q(kpid)}`;
default:
throw Fail`unknown promise status ${q(kpid)} ${q(status)}`;
}
};

type EVProxy = typeof E & {
sendOnly: (presence: unknown) => Record<string, (...args: any) => void>;
vat: (name: string) => Record<string, (...args: any) => Promise<any>>;
};

// IMPORTANT WARNING TO USERS OF `EV`
//
// `EV` presents an abstraction that can be used (within tests only!) to get
// much of the convenience with respect to messaging that `E` provides in
// normal code. However, this convenience comes with a huge caveat that all
// users of this convenience feature MUST keep in mind.
//
// A test can drop messages onto the kernel's run queue using the
// `controller.queueToVatRoot` and `controller.queueToVatObject` methods.
// These are synchronous operations which merely place messages onto the run
// queue without causing execution. Execution, on the other hand, is
// initiated by calling `controller.run`, which will cause the kernel to begin
// delivering messages to vats from the run queue, continuing until the run
// queue is exhausted. HOWEVER, exhaustion of the run queue, which resolves
// the result promise returned by the `run` call, IS NOT coupled in any causal
// way to the resolution of result promises associated with the individual
// queued messages themselves. The status and resolution values of these
// promises can be synchronously queried (by kpid) via the
// `controller.kpStatus` and `controller.kpResolution` methods once `run` has
// completed. These queries are only available once the swingset has
// reqlinquished agency, i.e., when the work initiated by `controller.run` has
// finished. At that point, nothing is going on inside the kernel, and
// nothing WILL be going on inside the kernel until a subsequent call to
// `controller.run`, which in turn will only have an effect if additional
// messages have been placed on the kernel run queue in the meantime. You MAY
// NOT call `queueToVatRoot`, `queueToVatObject`, `kpStatus`, or
// `kpResolution` while run queue execution, triggered by a call to `run`, is
// in progress
//
// The functionality made available by `EV` looks similar to that provided by
// `E`, but it is very much not. When you send a message using `EV`, it
// places the message onto the kernel run queue and then immediately invokes
// `controller.run`. When the result of `run` resolves, the kpid returned by
// the message enqueueing operation is queried. If at that time the promise
// it identifies is resolved (or rejected), the value it was resolved (or
// rejected) to is used as the result from the `EV` invocation. However, if
// it is still pending at that time, `EV` will throw an exception, which will
// manifest as a rejection and your test will fail confusingly or abort. This
// means that if you initiate some operation via an `EV` message send, it must
// complete within a single `run` cycle for it to be of any use to you. This
// is quite different from a message sent using `E`, which will return a
// promise that can remain pending indefinitely, possibly to be settled by a
// future message delivery.

// @ts-expect-error cast, approximate
const EV: EVProxy = presence =>
new Proxy(harden({}), {
get: (_t, method, _rx) => {
const boundMethod = (...args) =>
queueAndRun(() =>
controller.queueToVatObject(presence, method, args),
);
return harden(boundMethod);
},
});
EV.vat = vatName =>
new Proxy(harden({}), {
get: (_t, method, _rx) => {
const boundMethod = (...args) =>
queueAndRun(() => controller.queueToVatRoot(vatName, method, args));
return harden(boundMethod);
},
});
// @ts-expect-error xxx
EV.sendOnly = presence =>
new Proxy(harden({}), {
get: (_t, method, _rx) => {
const boundMethod = (...args) =>
queueAndRun(
() => controller.queueToVatObject(presence, method, args),
true,
);
return harden(boundMethod);
},
});
// @ts-expect-error xxx
EV.get = presence =>
new Proxy(harden({}), {
get: (_t, pathElement, _rx) =>
queueAndRun(() =>
controller.queueToVatRoot('bootstrap', 'awaitVatObject', [
presence,
[pathElement],
]),
),
});
return harden({ runThunk, EV });
};
export type RunUtils = ReturnType<typeof makeRunUtils>;
1 change: 1 addition & 0 deletions packages/agoric-cli/src/sdk-package-names.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export default [
"@agoric/access-token",
"@agoric/assert",
"@agoric/base-zone",
"@agoric/benchmark",
"@agoric/boot",
"@agoric/builders",
"@agoric/cache",
Expand Down
Empty file added packages/benchmark/CHANGELOG.md
Empty file.
70 changes: 70 additions & 0 deletions packages/benchmark/benchmark/benchmark-vault-adjust.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { bench } from '../src/benchmarkerator.js';

// eslint-disable-next-line import/order
import { Offers } from '@agoric/inter-protocol/src/clientSupport.js';

const collateralBrandKey = 'ATOM';
const adjustOpenOfferId = 'adjust-open';

// The benchmark-defined option `size` (default 1) indicates how many operations
// will be performed per round. The difference between performing 10 rounds of
// 1 operation each (command line: `--rounds 10`) and performing 1 round of 10
// operations (command line: `-o size 10`) is that while both will perform 10
// operations, in the first case the 10 operations will be done sequentially
// while in the second case they will be done concurrently. These are two
// different modes you might want to measure. (Of course, you could do 10
// rounds of 10 operations each: `--rounds 10 -o size 10`, and that would work
// fine also.)

bench.addBenchmark('adjust vault balance', {
setup: async context => {
const { alice } = context.actors;

await alice.executeOfferMaker(Offers.vaults.OpenVault, {
offerId: adjustOpenOfferId,
collateralBrandKey,
wantMinted: 5.0,
giveCollateral: 9.0,
});
const upd = alice.getLatestUpdateRecord();
assert(
upd.updated === 'offerStatus' &&
upd.status.id === adjustOpenOfferId &&
upd.status.numWantsSatisfied === 1,
);
return undefined;
},

executeRound: async (context, round) => {
const { alice } = context.actors;

const adjustVault = async (i, n, r) => {
const offerId = `adjust-vault-${i}-of-${n}=round-${r}`;
await alice.executeOfferMaker(
Offers.vaults.AdjustBalances,
{
offerId,
collateralBrandKey,
giveMinted: 0.0005,
},
adjustOpenOfferId,
);
const upd = alice.getLatestUpdateRecord();
assert(
upd.updated === 'offerStatus' &&
upd.status.id === offerId &&
upd.status.numWantsSatisfied === 1,
);
};

const adjustN = async n => {
const range = [...Array(n)].map((_, i) => i + 1);
await Promise.all(range.map(i => adjustVault(i, n, round)));
};

const roundSize = context.options.size ? Number(context.options.size) : 1;
await adjustN(roundSize);
},
});

await bench.run();
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { bench } from './benchmarkerator.js';
import { bench } from '../src/benchmarkerator.js';

// eslint-disable-next-line import/order
import { Offers } from '@agoric/inter-protocol/src/clientSupport.js';

const collateralBrandKey = 'ATOM';

bench.addBenchmark('vault open', {
bench.addBenchmark('open vault', {
executeRound: async (context, round) => {
const { alice } = context.actors;

Expand Down Expand Up @@ -36,4 +36,4 @@ bench.addBenchmark('vault open', {
},
});

await bench.run('vaults');
await bench.run();
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,11 @@ yourself).[^2]
Those will themselves be deprecated and/or moved into `benchmarks`.
However, as of this writing that has not yet been done.

The first thing a benchmark should do is import The Benchmarkerator:[^3]
The first thing a benchmark should do is import The Benchmarkerator:
```
import { bench } from '@agoric/benchmark';
```

[^3]: As of this writing The Benchmarkerator actually lives in
`@agoric/boot/test/boostrapTests/benchmarkerator.js`. This should be
changed soon, hopefully before the first PR containing this writeup is
landed, in which case you will never see this footnote.

Note that this importation usually should be the very first thing you do, much
as you typically import `@agoric/swingset-vat/tools/prepare-test-env-ava.js` or
`@agoric/zoe/tools/prepare-test-env-ava.js` or the like as the first thing in a
Expand All @@ -69,10 +64,7 @@ labelled in Ava), and `benchmark` is an object describing the benchmark itself
After defining one or more benchmarks with `addBenchmark` you must then end by
invoking:

```await bench.run(name);```

where `name` is a string naming the benchmark run for purposes of error and
result reporting.
```await bench.run();```

### The `Benchmark` object

Expand Down Expand Up @@ -151,7 +143,8 @@ The supported command line options are:
| `-v`<br/>`--verbose` | Enable verbose output |
| `--vat-type TYPE` | Use the specified vat manager type rather than the default `xs-worker` |
| `-l`<br/>`--local` | Shorthand for `--vat-type local` (vats run in the same process as the kernel; less realistic than `xs-worker` but much faster and easier to debug) |
| `-d`<br/>`--dump` | Output JSON-formated benchmark data to a file |
| `-d PATH`<br/>`--dump PATH` | Output JSON-formated benchmark data into _PATH_ |
| `-s PATH`<br/>`--slot PATH` | Output a log file into _PATH_ |
| `-h`<br/>`--help` | Output this helpful usage information and then exit |

An optional `--` flag ends the options list. Any remaining command line
Expand All @@ -164,11 +157,13 @@ Timing results and other collected metrics are output to _stdout_. Two batches
of information are provided: one for the setup phase and one for the benchmark
rounds themselves (in aggregate).

In addition, if you specify the `--dump` command line option, a JSON-formatted
(i.e., machine readable) version of this same data will be output to the file
`benchmark-NAME.json` in the current working directory (where _NAME_ is the name
that you provided as the argument to `bench.run`).
If you specify the `--dump FILEPATH` command line option, a JSON-formatted
(i.e., machine readable) version of this same data will be output to the
indicated file.

Output results include execution times (according to Node's nanosecond clock),
crank counts, and the various kernel resource usage data reported by
`controller.getStats()`.

In addition, if you specify the `--slog FILEPATH` command line option, a
SwingSet slog file for the run will be output to the file indicated.
66 changes: 66 additions & 0 deletions packages/benchmark/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
{
"name": "@agoric/benchmark",
"version": "0.1.0",
"private": true,
"description": "Benchmark support",
"type": "module",
"main": "./src/benchmarkerator.js",
"exports": {
".": "./src/benchmarkerator.js"
},
"repository": "https://github.com/Agoric/agoric-sdk",
"scripts": {
"build": "exit 0",
"test": "exit 0",
"test:xs": "exit 0",
"lint-fix": "yarn lint:eslint --fix",
"lint": "run-s --continue-on-error lint:*",
"lint:types": "tsc",
"lint:eslint": "eslint ."
},
"keywords": [],
"author": "Agoric",
"license": "Apache-2.0",
"dependencies": {
"@agoric/assert": "^0.6.0",
"@agoric/boot": "^0.1.0",
"@agoric/cosmic-swingset": "^0.41.3",
"@agoric/internal": "^0.3.2",
"@agoric/inter-protocol": "^0.16.1",
"@agoric/vats": "^0.15.1",
"@agoric/zoe": "^0.26.2",
"@endo/init": "^0.5.59"
},
"devDependencies": {},
"files": [
"CHANGELOG.md",
"src/",
"scripts/",
"tools/",
"*.json",
"globals.d.ts",
"exported.js"
],
"engines": {
"node": ">=14.15.0"
},
"ava": {
"extensions": {
"js": true,
"ts": "module"
},
"files": [
"test/**/test-*.js",
"test/**/test-*.ts"
],
"nodeArguments": [
"--loader=tsx",
"--no-warnings"
],
"require": [
"@endo/init/debug.js"
],
"timeout": "20m",
"workerThreads": false
}
}
Loading

0 comments on commit 6d9ae67

Please sign in to comment.