now concho computes the price of the memory as well

- the empty machine price is now correct (the base memory is substracted)
- also displays the mem size on the graph now that all machines don't have the same amount of memory

nb : work involved for Bug 2886 - Devis pour l'achat d'un nouveau noeud pour le cluster (crédits TS 2020)
This commit is contained in:
Guillaume Raffy 2020-09-28 10:49:26 +02:00
parent 93a8b77231
commit 053d0f17ac
3 changed files with 111 additions and 29 deletions

View File

@ -17,6 +17,7 @@ class Chassis(Item):
self.num_cpu_slots_per_server = 2 self.num_cpu_slots_per_server = 2
if re.match('dell-poweredge-r9.*', uid): if re.match('dell-poweredge-r9.*', uid):
self.num_cpu_slots_per_server = 4 self.num_cpu_slots_per_server = 4
self.num_dimm_slots_per_channel = 2
class Dimm(Item): class Dimm(Item):
@ -34,7 +35,7 @@ class Cpu(Item):
super().__init__(proc_id) super().__init__(proc_id)
cpuTable = numpy.genfromtxt('cpu_table.dat', dtype=("|U32", float, int, float, float, float), names=True, delimiter='\t') cpuTable = numpy.genfromtxt('cpu_table.dat', dtype=("|U32", float, int, float, float, float), names=True, delimiter='\t')
for cpu_id, clock, num_cores, tdp, cpumark in zip(cpuTable['id'], cpuTable['clock'], cpuTable['num_cores'], cpuTable['tdp'], cpuTable['cpumark_1_cpu']): for cpu_id, clock, num_cores, tdp, cpumark in zip(cpuTable['id'], cpuTable['clock'], cpuTable['num_cores'], cpuTable['tdp'], cpuTable['cpumark_1_cpu']):
print(cpu_id) # print(cpu_id)
if cpu_id == proc_id: if cpu_id == proc_id:
# print('found '+procId) # print('found '+procId)
break break
@ -174,6 +175,15 @@ def get_simd_id(proc_arch):
'coffeelake':'avx2' 'coffeelake':'avx2'
}[proc_arch] }[proc_arch]
class MemChannel():
def __init__(self):
self.dimms = []
class CpuSlotMem():
def __init__(self):
self.mem_channels = []
class Config(): class Config():
@ -182,7 +192,8 @@ class Config():
self.num_servers = 0 self.num_servers = 0
self.num_cpu_per_server = 0 self.num_cpu_per_server = 0
self.cpu = None self.cpu = None
self.dimms_per_channel = [] self.cpu_slots_mem = []
@property @property
def chassis(self): def chassis(self):
@ -236,16 +247,39 @@ class Config():
# dimm_capacity = 16 # dimm_capacity = 16
dimm = self.configurator.get_dimm(dimm_capacity) dimm = self.configurator.get_dimm(dimm_capacity)
assert dimm assert dimm
self.dimms_per_channel = [ dimm ] print(cpu.uid, cpu.num_cores, ram_per_channel)
for cpu_slot_mem in self.cpu_slots_mem:
for mem_channel in cpu_slot_mem.mem_channels:
for dimm_slot_index in range(self.configurator.chassis.item.num_dimm_slots_per_channel):
mem_channel.dimms[dimm_slot_index] = dimm
@property
def ram_size(self):
ram_size = 0
for cpu_slot_mem in self.cpu_slots_mem:
for mem_channel in cpu_slot_mem.mem_channels:
for dimm_slot_index in range(self.configurator.chassis.item.num_dimm_slots_per_channel):
dimm = mem_channel.dimms[dimm_slot_index]
if dimm is not None:
dimm = self.configurator.get_item(dimm.uid)
ram_size += self.num_servers * dimm.num_gb
return ram_size
@property
def ram_price(self):
ram_price = 0.0
for cpu_slot_mem in self.cpu_slots_mem:
for mem_channel in cpu_slot_mem.mem_channels:
for dimm_slot_index in range(self.configurator.chassis.item.num_dimm_slots_per_channel):
dimm = mem_channel.dimms[dimm_slot_index]
if dimm is not None:
dimm_price = self.configurator.get_item_price(dimm.uid)
ram_price += self.num_servers * dimm_price
return ram_price
def get_price(self): def get_price(self):
price = self.configurator.chassis.price price = self.configurator.chassis.price
price += self.num_servers * self.num_cpu_per_server * self.configurator.get_item_price(self.cpu.uid) price += self.num_servers * self.num_cpu_per_server * self.configurator.get_item_price(self.cpu.uid) + self.ram_price
for dimm in self.dimms_per_channel:
dimm_price = self.configurator.get_item_price(dimm.uid)
price += self.num_servers * self.num_cpu_per_server * self.cpu.num_ram_channels * dimm_price
# print("ram_price : %f € for %d dimms of %d Gb" % (ram_price, self.num_servers * self.num_cpu_per_server * cpu.num_ram_channels, dimm_capacity))
assert price > 0.0 assert price > 0.0
return price return price
@ -261,7 +295,21 @@ class Config():
def set_cpu(self, cpu): def set_cpu(self, cpu):
self.cpu = cpu self.cpu = cpu
# create the dimm slots
self.cpu_slots_mem = []
for cpu_index in range(self.num_cpu_per_server):
cpu_slot_mem = CpuSlotMem()
for channel_index in range(cpu.num_ram_channels):
mem_channel = MemChannel()
for dimm_slot_index in range(self.configurator.chassis.item.num_dimm_slots_per_channel):
mem_channel.dimms.append(None) # dimm slots are empty
cpu_slot_mem.mem_channels.append(mem_channel)
self.cpu_slots_mem.append(cpu_slot_mem)
@property
def num_cpus(self):
return self.num_cpu_per_server * self.num_servers
class Option(): class Option():
@ -305,6 +353,11 @@ class Configurator():
return dimm return dimm
assert False, 'failed to find an option for a dimm of capacity %d gb' % dimm_capacity assert False, 'failed to find an option for a dimm of capacity %d gb' % dimm_capacity
def get_item(self, item_uid):
for module in self.modules.values():
if item_uid in module.options:
return module.options[item_uid].item
def get_item_price(self, item_uid): def get_item_price(self, item_uid):
for module in self.modules.values(): for module in self.modules.values():
if item_uid in module.options: if item_uid in module.options:

