Adding first part of codex simulator

This commit is contained in:
Leonardo Bautista-Gomez 2022-06-22 16:30:09 +02:00
parent 9b94462fe0
commit cbab3166e9

287
simulator/codex.py Normal file
View File

@ -0,0 +1,287 @@
#!/bin/python3
import os, time, json, hashlib
import random as random
from datetime import datetime
import matplotlib.pyplot as plt
def plotHistoricData(data, xLabel, yLabel, filename):
plt.clf()
plt.plot(data)
plt.grid(True)
plt.ylabel(yLabel)
plt.xlabel(xLabel)
plt.savefig(filename)
print("Figure %s created." % filename)
def plotStackedBars(nbBars, data, legend, xLabel, yLabel, filename):
plt.clf()
for i in range(nbBars):
if i == 0:
plt.bar(range(len(data[0])), data[0], label=legend[i])
else:
plt.bar(range(len(data[i])), data[i], label=legend[i], bottom=data[i-1])
plt.legend(loc="upper left")
plt.grid(True)
plt.ylabel(yLabel)
plt.xlabel(xLabel)
plt.savefig(filename)
print("Figure %s created." % filename)
def plotPieChart(data, legend, filename):
plt.clf()
plt.pie(data, labels=legend, autopct='%1.1f%%', shadow=True, startangle=90)
plt.savefig(filename)
plt.axis("equal")
print("Figure %s created." % filename)
class Config:
def __init__(self):
self.maxStoragePerNode = 9 # in Terabytes
self.maxFileSize = 111028 # in Megabytes
self.minFileSize = 1 # in Megabytes
self.defaultECN = 128 # in number of blocks
self.minNbNodes = 32 # in nodes
self.simulationLength = 9 # in days
self.meanUsage = 99 # in %
class Tracker:
def __init__(self):
self.storageUsed = []
self.storageAvailable = []
self.nbNodes = []
self.nbFiles = []
class Block():
def __init__(self, fileID, blockID, size):
self.fileID = fileID
self.blockID = blockID
self.size = size
class Node():
def __init__(self, ID, MaxStorageNode):
self.storage = random.random() * MaxStorageNode * 1024 * 1024 # Storage in MBs
self.available = self.storage
self.ID = ID
self.used = 0
self.latency = random.random() * 100 # Latency score
self.blocks = []
def storeBlock(self, fileID, blockID, size):
self.used = self.used + size
self.available = self.storage - self.used
#block = Block(fileID, blockID, size)
#self.blocks.append(block) # Too much memory
class File:
def __init__(self, ID, network):
self.ID = ID #hashlib.sha256(str(ID).encode('utf-8')).hexdigest()
self.size = random.random() * network.config.maxFileSize
ECN = network.config.defaultECN
if network.nbNodes < ECN or self.size < 1:
self.ECK = int(ECN/8)
self.ECM = int(ECN/8)
self.ECN = int(ECN/4)
elif network.nbNodes < ECN*2:
self.ECK = int(ECN/4)
self.ECM = int(ECN/4)
self.ECN = int(ECN/2)
else:
self.ECK = int(ECN/2)
self.ECM = int(ECN/2)
self.ECN = int(ECN)
def disperse(self, network):
contracted = []
full = []
random.seed(time.time())
for blockID in range(self.ECN):
stored = 0
while(not stored):
r = random.randint(0, network.nbNodes-1)
if (r not in contracted):
if (network.nodes[r].available > self.size/self.ECK):
contracted.append(r)
stored = 1
else:
if (r not in full):
full.append(r)
#print("Not enough space on %i: %f available for %f" % (r, network.nodes[r].available, self.size/self.ECK))
else:
#print("Node %i already used for this file %s || contracted => %i full => %i" % (r, str(self.ID), len(contracted), len(full)))
if (len(contracted) + len(full) >= network.nbNodes):
break
if len(contracted) == self.ECN:
for r in contracted:
network.nodes[r].storeBlock(self.ID, blockID, self.size/self.ECK)
return 0
elif len(contracted) > self.ECN:
print("Warning %i contracted, needed %i." % (len(contracted), self.ECN))
else:
print("Warning %i contracted, needed %i." % (len(contracted), self.ECN))
#print("Contract could not be fulfilled, only %i nodes with enough space, %i needed." % (len(contracted), self.ECN))
if self.ECN == network.config.defaultECN/4: # Maximum ECN reduction reached
network.rejectedFiles = network.rejectedFiles + 1
return 0
elif self.ECN > network.config.defaultECN/4: # ECN should be reduced
return 1
else:
print("Warning, the ECN should have not reached this low")
return 0
def reduceECN(self):
if (self.ECN == network.config.defaultECN) or (self.ECN == network.config.defaultECN/2):
self.ECK = self.ECK/2
self.ECM = self.ECM/2
self.ECN = self.ECN/2
class Network():
def __init__(self, conf):
self.nodes = []
self.files = []
self.nbNodes = 0
self.nbFiles = 0
self.rejectedFiles = 0
self.nbBlocks = 0
self.storageProvided = 0
self.storageUsed = 0
self.storageAvailable = 0
self.config = conf
now = datetime.now()
self.name = "Codex-"+now.strftime("%y-%m-%d-%H-%M-%S")
def addNode(self):
node = Node(self.nbNodes, self.config.maxStoragePerNode)
self.nodes.append(node)
self.nbNodes = self.nbNodes + 1
self.storageProvided = self.storageProvided + node.storage
self.storageAvailable = self.storageAvailable + node.storage
def addFile(self):
file = File(self.nbFiles, self)
while (file.disperse(self)):
file.reduceECN
self.files.append(file)
self.nbFiles = self.nbFiles + 1
self.nbBlocks = self.nbBlocks + file.ECN
self.storageUsed = self.storageUsed + ((file.size/file.ECK)*file.ECN)
self.storageAvailable = self.storageAvailable - ((file.size/file.ECK)*file.ECN)
def trackStats(self, sec, tracker):
os.system("clear")
print("|==================================================================================|")
print("| Day | Hour | Nodes | Files | Rejected | Prov.(TBs) | Used(TBs) | Avai.(TBs) |")
print("|==================================================================================|")
print("| %04i | %02i:00 | %05i | %08i | %06i | %010.4f | %09.3f | %010.3f |" % (sec/(3600*24), (sec/3600)%24, self.nbNodes, self.nbFiles, self.rejectedFiles, self.storageProvided/(1024*1024), self.storageUsed/(1024*1024), self.storageAvailable/(1024*1024)))
print("|==================================================================================|")
tracker.storageUsed.append(self.storageUsed/(1024*1024))
tracker.storageAvailable.append(self.storageAvailable/(1024*1024))
tracker.nbNodes.append(self.nbNodes)
tracker.nbFiles.append(self.nbFiles/1000000)
def snapshot(self):
print("Creating a snapshot...")
nodesDict = []
for node in self.nodes:
blocksDict = []
#for block in node.blocks:
# blocksDict.append({"fileID" : block.fileID, "blockID" : block.blockID, "size" : block.size})
nodesDict.append({"nodeID" : node.ID, "storage" : node.storage, "used" : node.used, "available" : node.available, "latency": node.latency, "blocks": blocksDict})
network = {"Name" : "Codex", "Number of nodes" : self.nbNodes, "nodes" : nodesDict}
with open(self.name+"/Codex.json", "w") as outfile:
json.dump(network, outfile)
print("Snapshot Created.")
def makeReport(self):
output = "<html><head><title>Report "+self.name+"</title></head><body>\n"
output = output+"<h1>"+self.name+"</h1><br/>\n"
output = output+"<img src='nbNodes.png'><br/>\n"
output = output+"<img src='nbFiles.png'><br/>\n"
output = output+"<img src='storage.png'><br/>\n"
output = output+"<img src='storageNodes.png'><br/>\n"
output = output+"<img src='fileECN.png'><br/>\n"
output = output+"<a href='Codex.json'> JSON snapshot </a><br/>\n"
output = output+"</body></html>"
with open(self.name+"/index.html", "w") as outfile:
outfile.write(output)
outfile.close()
print("Report html writen.")
def plotData(self, tracker):
dir = os.path.join(os.getcwd(), self.name)
if not os.path.exists(dir):
os.mkdir(dir)
used = []
avai = []
print("Plotting historic data...")
self.nodes.sort(key=lambda x: x.used, reverse=True)
for node in self.nodes:
used.append(node.used/(1024*1024))
avai.append(node.available/(1024*1024))
print("Network is using %f TBs and has %f TBs available" % (self.storageUsed/(1024*1024), self.storageAvailable/(1024*1024)))
print("Network is using %f TBs and has %f TBs available" % (sum(used), sum(avai)))
ECNstats = [0] * self.config.defaultECN
for file in self.files:
ECNstats[file.ECN-1] = ECNstats[file.ECN-1] + 1
dECN = []
lECN = []
for i in range(len(ECNstats)):
if ECNstats[i] > 0:
dECN.append(ECNstats[i])
lECN.append(i+1)
print("Network has %i files, %i were treated" % (self.nbFiles, sum(dECN)))
plotHistoricData(tracker.nbNodes, "Hours", "Nb. of nodes", self.name+"/nbNodes.png")
plotHistoricData(tracker.nbFiles, "Hours", "Nb. of files (Millions)", self.name+"/nbFiles.png")
plotStackedBars(2, [tracker.storageUsed, tracker.storageAvailable], ["Used", "Available"], "Hours", "Storage (TBs)", self.name+"/storage.png")
plotStackedBars(2, [used, avai], ["Used", "Available"], "Nodes", "Storage (TBs)", self.name+"/storageNodes.png")
plotPieChart(dECN, lECN, self.name+"/fileECN.png")
self.makeReport()
self.snapshot()
def run():
# Initialization
config = Config()
network = Network(config)
tracker = Tracker()
# Main Loop
print("Starting simulation %s..." % network.name)
for sec in range(60*60*24*config.simulationLength): # X days in seconds
# Adding storage nodes to the network
if (network.nbNodes < (network.config.minNbNodes)): # Bootstrap a number of nodes
network.addNode()
else:
if ((network.storageUsed * 100 / network.storageProvided) > config.meanUsage): # Only add more storage if X% in use already
dice = random.random() * 100
if (dice < (network.storageUsed * 100 / network.storageProvided)): # Less space avilable more likelihood of incoming nodes
network.addNode()
# Uploading files to the network
if (network.storageProvided - network.storageUsed > network.config.maxFileSize) and (network.nbNodes >= network.config.minNbNodes): # if enough space and enough storage providers
network.addFile()
if (sec % 3600 == 0): # Every hour
network.trackStats(sec, tracker)
print("Simulation %s finished." % network.name)
# Plot Data
# network.plotData(tracker)
print("Done.")
run()