Basic Network Simulations and Beyond in Python

Introduction

Our purpose is to show how to do a variety of network related simulations
involving random variables with Python. In particular we'll show some of the
great features of Python, MatPlotLib and
SimPy libraries. All code
has been tested with Python version 3.6 and current versions of the referenced libraries as of June 2017.

This tutorial will not cover theoretical aspects of probability, statistics, or
queueing theory. For those interested in digging deeper or reviewing probability and queueing
theory the following free resources are highly recommended and are listed in
order of most advanced to most fundamental.

Random Variables in Python and SciPy

Python makes it extremely easy to generate samples of a random number for simulation
or other purposes. The Python standard library includes a module random
explicitly for this purpose.

import random
print("Here is a single sample from a uniform random variable")
print(random.random())
print("Here is a list of three samples:")
uniSamples = [random.random(), random.random(), random.random()]
print(uniSamples)
print("Here is a list of three exponential samples:")
expSamples = [random.expovariate(1.0), random.expovariate(1.0), random.expovariate(1.0)]
print(expSamples)

Code Fragment 1. Simple example of Python
random number generation.

The previous code should work in any Python 2.7 release that includes the standard library. As a
bit
of a review, random number generators implemented via computer algorithms are not "truely"
random
but appear random as judged by a number of different criteria. The underlying random number
generator in the Python standard library is based on the very high quality
Mersenne Twister
algorithm. Which is very suitable for almost all types of simulations. What it is not suitable
for
is any cryptographic
purpose!

Visualizing Distributions: Histograms with Matplotlib

One of the best ways to look at a probability distribution is via a histogram. Matplotlib makes
this very easy as the following two code snippets show.

Distributions and Network Simulations

Although probability distributions can play a wide variety of roles
in modeling and simulating networks we will focus on just two uses here. First
we will use a probability distribution to model the time between packet arrivals, the
inter-arrival
time. Second we will use a probability distribution to model the sizes of the packets sent.

A notion closely related to the packet inter-arrival time is the count of the number of
packets received by a certain time. In the fancy language of
stochastic
processes we
would call the count of the number of packets received a
counting process. A
point
of potential confusion is that one can talk about packet arrivals either in terms of their
inter-arrival
time process or the counting process of the (total) number of packet arrivals by a given time.
For example to enable analytical analysis of the buffer backlog at the output of a packet switch
one utilizes a Poisson
process
to model the packet arrival count. A Poisson process is a counting process that has exponentially
distributed inter-arrival times. In discrete event simulations we must compute the time until
the next
"event" hence we will always work with inter-arrival time distributions. Hence to generate
Poisson
process packet arrivals we will use
an exponentially distributed random number generator and not a Poisson distributed random
variable!
Do not confuse the two notions. The difference between these two notions is a typical quiz or
exam question.

Network Simulations Directly in Python

In network simulations we can have a number of tasks such as packet generation, transmission,
queueing, that appear to run at the same time. In modern computers, operating system mechanisms
such as processes and threads providing this illusion. For simplicity of implementation, ease of use, and
scalability such mechanisms are not used for discrete event simulation (DES), instead techniques more akin to
coroutines are frequently
employed.

Although Python's generators provide functionality very similar to coroutines there is still a
fair amount of work needed to create a
discrete event simulation (DES) system. This is where SimPy,
a very nice, open source, DES package comes in. SimPy is a general purpose DES package, not networking
specific.
I wanted my students to jump right into network simulations without learning too much Python
or SimPy so I came up with a set of components,
SimComponents.py, to assist them and I in creating network simulations.
The key components/classes are:

Packet

This is a simple data object that represents a packet. Generally created by PacketGenerators.
Key fields include: generation time, size, flow_id, packet id, source, and destination. We do
not model upper layer protocols, i.e., packets don't contain a payload. The size (in bytes) field is used to
determine transmission time.

PacketGenerator

A PacketGenerator simulates the sending of packets with a specified inter-arrival time distribution
and a packet size distribution. One can set an initial delay and a finish time for packet generation.
In addition one can set the source id and flow ids for the packets generated. The packet generators
out member variable is used to connect the generator to any component with a
put() member function.

PacketSink

