Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .superset/workspace.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ unset PYTHONPATH PYTHONHOME
if [[ "$mode" == setup ]]; then
python -m pip install -e '.[dev]'
python -m pip check
python -c 'import ngraph_sim; print("Workspace environment ready")'
python -c 'import netlab_sim; print("Workspace environment ready")'
else
make check-ci
fi
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# NetGraph-Sim Development Makefile
# NetLab-Sim Development Makefile

.PHONY: help venv clean-venv dev install check check-ci lint format test qt venv-ft check-ft build clean check-dist publish-test publish info hooks check-python

Expand All @@ -24,7 +24,7 @@ PRECOMMIT := $(PYTHON) -m pre_commit
# Help
# --------------------------------------------------------------------------
help:
@echo "NetGraph-Sim Development Commands"
@echo "NetLab-Sim Development Commands"
@echo ""
@echo "Setup:"
@echo " make venv - Create virtual environment (./venv)"
Expand Down Expand Up @@ -147,7 +147,7 @@ build:
@$(PYTHON) -m build

clean:
@rm -rf build/ dist/ *.egg-info/ ngraph_sim.egg-info/
@rm -rf build/ dist/ *.egg-info/ netlab_sim.egg-info/

check-dist:
@$(PYTHON) -m twine check dist/*
Expand Down
64 changes: 32 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
# NetGraph-Sim
# NetLab-Sim

[![CI](https://github.com/networmix/NetGraph-Sim/actions/workflows/python-test.yml/badge.svg?branch=main)](https://github.com/networmix/NetGraph-Sim/actions/workflows/python-test.yml)
[![CI](https://github.com/networmix/NetLab-Sim/actions/workflows/python-test.yml/badge.svg?branch=main)](https://github.com/networmix/NetLab-Sim/actions/workflows/python-test.yml)

Discrete-event simulation for IP networks. Zero dependencies. Python 3.11+,
including free-threaded builds.

Two layers:

- `ngraph_sim` is a small SimPy-style engine: an environment, events,
- `netlab_sim` is a small SimPy-style engine: an environment, events,
processes and resources.
- `ngraph_sim.model`, `ngraph_sim.runtime`, `ngraph_sim.agents` and
`ngraph_sim.study` model routers with a RIB and a FIB, interfaces and links
- `netlab_sim.model`, `netlab_sim.runtime`, `netlab_sim.agents` and
`netlab_sim.study` model routers with a RIB and a FIB, interfaces and links
that fail and recover, static and shortest-path routing with ECMP, SRv6
policies, flow placement with per-link utilization, protocol agents that
learn routes by exchanging messages, and failure studies that measure what
Expand All @@ -19,18 +19,18 @@ Two layers:
## Install

```bash
pip install git+https://github.com/networmix/NetGraph-Sim
pip install git+https://github.com/networmix/NetLab-Sim
```

The distribution is `ngraph-sim`, part of the NetGraph family (`ngraph`), and
the package imports as `ngraph_sim`. It does not depend on NetGraph.
NetLab-Sim is distributed as `netlab-sim` and imports as `netlab_sim`.
It does not depend on NetGraph.

## Engine

```python
import ngraph_sim
import netlab_sim

env = ngraph_sim.Environment()
env = netlab_sim.Environment()

def producer(env, store):
for i in range(5):
Expand All @@ -42,7 +42,7 @@ def consumer(env, store):
item = yield store.get()
print(f't={env.now}: got {item}')

store = ngraph_sim.Store(env)
store = netlab_sim.Store(env)
env.process(producer(env, store))
env.process(consumer(env, store))
env.run()
Expand All @@ -59,17 +59,17 @@ env.run()
| `Container` | Bulk quantities |

A process interrupts another with `proc.interrupt(cause)`; the target sees
`ngraph_sim.Interrupt`. Resources are used with
`netlab_sim.Interrupt`. Resources are used with
`with resource.request() as req: yield req`. An `Environment` is
single-threaded and shares nothing with other environments, so independent
simulations run in parallel threads on a free-threaded build.

## Network layer

```python
import ngraph_sim
from ngraph_sim.model.network import Network
from ngraph_sim.runtime import Simulation
import netlab_sim
from netlab_sim.model.network import Network
from netlab_sim.runtime import Simulation

net = Network()
r1, r2 = net.add_device('R1'), net.add_device('R2')
Expand All @@ -79,7 +79,7 @@ net.add_p2p(r1, 'eth1', r2, 'eth1', ipv4=('10.1.12.0/31', '10.1.12.1/31'), speed
r1.add_route('10.0.0.2/32', [('eth1', '10.1.12.1')])
net.add_demand('d1', 'R1', '10.0.0.2', rate=4e9)

sim = Simulation(ngraph_sim.Environment(), net) # converges at t=0
sim = Simulation(netlab_sim.Environment(), net) # converges at t=0
sim.at(10, net.links['R1:eth1--R2:eth1'].fail)
sim.run_until(20)
print(sim.timeline.snapshot_at(10).placement.dropped_by_reason) # PMap({'NO_ROUTE': 4000000000.0})
Expand All @@ -97,7 +97,7 @@ traffic is still sent to a dead leg and dropped.
`select(kind=EventClass, device=...)`, `interface_series(device, interface)`,
`utilization_series(edge)`, `rows()`, `to_csv(path)`. By default it keeps the
latest 10,000 records and 100,000 events.
- `net.add_source(oracle_igp)`, from `ngraph_sim.model.igp`, installs
- `net.add_source(oracle_igp)`, from `netlab_sim.model.igp`, installs
shortest-path ECMP routes without a protocol.

## Segment routing
Expand All @@ -109,9 +109,9 @@ H.Encaps.Red at the headend. Placement follows the encapsulated packet hop
by hop, so wire load includes the outer headers.

```python
from ngraph_sim.model import srv6 as sr
from ngraph_sim.model.contracts import STATIC
from ngraph_sim.model.igp import oracle_igp
from netlab_sim.model import srv6 as sr
from netlab_sim.model.contracts import STATIC
from netlab_sim.model.igp import oracle_igp

net = Network()
with net.batch():
Expand All @@ -134,7 +134,7 @@ print(r1.node.srv6_policies.states[policy.key].status) # UP

Policy state reports validity, the selected path and programming status;
`srv6.policy_status(state)` adds the delivery observed in placement. See
`ngraph_sim/model/srv6.py` for the records and `docs/reference.md` for the
`netlab_sim/model/srv6.py` for the records and `docs/reference.md` for the
SONiC CONFIG_DB interchange format.

## Protocol agents
Expand All @@ -147,14 +147,14 @@ round and isolates a rejected output from its peers. Datagrams travel over
link channels that honour physical failure; sessions are ordered reliable
streams with timeouts.

`ngraph_sim.agents.reference` is a small link-state protocol built on that
`netlab_sim.agents.reference` is a small link-state protocol built on that
contract: hello discovery, flooding, SPF, learned SRv6 SIDs. It converges to
the same routes as the oracle without reading remote state.

```python
from ngraph_sim.agents.reference import ReferenceAgent, ReferenceConfig
from ngraph_sim.model.addressing import to_network
from ngraph_sim.model.contracts import ClientId
from netlab_sim.agents.reference import ReferenceAgent, ReferenceConfig
from netlab_sim.model.addressing import to_network
from netlab_sim.model.contracts import ClientId

net = Network()
r1, r2 = net.add_device('R1'), net.add_device('R2')
Expand All @@ -163,16 +163,16 @@ r2.add_loopback('lo0', ipv4=['10.0.0.2/32'])
net.add_p2p(r1, 'eth1', r2, 'eth1', unnumbered=True)
for name in ('R1', 'R2'):
net.add_agent(name, ReferenceAgent(ReferenceConfig(hello_interval=0.5, hold_time=1.5)))
sim = Simulation(ngraph_sim.Environment(), net)
sim = Simulation(netlab_sim.Environment(), net)
sim.run_until(5)
print([str(to_network(*row.prefix, 4)) for row in r1.rib(4).rows_of(ClientId('ref'))]) # ['10.0.0.2/32']
```

## Failure studies

```python
from ngraph_sim.runtime import Draws, Process
from ngraph_sim.study import Study
from netlab_sim.runtime import Draws, Process
from netlab_sim.study import Study

net = Network()
r1, r2, r3 = (net.add_device(f'R{i}') for i in (1, 2, 3))
Expand Down Expand Up @@ -202,16 +202,16 @@ and the `SimStudy` workflow step reuse NetGraph scenarios, failure
policies, seeds and the `results.json` format:

```sh
ngraph-sim run scenario.yaml --results results.json
netlab-sim run scenario.yaml --results results.json
```

## Adapters

- `ngraph_sim.adapters.ngraph`: NetGraph scenarios and networks in, per-link
- `netlab_sim.adapters.ngraph`: NetGraph scenarios and networks in, per-link
utilization and NetGraph-shaped results out.
- `ngraph_sim.adapters.core`: links as NetGraph-Core graph arrays
- `netlab_sim.adapters.core`: links as NetGraph-Core graph arrays
(`edge_arrays`, `build_graph`), used to cross-check plain-IP placement.
- `ngraph_sim.adapters.sonic`: SONiC CONFIG_DB load and dump for a bounded
- `netlab_sim.adapters.sonic`: SONiC CONFIG_DB load and dump for a bounded
schema.

Details of these formats and the study options are in
Expand Down
8 changes: 4 additions & 4 deletions benchmarks/batch.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@
from time import perf_counter
from typing import Any

from ngraph_sim.model.lpm import FrozenPrefixTable
from ngraph_sim.model.network import Network
from ngraph_sim.model.state import FloatArray, PMap, _is_ignored_field
from netlab_sim.model.lpm import FrozenPrefixTable
from netlab_sim.model.network import Network
from netlab_sim.model.state import FloatArray, PMap, _is_ignored_field


def normalized(value: Any) -> Any:
Expand Down Expand Up @@ -73,7 +73,7 @@ def main() -> None:
scenario = None
adapter = None
if args.workload == 'scenario':
adapter = importlib.import_module('ngraph_sim.adapters.ngraph')
adapter = importlib.import_module('netlab_sim.adapters.ngraph')
Scenario = importlib.import_module('ngraph.scenario').Scenario
scenario = Scenario.from_yaml(Path(args.scenario).read_text())
timings = []
Expand Down
6 changes: 3 additions & 3 deletions benchmarks/batch_snapshots.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@
from dataclasses import replace
from time import perf_counter

from ngraph_sim.model import routing
from ngraph_sim.model.addressing import IPV4
from ngraph_sim.model.network import Network
from netlab_sim.model import routing
from netlab_sim.model.addressing import IPV4
from netlab_sim.model.network import Network


def main() -> None:
Expand Down
6 changes: 3 additions & 3 deletions benchmarks/pmap_scale.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ def main():
args = parser.parse_args()
sys.path.insert(0, os.path.abspath(args.repo))

from ngraph_sim.model import derive
from ngraph_sim.model.network import Network
from ngraph_sim.model.state import DeviceState, NetworkState, PMap, StateDelta
from netlab_sim.model import derive
from netlab_sim.model.network import Network
from netlab_sim.model.state import DeviceState, NetworkState, PMap, StateDelta

if args.shards:
PMap._SHARD_COUNT = args.shards
Expand Down
12 changes: 6 additions & 6 deletions benchmarks/s0_prng.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@

sys.path.insert(0, str(Path(__file__).resolve().parents[1]))

from ngraph_sim import Environment # noqa: E402
from ngraph_sim.model import contracts as c # noqa: E402
from ngraph_sim.model.network import Network # noqa: E402
from ngraph_sim.runtime import simulation # noqa: E402
from netlab_sim import Environment # noqa: E402
from netlab_sim.model import contracts as c # noqa: E402
from netlab_sim.model.network import Network # noqa: E402
from netlab_sim.runtime import simulation # noqa: E402


class Ticker:
Expand Down Expand Up @@ -66,10 +66,10 @@ def main():
args = parser.parse_args()
if args.runtime_ref:
source = subprocess.check_output(
['git', 'show', f'{args.runtime_ref}:ngraph_sim/runtime/agents.py'],
['git', 'show', f'{args.runtime_ref}:netlab_sim/runtime/agents.py'],
text=True,
)
module = types.ModuleType('ngraph_sim.runtime._s0_benchmark_agents')
module = types.ModuleType('netlab_sim.runtime._s0_benchmark_agents')
sys.modules[module.__name__] = module
exec(compile(source, f'{args.runtime_ref}:agents.py', 'exec'), module.__dict__)
simulation.AgentRuntime = module.AgentRuntime
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/s0_prng_determinism.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"script": "\nimport json\nfrom ngraph_sim.model.prng import Prng\nstate = None\noutput = []\nfor run in range(100):\n r = Prng.for_agent(17, -9, 'module:β', 2) if state is None else Prng.restore(state)\n items = list(range(8))\n r.stream('shuffle').shuffle(items)\n output.append([r.random(), r.randrange(-10, 12, 3), r.choice('abc'),\n r.sample(range(30), 4), r.getrandbits(101),\n r.stream('jitter').uniform(1, 5), r.randint(-4, 8), items])\n state = r.getstate()\nprint(json.dumps([output, state], sort_keys=True))\n",
"script": "\nimport json\nfrom netlab_sim.model.prng import Prng\nstate = None\noutput = []\nfor run in range(100):\n r = Prng.for_agent(17, -9, 'module:β', 2) if state is None else Prng.restore(state)\n items = list(range(8))\n r.stream('shuffle').shuffle(items)\n output.append([r.random(), r.randrange(-10, 12, 3), r.choice('abc'),\n r.sample(range(30), 4), r.getrandbits(101),\n r.stream('jitter').uniform(1, 5), r.randint(-4, 8), items])\n state = r.getstate()\nprint(json.dumps([output, state], sort_keys=True))\n",
"results": [
{
"python": "venv/bin/python",
Expand Down
12 changes: 6 additions & 6 deletions benchmarks/s0_w1_scale.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,26 +28,26 @@
import tracemalloc
from dataclasses import replace

from ngraph_sim.model import network as nw
from ngraph_sim.model import srv6
from netlab_sim.model import network as nw
from netlab_sim.model import srv6


def load_baseline(revision: str) -> str:
commit = subprocess.check_output(['git', 'rev-parse', revision], text=True).strip()
source = subprocess.check_output(
['git', 'show', f'{commit}:ngraph_sim/model/network.py'], text=True
['git', 'show', f'{commit}:netlab_sim/model/network.py'], text=True
)
exec(compile(source, f'{commit}:ngraph_sim/model/network.py', 'exec'), nw.__dict__)
exec(compile(source, f'{commit}:netlab_sim/model/network.py', 'exec'), nw.__dict__)
source = subprocess.check_output(
['git', 'show', f'{commit}:ngraph_sim/model/srv6.py'], text=True
['git', 'show', f'{commit}:netlab_sim/model/srv6.py'], text=True
)
function = next(
node
for node in ast.parse(source).body
if isinstance(node, ast.FunctionDef) and node.name == 'consumer_index'
)
module = ast.Module(body=[function], type_ignores=[])
exec(compile(module, f'{commit}:ngraph_sim/model/srv6.py', 'exec'), srv6.__dict__)
exec(compile(module, f'{commit}:netlab_sim/model/srv6.py', 'exec'), srv6.__dict__)
return commit


Expand Down
14 changes: 7 additions & 7 deletions benchmarks/s0_w2_scale.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@


def lookup():
from ngraph_sim.model.lpm import PrefixTable
from netlab_sim.model.lpm import PrefixTable

results = {}
for bits in (32, 128):
Expand Down Expand Up @@ -91,7 +91,7 @@ def batch(call=call, addresses=addresses, method=method):
def converge():
import tracemalloc

from ngraph_sim.model.network import Network
from netlab_sim.model.network import Network

count = 2000
net = Network(seed=1)
Expand Down Expand Up @@ -145,24 +145,24 @@ def main():
if args.case:
assert args.source is not None
sys.path.insert(0, str(args.source))
# No NetGraph-Sim import is allowed before selecting the source snapshot.
# No NetLab-Sim import is allowed before selecting the source snapshot.
data = lookup() if args.case == 'lookup' else converge()
import ngraph_sim
import netlab_sim

print(json.dumps({'source': ngraph_sim.__file__, 'data': data}))
print(json.dumps({'source': netlab_sim.__file__, 'data': data}))
return
assert args.output is not None, '--output is required for the comparison'
repo = Path(__file__).resolve().parents[1]
baseline = subprocess.check_output(
['git', 'rev-parse', args.baseline], cwd=repo, text=True
).strip()
archive = subprocess.check_output(
['git', 'archive', '--format=tar', baseline, 'ngraph_sim'], cwd=repo
['git', 'archive', '--format=tar', baseline, 'netlab_sim'], cwd=repo
)
result: dict[str, object] = {'baseline': baseline, 'python': sys.version}
runs = []
result['runs'] = runs
with tempfile.TemporaryDirectory(prefix='ngraph-sim-s0-w2-') as temporary:
with tempfile.TemporaryDirectory(prefix='netlab-sim-s0-w2-') as temporary:
old = Path(temporary)
with tarfile.open(fileobj=io.BytesIO(archive)) as source:
source.extractall(old, filter='data')
Expand Down
Loading
Loading