View File

@ -487,11 +487,9 @@ class DellConfiguratorParser():
assert False, 'unhandled label : %s' % label assert False, 'unhandled label : %s' % label
return ram_options return ram_options
def _parse_base_config(self, html_root, configurator): @classmethod
base_config = Config(configurator) def _get_module_default_item(cls, module_label, html_root):
base_config.num_servers = configurator.chassis.item.max_num_servers module_root_element = DellConfiguratorParser._get_module(html_root, module_label)
base_config.num_cpu_per_server = 1
module_root_element = DellConfiguratorParser._get_module(html_root, 'Processeurs (Passage)')
assert module_root_element is not None assert module_root_element is not None
for option_root_element in module_root_element.xpath(".//div[@class='row']"): for option_root_element in module_root_element.xpath(".//div[@class='row']"):
label_elements = option_root_element.xpath(".//div[@class='option-selected ']") label_elements = option_root_element.xpath(".//div[@class='option-selected ']")
@ -499,15 +497,37 @@ class DellConfiguratorParser():
if len(label_elements) > 0: if len(label_elements) > 0:
label = label_elements[0].text_content().replace('\n', '') label = label_elements[0].text_content().replace('\n', '')
price = DellConfiguratorParser.price_str_as_float(option_root_element.xpath(".//div[@class='col-md-3 text-right option-price option-price-selected']")[0].text_content()) price = DellConfiguratorParser.price_str_as_float(option_root_element.xpath(".//div[@class='col-md-3 text-right option-price option-price-selected']")[0].text_content())
assert price == 0.0 assert price == 0.0, 'default items are expected to have a price of 0.0 € (%s s price is %f)' % (label, price)
# print(label, price) return label
assert False, 'failed to find the default item of module %s' % module_label
def _parse_base_config(self, html_root, configurator):
base_config = Config(configurator)
base_config.num_servers = configurator.chassis.item.max_num_servers
base_config.num_cpu_per_server = 1
# initialize cpu
item_label = DellConfiguratorParser._get_module_default_item('Processeurs (Passage)', html_root)
# Processeur Intel Xeon Silver 4208 2.1GHz,11M Cache,9.60GT/s, 2UPI,No Turbo, HT,8C/16T (85W) - DDR4-2400 # Processeur Intel Xeon Silver 4208 2.1GHz,11M Cache,9.60GT/s, 2UPI,No Turbo, HT,8C/16T (85W) - DDR4-2400
match = re.match(r'^Processeur Intel Xeon (?P<cpu_class>Silver|Gold|Platinium) (?P<cpu_number>[0-9][0-9][0-9][0-9][RLYU]?).*', label) match = re.match(r'^Processeur Intel Xeon (?P<cpu_class>Silver|Gold|Platinium) (?P<cpu_number>[0-9][0-9][0-9][0-9][RLYU]?).*', item_label)
assert match, 'unhandled label : %s' % label assert match, 'unhandled label : %s' % item_label
# print(match['cpu_class'], match['cpu_number']) # print(match['cpu_class'], match['cpu_number'])
cpu_id = "intel-xeon-%s-%s" % (match['cpu_class'].lower(), match['cpu_number'].lower()) cpu_id = "intel-xeon-%s-%s" % (match['cpu_class'].lower(), match['cpu_number'].lower())
base_config.set_cpu(Cpu(cpu_id)) base_config.set_cpu(Cpu(cpu_id))
assert base_config.cpu is not None
# initialize the default ram dimms
item_label = DellConfiguratorParser._get_module_default_item('Mémoires (Passage)', html_root)
# Mémoire 16 Go DDR4 à 2933MHz (1x16Go)
match = re.match(r'^Mémoire (?P<num_gb>[0-9]+) Go DDR4 à (?P<num_mhz>[0-9]+)MHz \((?P<num_dimms>[0-9]+)x(?P<num_gb2>[0-9]+)Go\)', item_label)
assert match, 'unhandled label : %s' % item_label
assert int(match['num_dimms']) == 1
assert match['num_gb'] == match['num_gb2']
# print(match['cpu_class'], match['cpu_number'])
cpu_slot_index = 0
mem_channel_index = 0
dimm_slot = 0
base_config.cpu_slots_mem[cpu_slot_index].mem_channels[mem_channel_index].dimms[dimm_slot] = Dimm(num_gb=int(match['num_gb']), num_mhz=int(match['num_mhz']), mem_type='rdimm')
return base_config return base_config
@ -570,7 +590,8 @@ class DellConfiguratorParser():
assert price_value_element is not None assert price_value_element is not None
base_price = DellConfiguratorParser.price_str_as_float(price_value_element.text_content()) base_price = DellConfiguratorParser.price_str_as_float(price_value_element.text_content())
assert base_price is not None assert base_price is not None
configurator.chassis.price = base_price - configurator.get_item_price(configurator.base_config.cpu.uid)
configurator.chassis.price = base_price - configurator.base_config.num_cpus * configurator.get_item_price(configurator.base_config.cpu.uid) - configurator.base_config.ram_price
class DellMatinfoConfigurator(Configurator): class DellMatinfoConfigurator(Configurator):
@ -583,5 +604,12 @@ class DellMatinfoConfigurator(Configurator):
def create_config(self): def create_config(self):
return copy.copy(self.base_config) # config = copy.deepcopy(self.base_config)
config = Config(self)
config.num_servers = self.base_config.num_servers
config.num_cpu_per_server = self.base_config.num_cpu_per_server
config.set_cpu(self.base_config.cpu)
config.cpu_slots_mem = copy.deepcopy(self.base_config.cpu_slots_mem)
return config

