Sign inSign up

bnnupc/netsim

By bnnupc

Updated about 4 years ago

Image of the NetSimulator used in the Graph Neural Networking Challenge 2022.

Image
0

808

bnnupc/netsim repository overview

Dataset Generation

To generate a dataset with netsim simulator we must define for each sample the graph topology, the routing paths between nodes and its traffic matrix. Then a simulator will use them to calculate the delay and jitter per each path. For more details about the parameters used to generate the dataset, check out the parameters glossary. Here you can find an example to generate an initial dataset:

import networkx as nx
import random
import os

# Define destination for the generated samples
training_dataset_path = "training"

#paths relative to data folder
graphs_path = "graphs"
routings_path = "routings"
tm_path = "tm"

# Path to simulator file
simulation_file = os.path.join(training_dataset_path,"simulation.txt")
# Name of the dataset: Allows you to store several datasets in the same path
# Each dataset will be stored at /results/
dataset_name = "dataset"

# Create folders
if (os.path.isdir(training_dataset_path)):
    print ("Destination path already exists. Files within the directory may be overwritten.")
else:
    os.makedirs(os.path.join(training_dataset_path,graphs_path))
    os.mkdir(os.path.join(training_dataset_path,routings_path))
    os.mkdir(os.path.join(training_dataset_path,tm_path))

'''
Generate a graph topology file. The graphs generated have the following characteristics:
- All nodes have buffer sizes of 32000 bits and FIFO scheduling
- All links have bandwidths of 100000 bits per second
'''
def generate_topology(net_size, graph_file):
    G = nx.Graph()
    nodes = []
    node_degree = []
    for i in range(net_size):
        node_degree.append(random.choices([2,3,4,5,6],weights=[0.34,0.35,0.2,0.1,0.01])[0])
        
        nodes.append(i)
        G.add_node(i)
        # Assign to each node the scheduling Policy
        G.nodes[i]["schedulingPolicy"] = "FIFO"
        # Assign the buffer size of all the ports of the node
        G.nodes[i]["bufferSizes"] = 32000

    finish = False
    while (True):
        aux_nodes = list(nodes)
        n0 = random.choice(aux_nodes)
        aux_nodes.remove(n0)
        # Remove adjacents nodes (only one link between two nodes)
        for n1 in G[n0]:
            if (n1 in aux_nodes):
                aux_nodes.remove(n1)
        if (len(aux_nodes) == 0):
            # No more links can be added to this node - can not acomplish node_degree for this node
            nodes.remove(n0)
            if (len(nodes) == 1):
                break
            continue
        n1 = random.choice(aux_nodes)
        G.add_edge(n0, n1)
        # Assign the link capacity to the link
        G[n0][n1]["bandwidth"] = 100000
        
        for n in [n0,n1]:
            node_degree[n] -= 1
            if (node_degree[n] == 0):
                nodes.remove(n)
                if (len(nodes) == 1):
                    finish = True
                    break
        if (finish):
            break
    if (not nx.is_connected(G)):
        G = generate_topology(net_size, graph_file)
        return G
    
    nx.write_gml(G,graph_file)
    
    return (G)


'''
Generate a file with the shortest path routing of the topology G
'''
def generate_routing(G, routing_file):
    with open(routing_file,"w") as r_fd:
        lPaths = nx.shortest_path(G)
        for src in G:
            for dst in G:
                if (src == dst):
                    continue
                path =  ','.join(str(x) for x in lPaths[src][dst])
                r_fd.write(path+"\n")
            

'''
Generate a traffic matrix file. We consider flows between all nodes in the newtork, each with the following characterstics
- The average bandwidth ranges between 10 and max_avg_lbda
- We consider three time distributions (in case of the ON-OFF policy we have off periods of 10 and on periods of 5)
- We consider two packages distributions, chosen at random
- ToS is assigned randomly
'''
def generate_tm(G,max_avg_lbda, traffic_file):
    poisson = "0" 
    cbr = "1"
    on_off = "2,10,5" #time_distribution, avg off_time exp, avg on_time exp
    time_dist = [poisson,cbr,on_off]
    
    pkt_dist_1 = "0,300,0.5,1700,0.5" #genric pkt size dist, pkt_size 1, prob 1, pkt_size 2, prob 2
    pkt_dist_2 = "0,500,0.6,1000,0.2,1400,0.2" #genric pkt size dist, pkt_size 1, prob 1, 
                                               # pkt_size 2, prob 2, pkt_size 3, prob 3
    pkt_size_dist = [pkt_dist_1, pkt_dist_2]
    tos_lst = [0,1,2]
    
    with open(traffic_file,"w") as tm_fd:
        for src in G:
            for dst in G:
                avg_bw = random.randint(10,max_avg_lbda)
                td = random.choice(time_dist)
                sd = random.choice(pkt_size_dist)
                tos = random.choice(tos_lst)
                
                traffic_line = "{},{},{},{},{},{}".format(
                    src,dst,avg_bw,td,sd,tos)
                tm_fd.write(traffic_line+"\n")
            

