__all__ = ["Summary"]
__author__ = ["Hongyi Yang"]
from NetworkSim.simulation.tools.performance_analysis import \
get_transfer_delay, get_queueing_delay, get_service_delay, get_overall_delay
import numpy as np
import pandas as pd
[docs]class Summary:
"""
Summary class to generate summaries for a given simulator.
Parameters
----------
simulator : BaseSimulator
The simulator used in the simulation.
"""
def __init__(
self,
simulator
):
self.simulator = simulator
[docs] def simulation_summary(self):
"""
Overall summary of the simulation.
Returns
-------
simulation_summary : pandas DataFrame
A DataFrame containing a few key parameters in the simulation.
"""
if self.simulator.bidirectional:
_direction = 'bi-directional'
else:
_direction = 'uni-directional'
_rows = {
'Total Number of Nodes': self.simulator.model.network.num_nodes,
'Transmitter Type': self.simulator.transmitter_type,
'Receiver Type': self.simulator.receiver_type,
'Source Traffic Generation Method': self.simulator.traffic_generation_method,
'Direction of Transmission': _direction,
'Designed Average Data Rate (Gbit/s)': self.simulator.model.constants['average_bit_rate'],
'Designed Maximum Data Rate (Gbit/s)': self.simulator.model.constants['maximum_bit_rate'],
'Total Number of Data Packet Transmitted': len(self.simulator.latency),
'Total Number of Transmission Error': len(self.simulator.error),
'Estimated Average Queueing Delay (ns)':
get_queueing_delay(self.simulator) + get_service_delay(self.simulator),
'Estimated Average Transfer Delay (ns)': get_transfer_delay(self.simulator),
'Average Queueing Delay Latency (ns)': get_overall_delay(self.simulator)['mean_queueing_delay'],
'Average Transfer Delay Latency (ns)': get_overall_delay(self.simulator)['mean_transfer_delay'],
'Final Data Rate (Gbit/s)': self.simulator.latency[-1]['Data Rate'],
'Simulation Runtime (s)': self.simulator.runtime
}
summary = pd.DataFrame.from_dict(_rows, orient='index', columns=['Value'])
summary.index.name = 'Simulation Parameter'
return summary
[docs] def ram_summary(self):
"""
Summary of the transmitter RAMs.
Returns
-------
ram_summary : pandas DataFrame
A DataFrame with the packet generation information of each RAM, containing the columns:
- `RAM ID`
The RAM ID.
- `Total Number of Data Packet Generated`
The total number of data packets generated at each RAM.
- `Percentage of Data Packet Generated (%)`
Percentage of data packets generated at each RAM, compared to the total number of data packets \
generated in all RAMs in the network.
"""
_num_packet = [len(self.simulator.RAM[i].generated_data_packet)
for i in range(self.simulator.model.network.num_nodes)]
_total_num_packet = np.sum(_num_packet)
_percentage_generated = [_num_packet[i] / _total_num_packet * 100
for i in range(self.simulator.model.network.num_nodes)]
_ram_dict = {
'RAM ID': list(range(self.simulator.model.network.num_nodes)),
'Total Number of Data Packet Generated': _num_packet,
'Percentage of Data Packet Generated (%)': _percentage_generated
}
return pd.DataFrame(_ram_dict)
[docs] def transmitter_summary(self):
"""
Summary of transmitters.
Returns
-------
transmitter_summary : pandas DataFrame
A DataFrame with the information on control and data packets transmitted from each transmitter, \
containing the columns:
- `Transmitter ID`
The transmitter ID.
- `Total Number of Control Packet Transmitted`
Total number of control packets transmitted from each transmitter.
- `Percentage of Control Packet Transmitted (%)`
Percentage of control packets transmitted from each transmitter, compared to the total number \
of control packets transmitted in the network.
- `Total Number of Data Packet Transmitted`
Total number of data packets transmitted from each transmitter.
- `Percentage of Data Packet Transmitted (%)`
Percentage of data packets transmitted from each transmitter, compared to the total number \
of data packets transmitted in the network.
"""
_num_control_packet = [len(self.simulator.transmitter[i].transmitted_control_packet)
for i in range(self.simulator.model.network.num_nodes)]
_num_data_packet = [len(self.simulator.transmitter[i].transmitted_data_packet)
for i in range(self.simulator.model.network.num_nodes)]
_total_control_packet = np.sum(_num_control_packet)
_total_data_packet = np.sum(_num_data_packet)
_percentage_control_packet = [_num_control_packet[i] / _total_control_packet * 100
for i in range(self.simulator.model.network.num_nodes)]
_percentage_data_packet = [_num_data_packet[i] / _total_data_packet * 100
for i in range(self.simulator.model.network.num_nodes)]
_transmitter_dict = {
'Transmitter ID': list(range(self.simulator.model.network.num_nodes)),
'Total Number of Control Packet Transmitted': _num_control_packet,
'Percentage of Control Packet Transmitted (%)': _percentage_control_packet,
'Total Number of Data Packet Transmitted': _num_data_packet,
'Percentage of Data Packet Transmitted (%)': _percentage_data_packet
}
return pd.DataFrame(_transmitter_dict)
[docs] def receiver_summary(self):
"""
Summary of receivers.
Returns
-------
receiver_summary : pandas DataFrame
A DataFrame with the information on control and data packets received at each receiver, \
containing the columns:
- `Receiver ID`
The receiver ID.
- `Total Number of Control Packet Received`
Total number of control packets received at each receiver.
- `Percentage of Control Packet Received (%)`
Percentage of control packets received at each receiver, compared to the total number \
of control packets received in the network.
- `Total Number of Data Packet Received`
Total number of data packets received at each receiver.
- `Percentage of Data Packet Received (%)`
Percentage of data packets received at each receiver, compared to the total number \
of data packets received in the network.
"""
_num_control_packet = [len(self.simulator.receiver[i].received_control_packet)
for i in range(self.simulator.model.network.num_nodes)]
_num_data_packet = [len(self.simulator.receiver[i].received_data_packet)
for i in range(self.simulator.model.network.num_nodes)]
_total_control_packet = np.sum(_num_control_packet)
_total_data_packet = np.sum(_num_data_packet)
_percentage_control_packet = [_num_control_packet[i] / _total_control_packet * 100
for i in range(self.simulator.model.network.num_nodes)]
_percentage_data_packet = [_num_data_packet[i] / _total_data_packet * 100
for i in range(self.simulator.model.network.num_nodes)]
_receiver_dict = {
'Receiver ID': list(range(self.simulator.model.network.num_nodes)),
'Total Number of Control Packet Received': _num_control_packet,
'Percentage of Control Packet Received (%)': _percentage_control_packet,
'Total Number of Data Packet Received': _num_data_packet,
'Percentage of Data Packet Received (%)': _percentage_data_packet
}
return pd.DataFrame(_receiver_dict)
[docs] def latency_summary(self, data_range, latency_type):
"""
Summary of transmission latency.
Parameters
----------
data_range : str, optional
The range of data selected for latency summary, chosen from the following:
- `all` or `a`
All simulation data.
- `extended` or `e`
Extended simulation data in convergence mode.
- `batch` or `b`
Last batch data in convergence mode.
Default is ``None``, \
which is extended data for convergence mode and all data for non-convergence mode.
Returns
-------
average_latency_summary : pandas DataFrame
A DataFrame of average latencies from one node to another. The columns represent destination nodes while \
the index values represent source nodes.
"""
_all_range_keywords = {'all', 'a'}
_extended_range_keywords = {'extended', 'e'}
_batch_range_keywords = {'batch', 'b'}
_transfer_delay_keywords = {'transfer_delay', 'td'}
_queueing_delay_keywords = {'queueing delay', 'qd'}
if data_range is None:
if self.simulator.convergence:
data_range = 'e'
else:
data_range = 'a'
if data_range in _all_range_keywords:
_latency = self.simulator.latency
elif data_range in _extended_range_keywords:
if not self.simulator.convergence:
raise ValueError("Extended latency summary can only be generated "
"for the simulator with convergence mode enabled.")
_start = self.simulator.batch_stats[-1]['end_index']
_latency = self.simulator.latency[_start:]
elif data_range in _batch_range_keywords:
if not self.simulator.convergence:
raise ValueError("Last batch latency summary can only be generated "
"for the simulator with convergence mode enabled.")
_start = self.simulator.batch_stats[-1]['start_index']
_end = self.simulator.batch_stats[-1]['end_index']
_latency = self.simulator.latency[_start:_end]
else:
raise ValueError("Latency range not recognised")
if latency_type in _transfer_delay_keywords or latency_type is None:
_type = 'Transfer Delay'
elif latency_type in _queueing_delay_keywords:
_type = 'Queueing Delay'
else:
raise ValueError("Latency type not recognised.")
# Initialise n x n array (n is the number of nodes in the network)
# Row == Source Node, Column == Destination Node
_n = self.simulator.model.network.num_nodes
_latency_average = np.zeros([_n, _n])
_latency_sum = np.zeros([_n, _n])
_count = np.zeros([_n, _n])
# Record latency information
for latency_info in _latency:
_latency_sum[latency_info['Source ID']][latency_info['Destination ID']] += latency_info[_type]
_count[latency_info['Source ID']][latency_info['Destination ID']] += 1
# Calculate latency mean
for i in range(_n):
for j in range(_n):
if _count[i][j] == 0:
_latency_average[i][j] = 0
# _latency_average[i][j] = np.nan
else:
_latency_average[i][j] = _latency_sum[i][j] / _count[i][j]
_rows = [i for i in range(_n)]
_columns = [i for i in range(_n)]
return pd.DataFrame(_latency_average, index=_rows, columns=_columns)
[docs] def packet_count_summary(self):
"""
Summary of transmission packet count.
Returns
-------
count_summary : pandas DataFrame
A DataFrame of transmitted packet count. The columns represent destination nodes while \
the indices represent source nodes.
"""
# Initialise n x n array (n is the number of nodes in the network)
# Row == Source Node, Column == Destination Node
_n = self.simulator.model.network.num_nodes
_count = np.zeros([_n, _n])
# Record packet transmission information
for latency_info in self.simulator.latency:
_count[latency_info['Source ID']][latency_info['Destination ID']] += 1
_rows = [i for i in range(_n)]
_columns = [i for i in range(_n)]
return pd.DataFrame(_count, index=_rows, columns=_columns)
[docs] def packet_delay_summary(self, normalised=False, std=False):
"""
Summary of packet delay / queueing delay.
Packet Delay (also refer as Queueing Delay) = successful transmission time - generation time of the packet
Returns
-------
average_latency_summary : pandas DataFrame
A DataFrame of average latencies from one node to another. The columns represent destination nodes while \
the index values represent source nodes.
"""
# Initialise n x n array (n is the number of nodes in the network)
# Row == Source Node, Column == Destination Node
_n = self.simulator.model.network.num_nodes
_packet_duration = self.simulator.model.data_packet_duration
_count = np.zeros([_n, _n])
_sum = np.zeros([_n, _n])
_packet_info_source_node_pair = [[[] for _ in range(_n)] for _ in range(_n)]
_mean_packet_delay = np.zeros([_n, _n])
_std_packet_delay = np.zeros([_n, _n])
_rows = [i for i in range(_n)]
_columns = [i for i in range(_n)]
# Record packet transmission information
if std:
for latency_info in self.simulator.latency:
_packet_info_source_node_pair[latency_info['Source ID']
][latency_info['Destination ID']].append(latency_info['Queueing Delay'])
for i in range(_n):
for j in range(_n):
temp_array = np.array(_packet_info_source_node_pair[i][j])
if temp_array.size == 0:
_mean_packet_delay[i][j] = 0
_std_packet_delay[i][j] = 0
else:
if normalised:
temp_array = temp_array / _packet_duration
_mean_packet_delay[i][j] = np.mean(temp_array)
_std_packet_delay[i][j] = np.std(temp_array)
else:
_mean_packet_delay[i][j] = np.mean(temp_array)
_std_packet_delay[i][j] = np.std(temp_array)
return pd.DataFrame(_mean_packet_delay, index=_rows, columns=_columns), \
pd.DataFrame(_std_packet_delay, index=_rows, columns=_columns)
else:
for latency_info in self.simulator.latency:
_count[latency_info['Source ID']][latency_info['Destination ID']] += 1
_sum[latency_info['Source ID']][latency_info['Destination ID']] += latency_info['Queueing Delay']
for i in range(_n):
for j in range(_n):
if _count[i][j] == 0 or _sum[i][j] == 0:
_mean_packet_delay[i][j] = 0
else:
if normalised:
_mean_packet_delay[i][j] = (_sum[i][j] / _count[i][j]) / _packet_duration
else:
_mean_packet_delay[i][j] = (_sum[i][j] / _count[i][j])
return pd.DataFrame(_mean_packet_delay, index=_rows, columns=_columns)
[docs] def ram_queue_summary(self):
"""
Summary of queue size in the RAMs.
Returns
-------
overall_mean : float
Overall mean value of the recorded queue size in the RAMs.
overall_max : float
Overall maximum value of the recorded queue size in the RAMs.
queue_size_summary : pandas DataFrame
A pandas DataFrame containing the following columns for each RAM in the simulator:
For unidirectional transmission:
- `Mean Queue Size`
- `Maximum Queue Size`
For bidirectional transmission:
- `Mean Queue Size (Upstream)`
- `Mean Queue Size (Downstream)`
- `Maximum Queue Size (Upstream)`
- `Maximum Queue Size (Downstream)`
"""
_n = self.simulator.model.network.num_nodes
mean_queue_size = []
max_queue_size = []
mean_queue_size_bi = []
max_queue_size_bi = []
_rows = [i for i in range(_n)]
df = pd.DataFrame(index=_rows)
for ram in self.simulator.RAM:
if self.simulator.bidirectional:
_upstream_queue_record = list(record[1] for record in ram.queue_size_record)
_downstream_queue_record = list(record[2] for record in ram.queue_size_record)
mean_queue_size_bi.append([np.mean(_upstream_queue_record), np.mean(_downstream_queue_record)])
max_queue_size_bi.append([np.max(_upstream_queue_record), np.max(_downstream_queue_record)])
mean_queue_size.append(np.mean(_upstream_queue_record + _downstream_queue_record))
max_queue_size.append(np.max(_upstream_queue_record + _downstream_queue_record))
else:
_queue_record = list(record[1] for record in ram.queue_size_record)
mean_queue_size.append(np.mean(_queue_record))
max_queue_size.append(np.max(_queue_record))
if self.simulator.bidirectional:
df[['Mean Queue Size (Upstream)', 'Mean Queue Size (Downstream)']] = pd.DataFrame(mean_queue_size_bi)
df['Mean Queue Size (Overall)'] = pd.DataFrame(mean_queue_size)
df[['Maximum Queue Size (Upstream)', 'Maximum Queue Size (Downstream)']] = pd.DataFrame(max_queue_size_bi)
df['Maximum Queue Size (Overall)'] = pd.DataFrame(max_queue_size)
else:
df['Mean Queue Size'] = pd.DataFrame(mean_queue_size)
df['Maximum Queue Size'] = pd.DataFrame(max_queue_size)
overall_mean = np.mean(list(mean for mean in mean_queue_size))
overall_max = np.max(list(mean for mean in max_queue_size))
return overall_mean, overall_max, df