View File

@ -76,7 +76,7 @@ def plot_system_efficiency():
def getColorCodeFromItemLabel(label): def getColorCodeFromItemLabel(label):
generation=label[-1] generation=label[-1]
(model, proc_id) = re.split('_', label) (model, proc_id, ram_size) = re.split('_', label)
saturation = { saturation = {
'sandy bridge':0.0, 'sandy bridge':0.0,
'ivy bridge':0.2, 'ivy bridge':0.2,
@ -113,7 +113,7 @@ def plot_system_efficiency():
return matplotlib.colors.hsv_to_rgb((hue, saturation, value)) return matplotlib.colors.hsv_to_rgb((hue, saturation, value))
def get_marker_from_label(label): def get_marker_from_label(label):
(model, proc_id) = re.split('_', label) (model, proc_id, ram_size) = re.split('_', label)
return get_marker(proc_id) return get_marker(proc_id)
@ -125,14 +125,14 @@ def plot_system_efficiency():
dell.DellMatinfoConfigurator('rcrc1406676-4834664 - Cat2 Conf4 PowerEdge R640 - Dell.html')] dell.DellMatinfoConfigurator('rcrc1406676-4834664 - Cat2 Conf4 PowerEdge R640 - Dell.html')]
# dell.DellPowerEdgeR940()] # dell.DellPowerEdgeR940()]
for configurator in configurators: for configurator in configurators:
config = configurator.create_config()
for cpu in configurator.get_cpu_options(): for cpu in configurator.get_cpu_options():
if not cpu.architecture in ['coffeelake', 'skylake','cascadelake']: if not cpu.architecture in ['coffeelake', 'skylake','cascadelake']:
continue continue
item_label = numpy.append( item_label, config.chassis.uid + '_' + cpu.uid ) config = configurator.create_config()
config.set_cpu(cpu) config.set_cpu(cpu)
config.set_ram(ram_per_core=4.0e9) config.set_ram(ram_per_core=4.0e9)
config.num_cpu_per_server = config.configurator.chassis.item.num_cpu_slots_per_server config.num_cpu_per_server = config.configurator.chassis.item.num_cpu_slots_per_server
item_label = numpy.append( item_label, config.chassis.uid + '_' + cpu.uid + '_' + str(config.ram_size) + 'gb' )
# print('procOptionPrice', procOptionPrice) # print('procOptionPrice', procOptionPrice)
# config_price = procOptionPrice # config_price = procOptionPrice
# config_price += config.get_empty_price() # config_price += config.get_empty_price()
@ -146,6 +146,7 @@ def plot_system_efficiency():
# print('config.config.get_guarantee_price(5)', config.get_guarantee_price(5)) # print('config.config.get_guarantee_price(5)', config.get_guarantee_price(5))
# config_price += config.get_disk_upgrade_price(2.0e12) # config_price += config.get_disk_upgrade_price(2.0e12)
# print('config.get_disk_upgrade_price(2.0e12)', config.get_disk_upgrade_price(2.0e12)) # print('config.get_disk_upgrade_price(2.0e12)', config.get_disk_upgrade_price(2.0e12))
# print(item_label, config.get_price(), config.get_power_consumption())
item_price = numpy.append( item_price, config.get_price() ) item_price = numpy.append( item_price, config.get_price() )
item_power_consumption = numpy.append( item_power_consumption, config.get_power_consumption()) item_power_consumption = numpy.append( item_power_consumption, config.get_power_consumption())
# # print(hostTypeId, procId, item_power_consumption[-1]) # # print(hostTypeId, procId, item_power_consumption[-1])