Skip to Content
DocumentationExamplesNetwork Packets

Network Packets

A network router simulation demonstrating multiple probability distributions working together in a realistic scenario.

What it demonstrates

  • Five distributions used together: exponential, zipf, gaussian, bernoulli, uniform
  • Standalone distribution factories initialized with the simulation’s PRNG
  • Entity management for router state
  • Statistics collection for traffic analysis

Distributions used

DistributionPurpose
exponential(rate)Inter-arrival times (Poisson process)
zipf(n, s)Packet size ranks (few large, many small)
gaussian(mean, stddev)Base processing time
bernoulli(p)Random packet drop decision
uniform(min, max)Jitter added to processing time

Code

import { SimulationEngine } from 'simloop'; import type { SimContext } from 'simloop'; import { exponential, zipf, gaussian, bernoulli, uniform } from 'simloop'; const CONFIG = { seed: 2024, maxTime: 1000, avgArrivalRate: 5, packetSizeRanks: 5, zipfExponent: 1.5, meanProcessTime: 1.2, processTimeStddev: 0.3, dropProbability: 0.02, jitterMin: 0.0, jitterMax: 0.5, }; const PACKET_SIZES = [64, 256, 512, 1024, 1500]; // bytes per rank type NetworkEvents = { 'packet:arrive': { packetId: string; sizeBytes: number }; 'packet:process': { packetId: string; sizeBytes: number }; 'packet:done': { packetId: string; sizeBytes: number }; 'packet:drop': { packetId: string; sizeBytes: number; reason: string }; }; interface RouterState { queue: string[]; processing: boolean; totalBytes: number; } const sim = new SimulationEngine<NetworkEvents>({ seed: CONFIG.seed, maxTime: CONFIG.maxTime, logLevel: 'info', name: 'NetworkRouter', }); // Distribution samplers (initialized in init) let nextArrival: () => number; let packetSizeRank: () => number; let processTime: () => number; let shouldDrop: () => number; let jitter: () => number; sim.on('packet:arrive', (event, ctx) => { const { packetId, sizeBytes } = event.payload; const router = ctx.getEntity<RouterState>('router')!; ctx.stats.increment('arrivals'); ctx.stats.record('packetSize', sizeBytes); if (shouldDrop() === 1) { ctx.stats.increment('dropped'); ctx.schedule('packet:drop', ctx.clock, { packetId, sizeBytes, reason: 'random' }); } else { router.state.queue.push(packetId); if (!router.state.processing) { tryProcessNext(ctx); } } // Schedule next arrival const arrivalDelay = nextArrival(); const rank = packetSizeRank(); const nextSize = PACKET_SIZES[rank - 1]; const nextId = `pkt-${ctx.stats.get('arrivals').count + 1}`; ctx.schedule('packet:arrive', ctx.clock + arrivalDelay, { packetId: nextId, sizeBytes: nextSize, }); }); sim.on('packet:process', (event, ctx) => { const router = ctx.getEntity<RouterState>('router')!; router.state.processing = true; const baseTime = Math.max(0.1, processTime()); const addedJitter = jitter(); const totalTime = baseTime + addedJitter; ctx.stats.record('processTime', totalTime); ctx.schedule('packet:done', ctx.clock + totalTime, { packetId: event.payload.packetId, sizeBytes: event.payload.sizeBytes, }); }); sim.on('packet:done', (event, ctx) => { const router = ctx.getEntity<RouterState>('router')!; router.state.processing = false; router.state.totalBytes += event.payload.sizeBytes; ctx.stats.increment('processed'); ctx.stats.record('throughputBytes', event.payload.sizeBytes); tryProcessNext(ctx); }); sim.on('packet:drop', () => { // Marker event for tracing; stats already recorded }); function tryProcessNext(ctx: SimContext<NetworkEvents>): void { const router = ctx.getEntity<RouterState>('router')!; if (router.state.queue.length === 0 || router.state.processing) return; const packetId = router.state.queue.shift()!; const rank = packetSizeRank(); const sizeBytes = PACKET_SIZES[rank - 1]; ctx.schedule('packet:process', ctx.clock, { packetId, sizeBytes }); } sim.init((ctx) => { // Initialize samplers with simulation's PRNG const rng = () => ctx.random(); nextArrival = exponential(rng, CONFIG.avgArrivalRate); packetSizeRank = zipf(rng, CONFIG.packetSizeRanks, CONFIG.zipfExponent); processTime = gaussian(rng, CONFIG.meanProcessTime, CONFIG.processTimeStddev); shouldDrop = bernoulli(rng, CONFIG.dropProbability); jitter = uniform(rng, CONFIG.jitterMin, CONFIG.jitterMax); ctx.addEntity<RouterState>({ id: 'router', state: { queue: [], processing: false, totalBytes: 0 }, }); const firstRank = packetSizeRank(); ctx.schedule('packet:arrive', 0, { packetId: 'pkt-1', sizeBytes: PACKET_SIZES[firstRank - 1], }); }); sim.onEnd((ctx) => { const arrivals = ctx.stats.get('arrivals'); const processed = ctx.stats.get('processed'); const dropped = ctx.stats.get('dropped'); const procTime = ctx.stats.get('processTime'); const router = ctx.getEntity<RouterState>('router')!; console.log(`Arrivals: ${arrivals.count}, Processed: ${processed.count}`); console.log(`Dropped: ${dropped.count} (${((dropped.count / arrivals.count) * 100).toFixed(1)}%)`); console.log(`Throughput: ${(router.state.totalBytes / 1024).toFixed(1)} KB`); console.log(`Avg process time: ${procTime.mean.toFixed(3)}`); }); const result = sim.run();

Key takeaways

  • Distribution factories are standalone — they can be initialized outside handlers using any () => number RNG source
  • The rng function wraps ctx.random() to maintain determinism through the simulation’s seeded PRNG
  • The router processes one packet at a time; if the queue is non-empty after completion, the next packet starts immediately
Last updated on