"""
We generate the files using the previously defined functions. This code will produce 100 samples where:
- We generate 5 topologies, and then we generate 20 traffic matrices for each
- The topology sizes range from 6 to 10 nodes
- We consider the maximum average bandwidth per flow as 1000
"""
max_avg_lbda = 1000
with open (simulation_file,"w") as fd:
    for net_size in range (6,11):
        #Generate graph
        graph_file = os.path.join(graphs_path,"graph_{}.txt".format(net_size))
        G = generate_topology(net_size, os.path.join(training_dataset_path,graph_file))
        # Generate routing
        routing_file = os.path.join(routings_path,"routing_{}.txt".format(net_size))
        generate_routing(G, os.path.join(training_dataset_path,routing_file))
        # Generate TM:
        for i in range (20):
            tm_file = os.path.join(tm_path,"tm_{}_{}.txt".format(net_size,i))
            generate_tm(G,max_avg_lbda, os.path.join(training_dataset_path,tm_file))
            sim_line = "{},{},{}\n".format(graph_file,routing_file,tm_file)   
            # If dataset has been generated in windows, convert paths into linux format
            fd.write(sim_line.replace("\\","/"))

You can create a configuration file to define some of the features of the simulator. The file conf.yml should be creted into the root of thetraining_dataset_path and contain the following parameters:

threads: <number of threads>  # Number of concurrent simulations to be run. One per thread
name: <dataset_name> # Name of the dataset. It is created in /results/
samples_per_file: <nm> # Number of samples per compressed file
rm_prev_results: <y/n> # If 'y' is selected and the results folder already exists, the folder is removed.

To start the simulation, run the following command:

docker run --rm --mount type=bind,src=<dataset_path>,dst=/data bnnupc/netsim:v0.1 

When running the "docker run" command for the first time, the image will be downloaded automatically. This does not require more actions by the user, other that making sure the computer can connect to the internet.

Dataset Generation Parameters Glossary

THIS IS A GLOSSARY MEANT TO BE A REFERENCE, THE CODE CELLS ARE NOT MEANT TO BE EXECUTED

Each sample to be fed to the Neural Network model is comprised of three elements, each contained in its own individual file:

  • Graph topology: Represents a graph topology, including the nodes and edges that forms it as well as characterstics of each.
  • Routing file: Shows the recognized paths between each node witin the graph topology.
  • Traffic matrix ( TM ): Represents traffic flows going through a given network.

Each sample will be identified by a tuple of these three elements. This means we can generate multiple samples from the same graph topology if it's paired with different traffic matrices, for example.

In this README, we will show how to generate these files, and how their properties can be altered in order to generate different varying samples.

import networkx as nx
import random

# Generate, for instance, a complete graph
G = nx.complete_graph(10)

# Assign bandwidth to each edge of the graph. Its value is considered in bps.
for (n0,n1) in G.edges():
    G[n0][n1]["bandwidth"] = 100000

Each node is defined by two characteristics:

  • Scheduling policy. The order in which packets are served in an output port is based on the state of queues and the configured queue scheduling policy. We consider the following four policies:
    • First In First Out (FIFO)_: shared single queue for all packets, indepedently of ToSs.
    • Strict Priority (SP): one queue for each ToS (total of 3) were packets in queues with more priority are transmitted first.
    • Weighted Fair Queueing (WFQ): one queue for each ToS (total of 3). Each queue is assigned a weight by the configuration. The sum of weights must equal 100. Each time the policy chooses a queue according to its weight plus the data rate of the queue to achieve fairness.
    • Deficit Round Robin (DRR): one queue for each ToS (total of 3). Each queue is assigned a weight by the configuration. The sum of weights must equal 100. The policy will cycle through the queues. The amount of time dedicated to each queue is proportional to its weight.
  • Buffer size: the size of the buffer at the output ports of nodes, where packets are stored before they are processed. When a packet is received and its outgoing queue is full, the packet is dropped. The buffer size is computed in bits and its and its minimum value is 1000 bits.

We must define these two characterstics on all nodes of the topology. The scheduling policy of a node is stored in the attribute schedulingPolicy as a string. This is shown as follows:

# Let's configure all the nodes with a FIFO policy

for node in G:
    G.nodes[node]["schedulingPolicy"] = "FIFO"

# Let's configure all the nodes with a SP policy

