Skip to content

Commit

Permalink
add load analysis to ΔQ tool (#55)
Browse files Browse the repository at this point in the history
* [delta_q] get tests to pass again

* [delta_q] fix outcome combinators

* [delta_q] make computation with loads do something

* test distributive choice and fix small things

* [delta_q] make seq right-associative

* [delta_q] remove unnecessary parentheses

* [delta_q] test load factor handling

* [delta_q] update README
  • Loading branch information
rkuhn authored Oct 28, 2024
1 parent dd04e27 commit 563f370
Show file tree
Hide file tree
Showing 15 changed files with 868 additions and 264 deletions.
22 changes: 3 additions & 19 deletions delta_q/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion delta_q/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ name = "editor-web"

[dependencies]
gloo-utils = { version = "0.2.0" }
iter_tools = "0.21.0"
itertools = "0.13.0"
js-sys = { version = "0.3.70" }
serde = { version = "1.0.210", features = ["derive"] }
serde_json = { version = "1.0.128" }
Expand Down
55 changes: 19 additions & 36 deletions delta_q/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,57 +6,40 @@ The goal of this project is to provide tooling that is easy to use for designers
This should be supported by a web UI that allows modelling CDFs (cumulative distribution function), named outcome expressions, and constraints/expectations.
The underlying ΔQ expressions shall be used for export/import of the model, complemented with a library for CDF constructors (like step function etc.).

## Implementation plan
## Extensions to the theory

The first step is to provide a library for manipulating CDFs, offering all operations required by the theory; the internal representation will be discrete numerical [DONE], with later optimizations for constant parts at the beginning and end of the vector. [DONE]
One shortcoming of the published theory is that it doesn't make it obvious how to model not only timeliness but also resource usage a.k.a. perform a load analysis.
To this end, this tool contains an extension of the syntax in two ways:

The second step yields an internal DSL for creating ΔQ expressions and printing them. [DONE]
1. the basic outcome constructor `CDF[...]` accepts a postfix `WITH <metric>[...]` that may be supplied any number of times to register some step functions for resource usage of performing this outcome
2. the sequence operator `->-` accepts a load factor update of the form `->-×X+Y` which means that the right-hand side will have its load metrics scaled up by a factor that is obtained by taking the factor currently in effect, multiplying it by X and then adding Y (both components are optional)

The third step provides evaluation of ΔQ expressions as defined in the paper. [DONE]
This will later be expanded to include some exponentiation-like operator that simplifies expressing a randomly chosen repetition count for some sub-expression (as frequently occurs in gossip protocols). [DONE]
The rules for evaluating the load metrics of outcomes are as follows:

The fourth step adds a web UI to expose the internal DSL to no-code users. [DONE]
The interaction with a ΔQ expression shall closely resemble the refinement approach for system modelling as defined in the paper. [DONE]
It will allow the system designer to see immediately the result of the current model [DONE] and how its computed attenuation compares to the expectation or constraints.
- `a ->- b` will have the load metrics of `a` plus the load metrics of `b` convoluted with the timing CDF of `a` (i.e. the resources are used in a delayed fashion as described by the gradual completion of `a`)
- `a L<>R b` is the choice of `a` with weight `L` or `b` with weight `R`; the load metrics of `a` and `b` are thus averaged using the same weights
- `∀(a | b)` performs both `a` and `b`, therefore the load metrics are added
- `∃(a | b)` also performs both `a` and `b` and thus also sums up their load metrics

In addition and in parallel to the above, the theory shall be better understood and where required enhanced to support not only timeliness analysis but also load prediction.
It is expected that while the same system model can be used for both aspects, the inputs for the load analysis need to be somewhat different from CDFs, i.e. they will likely require more information.
If you want to model that `b` can only start after `a` has been done, you should model it as `∀(a | CDF[(<delay>,1)]) ->- b` where `delay` is the duration after which `a` is considered finished.

## Caveats

Since the timing CDFs don't model load dependence, they are only representative of the unloaded system.
The results of the load analysis will indicate where and under which conditions this assumption will be broken, but it isn't obvious how to feed that information back into a changed CDF to adapt the timeliness analysis to those circumstances.
> Note that in the syntax `->-` binds more closely than `L<>R` and both operators are **right-associative**.
> This is for convenience in modelling choice ladders and reading them from left to right, as well as managing load factors when reading left to right. `(a ->-+1 b) ->- c` only uses the increased load factor for `b` while `a ->-+1 b ->- c` also applies it to `c`.
## Building and Running

The build comprises two steps:

- `trunk build` (i.e. you’ll need to `cargo install --locked trunk` first)
- `cargo run --bin editor -F main`

The first one uses [trunk](https://trunkrs.dev) to build the web app in the `dist/` folder, which the second one then integrates into the single-binary application that will serve HTTP resources on port 8080 when run.
The example application is a pure web application that runs in the browser and is built and served using `trunk`.
You'll need to install the WASM toolchain as well:

When developing the web UI part you can leave `cargo run --bin editor` running while using `trunk serve` to serve the UI with change detection.
Requests to the `delta_q/*` endpoints will be proxied.

### Troubleshooting

Depending on local Rust configuration, building the web app might be less straightforward.
```sh
rustup target add wasm32-unknown-unknown

Trunk needs the Wasm bindings generators but they are not installed by default at least on MacOS M1:
cargo install --locked trunk

```
# it has been reported that the following might be necessary as well
cargo install --locked wasm-bindgen-cli
```

It also needs a Wasm toolchain:

```
rustup target add wasm32-unknown-unknown
```

## Known Shortcomings

- functional but ugly
- duplicates state management in web app and backend, not yet decided what to put where (currently ΔQ expression evaluation is done in the backend, could easily move to a web worker)
- should have export (probably as JSON) and matching import
41 changes: 40 additions & 1 deletion delta_q/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,15 @@
const { data } = event;
if (data === null || typeof data !== 'object') {
cleanup('output');
const load_container = document.getElementById('loads');
while (load_container.firstChild) {
load_container.removeChild(load_container.firstChild);
}
} else {
const { bins, values, max, name } = data;
const { bins, values, max, name, loads } = data;

const hist = core.createHistogram('TH1F', bins.length);
window.hist = hist;
hist.fXaxis.fXbins = bins;
for (const idx in values) {
hist.setBinContent(parseInt(idx) + 1, values[idx]);
Expand All @@ -32,6 +38,39 @@
hist.fYaxis.fTitle = 'cumulative distribution';
cleanup('output');
draw.draw('output', hist, 'nostat minimum:0 maximum:1.1');

const load_container = document.getElementById('loads');
while (load_container.firstChild) {
load_container.removeChild(load_container.firstChild);
}
for (const idx in loads || []) {
const id = 'loads' + idx;
const pad = load_container.appendChild(document.createElement('div'));
pad.id = id;
pad.setAttribute('style', `width: 100%; height: ${100 / loads.length}%;`);
}

let canvas = 0;
for (const { bins, values, name } of loads || []) {
const load_hist = core.createHistogram('TH1F', bins.length);
load_hist.fXaxis.fXbins = bins;
for (const idx in values) {
load_hist.setBinContent(parseInt(idx) + 1, values[idx]);
}
load_hist.fXaxis.fXmin = 0;
load_hist.fXaxis.fXmax = max;
// ROOT scales the labels as a fraction of the canvas height → counter that effect
load_hist.fXaxis.fLabelSize *= loads.length;
load_hist.fXaxis.fLabelOffset *= loads.length;
load_hist.fYaxis.fLabelSize *= loads.length;
load_hist.fYaxis.fLabelOffset *= loads.length;
load_hist.fTitle = name;
const pad = 'loads' + canvas;
window[pad] = load_hist;
cleanup(pad);
draw.draw(pad, load_hist, 'nostat');
canvas++;
}
}
});
</script>
Expand Down
24 changes: 24 additions & 0 deletions delta_q/models.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ CDF[(1, 1)]
validateIB:
CDF[(3, 1)]

=================================================================
4 -- better modelling of Leios (in format for import into editor)
=================================================================

-- diffusion of one IB (assuming 1MB)
diffuseIB := hopIB 0.6<>99.4 ((hopIB ->- hopIB) 8.58<>90.82 (((hopIB ->- hopIB) ->- hopIB) 65.86<>24.96 (((hopIB ->- hopIB) ->- hopIB) ->- hopIB)))
Expand All @@ -64,3 +66,25 @@ nearXL := CDF[(0.078, 1)]
-- a hop (for an IB or EB) is a choice of near/mid/far with req-res-req-resLarge in each arm
hopIB := (((near ->- near) ->- near) ->- nearXL) 1<>2 ((((mid ->- mid) ->- mid) ->- midXL) 1<>1 (((far ->- far) ->- far) ->- farXL))
hopEB := (((near ->- near) ->- near) ->- nearL) 1<>2 ((((mid ->- mid) ->- mid) ->- midL) 1<>1 (((far ->- far) ->- far) ->- farL))

=======================================
5 -- Leios model including network load
=======================================
(this isn’t yet a model of the actual Leios, just playing around with the necessary primitives)

TOP := CDF[(0, 1)]
diffuseEB := TOP ->-×15 hopEB 0.6<>99.4 TOP ->-×15 hopEB ->-×15 hopEB 8.58<>90.82 TOP ->-×15 hopEB ->-×15 hopEB ->-×15 hopEB 65.86<>24.96 TOP ->-×15 hopEB ->-×15 hopEB ->-×15 hopEB ->-×15 hopEB
diffuseIB := TOP ->-×15 hopIB 0.6<>99.4 TOP ->-×15 hopIB ->-×15 hopIB 8.58<>90.82 TOP ->-×15 hopIB ->-×15 hopIB ->-×15 hopIB 65.86<>24.96 TOP ->-×15 hopIB ->-×15 hopIB ->-×15 hopIB ->-×15 hopIB
far := CDF[(0.268, 1)] WITH net[(0, 5597), (0.268, 0)]
farL := CDF[(0.531, 1)] WITH net[(0, 120527), (0.531, 0)]
farXL := CDF[(1.598, 1)] WITH net[(0, 625782), (1.598, 0)]
hopEB := ((near ->- near) ->- near) ->- nearL 1<>2 ((mid ->- mid) ->- mid) ->- midL 1<>1 ((far ->- far) ->- far) ->- farL
hopIB := ((near ->- near) ->- near) ->- nearXL 1<>2 ((mid ->- mid) ->- mid) ->- midXL 1<>1 ((far ->- far) ->- far) ->- farXL
mid := CDF[(0.069, 1)] WITH net[(0, 21739), (0.069, 0)]
midL := CDF[(0.143, 1)] WITH net[(0, 447552), (0.143, 0)]
midXL := CDF[(0.404, 1)] WITH net[(0, 2475247), (0.404, 0)]
near := CDF[(0.012, 1)] WITH netNear[(0, 125000), (0.012, 0)]
nearL := CDF[(0.024, 1)] WITH netNear[(0, 2666667), (0.024, 0)]
nearXL := CDF[(0.078, 1)] WITH netNear[(0, 12820513), (0.078, 0)]
round := CDF[(20, 1)]
someRounds := ∀(round | TOP ->-×5 diffuseIB) ->- ∀(round | TOP ->-×20 diffuseEB)
4 changes: 2 additions & 2 deletions delta_q/src/agent.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::{DeltaQ, EvaluationContext, CDF};
use crate::{DeltaQ, EvaluationContext, Outcome};
use yew_agent::prelude::oneshot;

#[oneshot]
pub async fn CalcCdf((name, mut ctx): (String, EvaluationContext)) -> Result<CDF, String> {
pub async fn CalcCdf((name, mut ctx): (String, EvaluationContext)) -> Result<Outcome, String> {
DeltaQ::name(&name)
.eval(&mut ctx)
.map_err(|e| e.to_string())
Expand Down
41 changes: 34 additions & 7 deletions delta_q/src/bin/editor-web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ macro_rules! cloned {
}};
}

use delta_q::{CalcCdf, DeltaQ, DeltaQComponent, DeltaQContext, EvalCtxAction, EvaluationContext};
use delta_q::{
CalcCdf, DeltaQ, DeltaQComponent, DeltaQContext, EvalCtxAction, EvaluationContext, StepFunction,
};
use gloo_utils::window;
use js_sys::Reflect;
use std::str::FromStr;
Expand Down Expand Up @@ -90,11 +92,8 @@ fn app_main() -> HtmlResult {
return;
}
};
let data = js_sys::Object::new();
Reflect::set(&data, &"bins".into(), &cdf.iter().map(|x| JsValue::from(x.0)).collect::<js_sys::Array>()).unwrap();
Reflect::set(&data, &"values".into(), &cdf.iter().map(|x| JsValue::from(x.1)).collect::<js_sys::Array>()).unwrap();
Reflect::set(&data, &"max".into(), &(cdf.width() * 1.2).max(0.1).into()).unwrap();
Reflect::set(&data, &"name".into(), &name.into()).unwrap();
let data = mk_graph_obj(name, cdf.cdf.steps());
Reflect::set(&data, &"loads".into(), &cdf.load.iter().map(|(metric, steps)| mk_graph_obj(metric, steps)).collect::<js_sys::Array>()).unwrap();
let init = MessageEventInit::new();
init.set_data(&data);
let _ = window().dispatch_event(&*MessageEvent::new_with_event_init_dict("rootjs", &init).unwrap());
Expand Down Expand Up @@ -179,6 +178,31 @@ fn app_main() -> HtmlResult {
})
}

fn mk_graph_obj(name: &str, steps: &StepFunction) -> js_sys::Object {
let data = js_sys::Object::new();
Reflect::set(
&data,
&"bins".into(),
&steps
.graph_iter()
.map(|x| JsValue::from(x.0))
.collect::<js_sys::Array>(),
)
.unwrap();
Reflect::set(
&data,
&"values".into(),
&steps
.graph_iter()
.map(|x| JsValue::from(x.1))
.collect::<js_sys::Array>(),
)
.unwrap();
Reflect::set(&data, &"max".into(), &(steps.max_x() * 1.2).max(0.1).into()).unwrap();
Reflect::set(&data, &"name".into(), &name.into()).unwrap();
data
}

#[derive(Properties, PartialEq, Clone)]
struct AddExpressionProps {
on_change: Callback<(String, Option<DeltaQ>)>,
Expand Down Expand Up @@ -294,7 +318,10 @@ fn app() -> Html {
html! {
<div>
<h1>{ "DeltaQ Editor" }</h1>
<div id="output" style="width: 50%; height: 30%; border: 1px solid black;" />
<div style="display: flex; flex-direction: row; height: 30%;">
<div id="output" style="width: 50%; height: 100%; border: 1px solid black;" />
<div id="loads" style="width: 50%; height: 100%; border: 1px solid black;" />
</div>
<Suspense fallback={waiting}>
<OneshotProvider<CalcCdf> path="worker.js">
<AppMain />
Expand Down
Loading

0 comments on commit 563f370

Please sign in to comment.