Packet sinks record arrival time information from packets. This can take the form of
raw arrival times or inter-arrival times. In addition the packet sink can record packet
waiting times. Supports the put() operation.

SwitchPort

Models a first-in, first-out (FIFO) queued output port on a packet switch/router.
You can set the rate of the output port and a queue size limit (in bytes). Keeps track of packets received
and packets dropped.

PortMonitor

A PortMonitor is used if you want to monitor queue size over time for a SwitchPort.
You need to specify a sampling distribution, i.e., a distribution that gives the time between
successive samples.

Example 1: Two Packet Generators and a Sink

In code fragment 3 below we show basic usage of the PacketGenerator and PacketSink classes. In
particular:

On lines 3-12 we define functions that return either inter-arrival times or packet
size distributions.

On line 14 we create a SimPy Environment object that is used when creating our
packet generators and sink. In addition we use this Environment object to run
the simulation on line 23.

On line 16 we create a packet sink and enable debugging which prints a summary
for every packet received.

On lines 17 and 18 we create our packet generators and on lines 20-21 we "wire" our
packet generators to our packet sink. Note that we can wire as many outputs to an input as
we like but to hook a single output to multiple inputs requires more work (packet duplication)
which we have a separate component.

Example 2: Overloaded Switch Port

In this example, Code Fragment 5, we have a packet generator connected to a switch port which is then connected to
a packet sink. We see from lines 4-5 that packets will be generated with a constant
interarrival time of 1.5 seconds and from lines 7-8 that the packets will have a
constant size of 100 bytes. This gives an average arrival rate of about 533.3 bps. On
line 14 we create our switch port but it only has a line rate of 200 bps and a
queue limit of 300 bytes. Hence should be pushed into dropping packets quickly.

We see from the output, Code Fragment 6, that only four packets are received at the sink by
the time the simulation is stopped (the simulation can be resumed with just another call to
the run() method). In addition to the debugging output for each packet received at the sink we
have the information from the packet sink and switch port as printed in lines 20-21. Note
how the first 100 byte packet incurs a delay of 4 seconds (due to the 200 bps line rate) and
also the received and dropped packet counts.

Example 3: An M/M/1 Queueing System

Now let's see how to simulate an switching port with exponential packet inter-arrival times,
and exponentially distributed packet sizes. With a FIFO queueing discipline and no limit on queue sizes such
a system is equivalent to the well known M/M/1
queueing system which has nice analytical expressions for average queue size and waiting times.

The code to simulate an M/M/1 queue, Code Fragment 7, is very similar to that used in the overloaded
switch port example, Code Fragment 5, however we have made the following changes:

On lines 7-9 we have used the standard Python functools module to slightly ease
the definition of functions returning a random sample with a given parameter.

On line 14 where we create the packet sink we have turned off debugging since
we will be processing a great many packets.

In Code Fragment 8 we show example output from a simulation run. Note since we are using
random numbers your output should be different. The arrival times λ = 0.5 packets per second,
and the packet service rate μ = 1.25 packets per second which gives a utilization ρ = 0.4.
The theoretical mean queue size = 2/3 and the mean waiting time is = 4/3.

In addition to computing statistics we can use the measurements from the packet
sink to create histograms as shown in Figure 2 below. Note the roughly exponential
character of plot. This is demonstration of
Burke's theorem.
Which is useful in the analysis of networks of queues.

Simulating & Analyzing Networks of Queues

Explanation to be written... But we have example code to simulate the network of queues shown inf Figure 3 here:
QueueNet2.py

Figure 3.A Network of Queues to be Simulated.

Traffic Shaping

Explanation to be written... But we have implemented a Token bucket shaper in
the SimComponents.py module and the example code that uses it to produce Figure 4, shown below, is given here:
ShaperExample1.py

Figure 4.Input and output from a traffic shaper with rate 1/2 the input rate.

Queueing Disciplines

Explanation to be written... But we have implemented Virtual Clock and Weighted
Fair Queueing disciplines in the SimComponents.py module and have example code that uses the WFQ component
to produce Figure 5, shown below,
here: WFQueueExample.py

Figure 5.Input and output from a traffic shaper with rate 1/2 the input rate.