for node in G:
    G.nodes[node]["schedulingPolicy"] = "SP"

In case of the WFQ and DRR policies, where we will also need to specify the weights of each queue, we will also need to define the attribute schedulingWeights. To do so, we will feed it a string that contains the weights for the queue dealing with ToS 0, 1, and 2, respectively, separated by commas:

# Let's configure all the nodes with a WFQ policy

for node in G:
    G.nodes[node]["schedulingPolicy"] = "WFQ"
    G.nodes[node]["schedulingWeights"] = "45, 30, 25"

# Let's configure all the nodes with a DRR policy

for node in G:
    G.nodes[node]["schedulingPolicy"] = "DRR"
    G.nodes[node]["schedulingWeights"] = "45, 30, 25"

To configure the buffer size we will only need to modify the attribute bufferSizes, including the size of the queue in bits.

# Assign to each node a queue size of 32000 bits

for node in G:
    G.nodes[node]["bufferSizes"] = 32000

# Finally we save the topology

graph_file = "graph.txt"
nx.write_gml(G,graph_file)

Routing

The routing is expressed as a text file where each line represents a path as a sequence of nodes. Destination base and source destination base routing can be used but they should not contain loops.

# For instance, we can use networkx to calculate the shortest path routing for each src-dst pair.

with open("routing.txt","w") as r_fd:
lPaths = nx.shortest_path(G)
for src in G:
    for dst in G:
        if (src == dst):
            continue
        path =  ','.join(str(x) for x in lPaths[src][dst])
        r_fd.write(path+"\n")

Traffic Matrix

The final step is to generate the traffic matrix ( TM ). Each line of the TM file describes one traffic flow between two nodes. These lines are formed by a set of parameters separated by commas as follows:

source, destination, avg_bw, time_distribution, [off_time, on_time,] pkt_dist, pkt_size_1, prob_1, [pkt_size_2, prob_2, [pkt_size_3, prob_3, [pkt_size_4, prob_4, [pkt_size_5, prob_5,]]]] tos

Here the brackets indicate optional parameters.

The source and destination parameters indicate the source and destination nodes for the given flow. Note that only one flow is allowed per source-destination pair in the input topology.

The avg_bw parameter indicates the average bandwidth, in bps, to be generated for this flow. Its value is limited between 10 and 10000 bps.

The next sets of parameters we'll discuss are pkt_dist, pkt_size_n and prob_n. These parameters are used to indicate the possible sizes of the packets and their relative frequency within the flow. pkt_dist specifically notes the distribution type used to generate the packets. Note: currently only one distribution is supported, so the value of pkt_dist should aways be 0.

Then, the pkt_size_n and prob_n properties are used to indicate a packet size, in bits, and its relative probability with respect to the other sizes. At least one packet size must be declared, but we can define up to 5 different sizes. The packet size should be a value between 256 and 2000 bits while the sum of all the prob_n values should equal 1.

The time_distribution parameter indicates how often packets should be generated over time. We support three time distributions:

  • Poisson (time_distribution=0): packets are generated following a Poisson distribution
  • CBR (time_distribution=1): packets are generated following a Continous Bit Rate model
  • ON-OFF (time_distribution=2): packets are generated following periods of activity and inactivity

We do NOT need to define the parameters that define Poisson and CBR distributions, as the packets will be generated considering the chosen packet size distribution and average bandwith parameters from earlier. In the case of using the ON-OFF distribution we will need to define the length of the activity and inactivity periods (on_time and off_time respectively).

Finally, tos indicates the ToS assigned to the packets generated for this flow, with values of 0, 1 or 2.

"""
Example: this code will generate flows between all nodes in the graph, such as:
- The average bandwidth is randomized between 10 and 10000 bps
- An ON-OFF time distribution is used, with an on_time of 5 s and an off_time of 10 s
- Packets can have two possible sizes, 300 and 1700 bits, both equally probable
- The ToS for all flows is 0 (high priority)
"""
with open("traffic.txt","w") as tm_fd:
    for src in G:
        for dst in G:
            avg_bw = random.randint(10,10000)
            time_dist = 2
            on_time = 5
            off_time = 10
            pkt_size_1 = 300
            prob_1 = 0.5
            pkt_size_2 = 1700
            prob_2 = 0.5
            tos = 0
            traffic_line = "{},{},{},{},{},{},0,{},{},{},{},{}".format(
                src,dst,avg_bw,time_dist,off_time,on_time,pkt_size_1,
                prob_1,pkt_size_2,prob_2,tos)
            tm_fd.write(traffic_line+"\n")

Tag summary

Content type

Image

Digest

sha256:144ac0b65

Size

270.7 MB

Last updated

about 4 years ago

docker pull bnnupc/netsim:v0.1