summaryrefslogtreecommitdiff
path: root/plugins
diff options
context:
space:
mode:
authorLoic Guegan <manzerbredes@mailbox.org>2022-06-09 21:48:32 +0200
committerLoic Guegan <manzerbredes@mailbox.org>2022-06-09 21:48:32 +0200
commitc2e6aad09f893e4c8cb5cb9243c32a0d6d0d1e12 (patch)
treebebdb575f200c7ea75f3115a81deecd5b797c4ff /plugins
Init ESDS repository
Diffstat (limited to 'plugins')
-rw-r--r--plugins/__init__.py1
-rw-r--r--plugins/node_plugin.py29
-rw-r--r--plugins/operating_states.py66
-rw-r--r--plugins/power_states.py164
-rw-r--r--plugins/wireless_area.py71
5 files changed, 331 insertions, 0 deletions
diff --git a/plugins/__init__.py b/plugins/__init__.py
new file mode 100644
index 0000000..abb734a
--- /dev/null
+++ b/plugins/__init__.py
@@ -0,0 +1 @@
+__all__ = [ "node_plugin", "power_states" ]
diff --git a/plugins/node_plugin.py b/plugins/node_plugin.py
new file mode 100644
index 0000000..325ff8a
--- /dev/null
+++ b/plugins/node_plugin.py
@@ -0,0 +1,29 @@
+class NodePlugin:
+ """
+ Node plugins get register to the node API get notified when events occurs.
+ The call and return suffixes are used for methods that are called at the beginning
+ and the end, respectively, of API calls triggered by the node source code.
+
+ Changing this API could brake most of the node plugins.
+ """
+
+ def __init__(self,plugin_name,api):
+ self.api=api
+ self.plugin_name=plugin_name
+ api.plugin_register(self)
+
+ def on_send_call(self,interface,data,datasize,dst):
+ pass
+
+ def on_send_return(self,interface,data,datasize,dst,code):
+ pass
+
+ def on_receive_return(self,interface,data,start_at,end_at):
+ pass
+
+ def on_terminated(self):
+ pass
+
+ def log(self,msg):
+ self.api.log(self.plugin_name+"(NP) "+msg)
+
diff --git a/plugins/operating_states.py b/plugins/operating_states.py
new file mode 100644
index 0000000..400aa1b
--- /dev/null
+++ b/plugins/operating_states.py
@@ -0,0 +1,66 @@
+#!/usr/bin/env python
+
+from plugins.node_plugin import *
+
+######################
+# _ ____ ___ #
+# / \ | _ \_ _| #
+# / _ \ | |_) | | #
+# / ___ \| __/| | #
+# /_/ \_\_| |___| #
+# #
+######################
+
+# import plugins.operating_states as op
+# # Load the directional transition graph from graph.txt starting at the "vertex1" state
+# opstate=op.OperatingStates(api,"graph.txt","vertex1")
+# Format of the graph.txt file consists in one edge per line
+# that consists on the source vertex and destination vertex sperated by a space
+# As an example:
+# vertex1 vertex2
+# vertex1 vertex3
+# vertex3 vertex2
+# vertex2 vertex1
+#
+# opstate.register_callback(boom)
+# # On each state transition boom will be called as boom(src_state,dst_state)
+# # This way the boom callback can contains power_state transitions for examples
+# opstate.goto("vertex2") # works
+# opstate.goto("vertex3") # wont work
+# opstate.goto("vertex1") # work since we are on vertex2
+
+class OperatingStates(NodePlugin):
+ """
+ OperatingStates plugin
+ """
+ def __init__(self,api, state_file, initial_state):
+ self.transitions=list()
+ self.callbacks=list()
+ self.state=initial_state
+ with open(state_file) as fp:
+ for i, line in enumerate(fp):
+ self.transitions.append(line)
+ super().__init__("OperatingStates",api)
+
+ def goto(self,state):
+ if (self.state+" "+state) in self.transitions:
+ old_state=self.state
+ self.state=state
+ for c in self.callbacks:
+ c(old_state,state)
+ else:
+ self.log("Invalid transition "+self.state+" => "+state)
+
+ def get_state(self):
+ return(self.state)
+
+ def register_callback(self,callback):
+ """
+ The callback will be called on each state transition
+ Callback takes two arguments which are:
+ - The source state
+ - The destination state
+ """
+ self.callbacks.append(callback)
+
+
diff --git a/plugins/power_states.py b/plugins/power_states.py
new file mode 100644
index 0000000..c8e9d0e
--- /dev/null
+++ b/plugins/power_states.py
@@ -0,0 +1,164 @@
+#!/usr/bin/env python
+
+from plugins.node_plugin import *
+
+# PowerStates allows you to measure the energy consumption of a
+# node that go through several power states during the simulation
+# Two version of Powerstates is provided by mean of two classes:
+# - Powerstates: Allow you to set power to any user's defined values
+# - PowerstatesFromFile: Allow you to set power from states defined in a file
+
+######################
+# _ ____ ___ #
+# / \ | _ \_ _| #
+# / _ \ | |_) | | #
+# / ___ \| __/| | #
+# /_/ \_\_| |___| #
+# #
+######################
+
+# #Regarding PowerStates:
+# import Powerstates as ps
+# pstates=ps.PowerStates(<node>,<power_init>)
+# pstates.set_power(<power>) # Switch the power consumption to <power>
+# pstates.report_energy() # Display the current node energy consumption up to the current simulated time
+# pstates.report_power_changes() # Display all the power changes up to the current simulated time
+
+# #Regarding PowerStatesFromFile:
+# #Format of <file> is one <entry> per line that follow this format <state-0>:<state-1>:...:<state-n>
+# #Each line can corresponds to one node
+# import Powerstates as ps
+# pstates=ps.PowerStatesFromFile(<node>,<file>,<entry-line>) # Create a power states on node <node> using line <entry-line> of file <file>
+# pstates.set_state(<id>) # Switch to the <id> power states
+# pstates.report_energy() # Display the current node energy consumption up to the current simulated time
+# pstates.report_power_changes() # Display all the power changes up to the current simulated time
+# pstates.report_state_changes() # Display all the states changes up to the current simulated time
+
+
+class PowerStates(NodePlugin):
+ """
+ PowerStates model the energy consumed by the various changes of power consumption of a node over time.
+ """
+ def __init__(self,node,power_init):
+ self.node=node
+ self.clock=self.node.clock()
+ self.energy=0
+ self.power=power_init
+ self.power_changes=dict()
+ self.set_power(power_init)
+ super().__init__("Powerstates",api)
+
+
+ def set_power(self,power_watt):
+ cur_clock=self.node.clock()
+ self.energy+=self.power*(cur_clock-self.clock)
+ self.clock=cur_clock
+ if self.power != power_watt:
+ self.power_changes[cur_clock]=power_watt
+ self.power=power_watt
+ return cur_clock
+
+ def report_energy(self):
+ self.set_power(self.power)
+ self.node.log("[PowerStates Plugin] Consumed "+str(self.energy) +"J")
+
+ def report_power_changes(self):
+ self.set_power(self.power)
+ for key in self.power_changes.keys():
+ self.node.log("[PowerStates Plugin] At t="+str(key)+" power is "+str(self.power_changes[key])+"W")
+
+
+
+class PowerStatesFromFile(PowerStates):
+ """
+ A version of Powerstates that load the power values from a file.
+ """
+ def __init__(self,node,state_file,entry_line=1):
+ self.node=node
+ self.state_changes=dict()
+ self.states=[]
+ self.state=0
+ with open(state_file) as fp:
+ for i, line in enumerate(fp):
+ if i+1 == entry_line:
+ self.states=line.split(":")
+ self.states=[float(i) for i in self.states]
+ assert len(self.states) > 0
+ super().__init__(node,self.states[0])
+ self.set_state(0)
+
+ def set_state(self,state_id):
+ assert state_id < len(self.states)
+ clock=super().set_power(self.states[state_id])
+ if self.state != state_id:
+ self.state_changes[clock]=state_id
+ self.state=state_id
+
+
+ def report_state_changes(self):
+ self.set_state(self.state)
+ for key in self.state_changes.keys():
+ self.node.log("[PowerStates Plugin] At t="+str(key)+" state is "+str(self.state_changes[key]))
+
+
+class PowerStatesComms(NodePlugin):
+ """
+ Monitor the energy consumed by the network interfaces by mean of power states.
+ Note that for finer grained predictions, bytes and packet power consumption must be accounted.
+ Which is not the case with these power states.
+ """
+
+ def __init__(self,api):
+ super().__init__("PowerStatesComms",api)
+ self.energy_dynamic=0.0 # Store the dynamic part of the energy consumption
+ self.power=dict() # Store the power states
+ self.tx_clock=0 # Dynamic clock (store the time at which a the last tx starts
+ self.idle_clock=api.clock() # Store the start time (to compute the idle part of the energy consumption)
+
+ def on_receive_return(self,interface,data,start_at,end_at):
+ duration=float(end_at)-float(start_at)
+ self.energy_dynamic+=self.power[interface]["rx"]*duration
+
+ def on_send_call(self,interface,data,datasize,dst):
+ self.tx_clock=self.api.clock()
+
+ def on_send_return(self,interface,data,datasize,dst,code):
+ clock=self.api.clock()
+ duration=(clock-float(self.tx_clock))
+ self.energy_dynamic+=self.power[interface]["tx"]*duration
+ self.tx_clock=clock # Any value could be use here
+
+ def set_power(self,interface,idle,tx,rx):
+ self.power[interface]=dict()
+ self.power[interface]["idle"]=idle
+ self.power[interface]["rx"]=rx
+ self.power[interface]["tx"]=tx
+
+ def get_idle(self):
+ clock=self.api.clock()
+ idle=0
+ for interface in self.power.keys():
+ idle+=(clock-self.idle_clock)*self.power[interface]["idle"]
+ return idle
+
+ def get_receive_queue_energy(self,interface):
+ """
+ Not that call to on_receive_return may not have happened yet (or never).
+ Thus we should manually compute the energy consumption stored in each queues of the node.
+ """
+ energy=0
+ # For each interface we should check if there is received data that has not been consumed
+ for data in list(self.api["interfaces"][interface].queue):
+ start_at=float(data[1])
+ end_at=float(data[2])
+ energy+=(end_at-start_at)*self.power[interface]["rx"]
+ return energy
+
+ def get_energy(self):
+ queue_energy=0
+ for interface in self.power.keys():
+ queue_energy+=self.get_receive_queue_energy(interface)
+ return self.get_idle()+self.energy_dynamic+queue_energy
+
+ def report_energy(self):
+ self.log("Communications consumed "+str(round(self.get_energy(),2))+"J")
diff --git a/plugins/wireless_area.py b/plugins/wireless_area.py
new file mode 100644
index 0000000..b2e2432
--- /dev/null
+++ b/plugins/wireless_area.py
@@ -0,0 +1,71 @@
+import math
+import numpy as np
+
+class WirelessArea:
+
+ def __init__(self):
+ self.nodes=list()
+
+ def dump_nodes(self):
+ i=0
+ for node in self.nodes:
+ x,y,z,com_range=node
+ print("Node {} at ({},{},{}) with a communication range of {}m".format(i,x,y,z,com_range))
+ i+=1
+
+ def dump_infos(self):
+ print("Number of nodes {}".format(len(self.nodes)))
+ adjacency=self.generate_adjacency_matrix(fill_diagonal=False)
+ print("Nodes average degree is {}".format(np.mean(np.sum(adjacency,axis=0))))
+ x = [node[0] for node in self.nodes]
+ y = [node[1] for node in self.nodes]
+ z = [node[2] for node in self.nodes]
+ com_range = [node[3] for node in self.nodes]
+ print("Nodes locations ranges: x in [{},{}] y in [{},{}] z in [{},{}]".format(min(x),max(x),min(y),max(y),min(z),max(z)))
+ print("Node communication ranges in [{},{}]".format(min(com_range),max(com_range)))
+
+ def add_node(self,x,y,z,com_range):
+ self.nodes.append((x,y,z,com_range))
+
+ def get_neighbours(self,node_id):
+ node=self.nodes[node_id]
+ neighbours=list()
+ for i in range(0,len(self.nodes)):
+ if i != node_id:
+ neighbour=self.nodes[i]
+ if math.dist(node[0:3],neighbour[0:3]) <= node[3]:
+ neighbours.append(i)
+ return neighbours
+
+ def generate_dot(self,filepath):
+ is_strict=False
+ com_range=self.nodes[0][3]
+ for node in self.nodes:
+ if node[3] != com_range:
+ is_strict=True
+ break
+
+ with open(filepath, "w") as f:
+ if is_strict:
+ f.write("digraph G {\n")
+ else:
+ f.write("strict graph G {\n")
+ for i in range(0,len(self.nodes)):
+ neighbours=self.get_neighbours(i)
+ for n in neighbours:
+ if is_strict:
+ f.write("{}->{}\n".format(i,n))
+ else:
+ f.write("{}--{}\n".format(i,n))
+ f.write("}")
+
+ def generate_adjacency_matrix(self,fill_diagonal=True):
+ matrix=np.full((len(self.nodes),len(self.nodes)),0)
+ if fill_diagonal:
+ np.fill_diagonal(matrix,1) # Required by ESDS
+ for i in range(0,len(self.nodes)):
+ neighbours=self.get_neighbours(i)
+ for n in neighbours:
+ matrix[i,n]=1
+ return matrix
+