2020-09-15 18:46:53 +02:00
# -*- coding: utf-8 -*-
import numpy
import pylab
import matplotlib . pyplot as plt
import matplotlib . colors
import itertools
import re
import hashlib
from string import ascii_lowercase
2020-09-18 10:38:24 +02:00
from abc import ABCMeta , abstractmethod
2020-09-15 18:46:53 +02:00
markerTypes = [ ' , ' , ' + ' , ' . ' , ' ^ ' , ' v ' , ' < ' , ' > ' , ' o ' , ' * ' , ' 1 ' , ' 2 ' , ' 3 ' , ' 4 ' , ' 8 ' , ' s ' , ' p ' , ' h ' , ' H ' , ' x ' , ' X ' , ' D ' , ' d ' , ' | ' , ' _ ' ]
#for c in ascii_lowercase:
# markerTypes.append('$%s$' % c)
#markerColors=('r', 'g', 'b')
markerColors = ( ' r ' )
def get_marker ( proc_id ) :
hash_object = hashlib . md5 ( proc_id . encode ( ' utf-8 ' ) )
hash = int ( hash_object . hexdigest ( ) , 16 )
return markerTypes [ hash % len ( markerTypes ) ]
def plotCpuPassmark ( ) :
cpuTable = numpy . genfromtxt ( ' cpu_table.dat ' , dtype = ( " |U10 " , float , int , float , float ) , names = True , delimiter = ' \t ' )
plt . subplot ( 1 , 1 , 0 )
plt . subplots_adjust ( bottom = 0.1 )
markersCycler = itertools . cycle ( itertools . product ( markerTypes , markerColors ) )
labels = cpuTable [ ' id ' ]
x = cpuTable [ ' clock ' ] * cpuTable [ ' num_cores ' ]
y = cpuTable [ ' cpumark ' ]
markerSize = 50
color = ' b '
for label , x1 , y1 in zip ( labels , x , y ) :
if y1 < = 0.0 :
continue # no passmark available fo this data
generation = label [ - 1 ]
if generation == ' 2 ' :
color = ' b '
else :
color = ' r '
marker = markersCycler . next ( )
plt . scatter ( x1 , y1 , color = color , s = markerSize , marker = marker [ 0 ] , label = label )
plt . xlabel ( u ' theoretical cpu speed [core.GHz] ' )
plt . ylabel ( u ' passmark [?] ' )
plt . title ( u ' comparison between cpu theoretical and effective speed ' )
plt . xlim ( xmin = 0.0 )
plt . ylim ( ymin = 0.0 )
plt . legend ( bbox_to_anchor = ( 0.2 , 1.0 ) )
#plt.legend()
plt . draw ( )
plt . show ( )
2020-09-18 10:38:24 +02:00
class Cpu ( ) :
def __init__ ( self , proc_id ) :
cpuTable = numpy . genfromtxt ( ' cpu_table.dat ' , dtype = ( " |U15 " , float , int , float , float , float ) , names = True , delimiter = ' \t ' )
for id , clock , num_cores , tdp , cpumark in zip ( cpuTable [ ' id ' ] , cpuTable [ ' clock ' ] , cpuTable [ ' num_cores ' ] , cpuTable [ ' tdp ' ] , cpuTable [ ' cpumark_1_cpu ' ] ) :
if id == proc_id :
# print('found '+procId)
break
assert id == proc_id , ' Failed to find %s in cputable ' % proc_id
self . proc_id = proc_id
self . clock = clock
self . num_cores = num_cores
self . tdp = tdp
self . cpumark = cpumark
@property
def architecture ( self ) :
proc_id = self . proc_id
if re . match ( ' core-i[357]-8[0-9][0-9][0-9][ktbuh] ' , proc_id ) :
return ' coffeelake '
elif re . match ( ' Silver-[0-9]2[0-9][0-9] ' , proc_id ) :
return ' cascadelake '
elif re . match ( ' Gold-[0-9]2[0-9][0-9] ' , proc_id ) :
return ' cascadelake '
elif re . match ( ' Platinum-[0-9]2[0-9][0-9] ' , proc_id ) :
return ' cascadelake '
elif re . match ( ' Gold-[0-9]1[0-9][0-9] ' , proc_id ) :
return ' skylake '
elif re . match ( ' Platinum-[0-9]1[0-9][0-9] ' , proc_id ) :
return ' skylake '
elif re . match ( ' E5-26[0-9][0-9][LWA]*v4 ' , proc_id ) :
return ' broadwell '
elif re . match ( ' E5-26[0-9][0-9][LWA]*v3 ' , proc_id ) :
return ' haswell '
elif re . match ( ' E5-26[0-9][0-9][LWA]*v2 ' , proc_id ) :
return ' ivy bridge '
elif re . match ( ' E5-26[0-9][0-9][LWA]* ' , proc_id ) :
return ' sandy bridge '
elif re . match ( ' X56[0-9][0-9] ' , proc_id ) :
return ' gulftown '
elif re . match ( ' X55[0-9][0-9] ' , proc_id ) :
return ' gainestown '
elif re . match ( ' E54[0-9][0-9] ' , proc_id ) :
return ' harpertown '
elif re . match ( ' 51[0-9][0-9] ' , proc_id ) :
return ' woodcrest '
else :
assert False
2020-09-15 18:46:53 +02:00
2020-09-18 10:38:24 +02:00
@property
def num_dp_flop_per_cycle ( self ) :
proc_arch = self . architecture
simd_id = get_simd_id ( proc_arch )
num_simd_per_core = 1
if proc_arch == ' skylake ' or proc_arch == ' cascadelake ' :
# from https://en.wikipedia.org/wiki/List_of_Intel_Xeon_microprocessors : Xeon Platinum, Gold 61XX, and Gold 5122 have two AVX-512 FMA units per core; Xeon Gold 51XX (except 5122), Silver, and Bronze have a single AVX-512 FMA unit per core
if re . match ( ' Gold-5122 ' , self . proc_id ) :
num_simd_per_core = 2
# https://en.wikichip.org/wiki/intel/xeon_gold/5222 : 'Note that this is the only processor in the Xeon Gold 52xx series with two 512b FMA units.'
if re . match ( ' Gold-5222 ' , self . proc_id ) :
num_simd_per_core = 2
if re . match ( ' Gold-61[0-9][0-9] ' , self . proc_id ) :
num_simd_per_core = 2
if re . match ( ' Gold-62[0-9][0-9] ' , self . proc_id ) :
num_simd_per_core = 2
dp_flops_per_cycle = num_simd_per_core * simd_id_to_dp_flops_per_cycle ( simd_id )
print ( self . proc_id , dp_flops_per_cycle )
return dp_flops_per_cycle
@property
def num_ram_channels ( self ) :
return {
' skylake ' : 6 ,
' coffeelake ' : 6 ,
' cascadelake ' : 6
} [ self . architecture ]
def get_proc_architecture ( proc_id ) :
return Cpu ( proc_id ) . architecture
def get_proc_arch_transistor_size ( proc_id ) :
2020-09-15 18:46:53 +02:00
return {
' woodcrest ' : 65 ,
' harpertown ' : 45 ,
' gainestown ' : 45 ,
' gulftown ' : 32 ,
' sandy bridge ' : 32 ,
' ivy bridge ' : 22 ,
' haswell ' : 22 ,
' broadwell ' : 14 ,
' skylake ' : 14 ,
' coffeelake ' : 14 ,
' cascadelake ' : 14
2020-09-18 10:38:24 +02:00
} [ get_proc_architecture ( proc_id ) ]
2020-09-15 18:46:53 +02:00
def simd_id_to_dp_flops_per_cycle ( simd_id ) :
"""
: param str simd_id : eg ' avx2 '
"""
# from https://stackoverflow.com/questions/15655835/flops-per-cycle-for-sandy-bridge-and-haswell-sse2-avx-avx2
# Intel Core 2 and Nehalem:
#
# 4 DP FLOPs/cycle: 2-wide SSE2 addition + 2-wide SSE2 multiplication
# 8 SP FLOPs/cycle: 4-wide SSE addition + 4-wide SSE multiplication
#
# Intel Sandy Bridge/Ivy Bridge:
#
# 8 DP FLOPs/cycle: 4-wide AVX addition + 4-wide AVX multiplication
# 16 SP FLOPs/cycle: 8-wide AVX addition + 8-wide AVX multiplication
#
# Intel Haswell/Broadwell/Skylake/Kaby Lake:
#
# 16 DP FLOPs/cycle: two 4-wide FMA (fused multiply-add) instructions
# 32 SP FLOPs/cycle: two 8-wide FMA (fused multiply-add) instructions
return {
' sse4.1 ' : 4 ,
' sse4.2 ' : 4 ,
' avx ' : 8 ,
' avx2 ' : 16 ,
' avx-512 ' : 16 ,
} [ simd_id ]
def get_simd_id ( proc_arch ) :
"""
: param str proc_arch : eg ' broadwell '
: return str : eg ' sse4 '
"""
return {
' woodcrest ' : ' sse4.1 ' ,
' harpertown ' : ' sse4.1 ' ,
' gainestown ' : ' sse4.2 ' ,
' gulftown ' : ' sse4.2 ' ,
' sandy bridge ' : ' avx ' ,
' ivy bridge ' : ' avx ' ,
' haswell ' : ' avx2 ' ,
' broadwell ' : ' avx2 ' ,
' skylake ' : ' avx-512 ' ,
' cascadelake ' : ' avx-512 ' ,
' coffeelake ' : ' avx2 '
} [ proc_arch ]
2020-09-18 10:38:24 +02:00
class HostType ( ) :
def __init__ ( self , host_type_id , num_cpu_per_server , num_servers = 1 ) :
self . host_type_id = host_type_id
self . num_cpu_per_server = num_cpu_per_server
self . num_servers = num_servers
@abstractmethod
def get_empty_price ( self ) :
pass
@abstractmethod
def get_dimm_price ( self , dimm_capacity ) :
pass
@abstractmethod
def get_guarantee_price ( self , guarantee_duration ) :
pass
@abstractmethod
def get_disk_upgrade_price ( self , disk_capacity ) :
pass
class DellPowerEdgeC6220 ( HostType ) :
def __init__ ( self , host_type_id ) :
super ( ) . __init__ ( host_type_id , num_cpu_per_server = 2 , num_servers = 4 )
def get_empty_price ( self ) :
return 4890.0
def get_guarantee_price ( self , guarantee_duration ) :
assert guarantee_duration < = 5 , ' only 5 year guarantee is handled for %s ' % self . host_type_id
return 880.0
def get_disk_upgrade_price ( self , asked_disk_capacity ) :
assert 1.9e12 < asked_disk_capacity < 2.1e12 , ' only 2To upgrades are handled for %s ' % self . host_type_id
return 320.0
class DellPowerEdgeR620 ( HostType ) :
def __init__ ( self , host_type_id ) :
super ( ) . __init__ ( host_type_id , num_cpu_per_server = 2 , num_servers = 1 )
def get_empty_price ( self ) :
return 860.0
def get_guarantee_price ( self , guarantee_duration ) :
assert guarantee_duration < = 5 , ' only 5 year guarantee is handled for %s ' % self . host_type_id
return 240.0
def get_disk_upgrade_price ( self , asked_disk_capacity ) :
assert 1.9e12 < asked_disk_capacity < 2.1e12 , ' only 2To upgrades are handled for %s ' % self . host_type_id
return - 20.0 * self . num_servers
class DellPowerEdgeR630 ( HostType ) :
def __init__ ( self , host_type_id ) :
super ( ) . __init__ ( host_type_id , num_cpu_per_server = 2 , num_servers = 1 )
def get_empty_price ( self ) :
# for r630 on 14/10/2016
# (x: price without procs, p2603: price of e5-2603v4, p2609: price of e5-2609v4)
# we want to know x, given dell's web site, where we can get the price for multiple proc but not 0
# x + p2603 = 948.0
# x + 2 * p2603 = 948.0 + 216
# => p2603 approx= 215.5
# => x = 948. - 215. = 733.0
# verification :
# x + p2609 = 1057.0
# => p2609 = 1057-733=324.0
# x + 2 * p2609 = 1381.0
return 733.0
def get_guarantee_price ( self , guarantee_duration ) :
assert guarantee_duration < = 5 , ' only 5 year guarantee is handled for %s ' % self . host_type_id
return 240.0
def get_disk_upgrade_price ( self , asked_disk_capacity ) :
assert 1.9e12 < asked_disk_capacity < 2.1e12 , ' only 2To upgrades are handled for %s ' % self . host_type_id
return 0.0 * self . num_servers
class DellPowerEdgeR730 ( HostType ) :
def __init__ ( self , host_type_id ) :
super ( ) . __init__ ( host_type_id , num_cpu_per_server = 2 , num_servers = 1 )
def get_empty_price ( self ) :
# for r730 on 06/10/2016
# (x: price without procs, p1 : price of e5-2603v4, p2: price of e5-2609v4)
# we want to know x, given dell's web site, where we can get the price for multiple proc but not 0
# x + p1 = 1014.0
# x + 2 * p1 = 1014.0 + 216
# => p1 approx= 215.5
# => x = 1014. - 215. = 799.0
# x + p2 = 1123.0
# => p2 = 324.0
# x + 2 * p2 = 1447.0
return 799.0
def get_guarantee_price ( self , guarantee_duration ) :
assert guarantee_duration < = 5 , ' only 5 year guarantee is handled for %s ' % self . host_type_id
return 240.0
def get_disk_upgrade_price ( self , asked_disk_capacity ) :
assert 1.9e12 < asked_disk_capacity < 2.1e12 , ' only 2To upgrades are handled for %s ' % self . host_type_id
return 0.0 * self . num_servers
class DellPowerEdgeC4130 ( HostType ) :
def __init__ ( self , host_type_id ) :
super ( ) . __init__ ( host_type_id , num_cpu_per_server = 2 , num_servers = 1 )
def get_empty_price ( self ) :
# for c4130 on 14/10/2016
# x + 2 x E5-2640v4 + 128G + 2 * K80 + X520 + p5years = 12281€
# x + 2 x E5-2640v4 + 128G + 4 * K80 + X520 + p5years = 19317€
# price of a K80
# >>> (19317.-12281)/2
# 3518.0
# assuming the options cost the same as for R630 (X520=210€, p5years=240€, 128G=1778€, E5-2640v4=951€), the cost of the base system is :
# >>> 12281-951-951-1778-210-240-3518-3518
# 1115
# but if we integrate the X520 card so that we have a 10Gb ethernet in the base, the cost of the base system becomes :
# >>> 1115+210
# 1325
return 1325.0
def get_guarantee_price ( self , guarantee_duration ) :
assert guarantee_duration < = 5 , ' only 5 year guarantee is handled for %s ' % self . host_type_id
return 240.0
def get_disk_upgrade_price ( self , asked_disk_capacity ) :
assert 1.9e12 < asked_disk_capacity < 2.1e12 , ' only 2To upgrades are handled for %s ' % self . host_type_id
return 0.0 * self . num_servers
class DellPowerEdgeC6320 ( HostType ) :
def __init__ ( self , host_type_id ) :
super ( ) . __init__ ( host_type_id , num_cpu_per_server = 2 , num_servers = 4 )
def get_empty_price ( self ) :
# for 4xc6320 on 14/10/2016
# (x: price without procs, p2603: price of e5-2603v4, p2609: price of e5-2609v4)
# x + 4 x (2 x p2620 + p32G) = 5135 € HT
# x + 4 x (2 x p2640 + p128G + pX520 + p5years) = 15590 € HT
# x + 4 x (2 x p2650 + p128G + pX520 + p5years) = 17340 € HT
# x + 4 x (2 x p2660 + p128G + pX520 + p5years) = 19490 € HT
# by examining this and the price of processors on R630
# - E5-2620v4 : 458€
# - E5-2640v4 : 951€
# - E5-2650v4 : 1209€
# - E5-2660v4 : 1525€
# - E5-2680v4 : 1867€
# - E5-2690v4 : 2261€
# I could work out that :
# - the price of procs on c6320 is the price of procs on r630 * 85%
# - the price of the base c6320 with 32 Go and no proc at all is 2020.6
# - the price of the 32G to 128G upgrade is 6222.6 euros (cheaper price of 16G->128G upgrade on r630 : (1778*4 = 7112))
# details :
# >>> (19490.-17340)/8
# 268.75
# >>> (17340.-15590)/8
# 218.75
# >>> 218.75/258.
# 0.8478682170542635
# >>> 268.75/316
# 0.8504746835443038
# >>> 15590.0+((1209.0-951.0)*0.85)*8
# 17344.4
# >>> 15590.0+((1525.0-951.0)*0.85)*8
# 19493.2
# price of 128G ram upgrade assuming that 5years guarantee costs 880€ (same as c6220),
# >>> 15590.0+((458.0-951.0)*0.85)*8-210.0*4-880.0 - 5135.0
# 6222.6
# >>> 5135.0 - (458.0*0.85)*8
# 2020.6
return 2020.6
def get_guarantee_price ( self , guarantee_duration ) :
assert guarantee_duration < = 5 , ' only 5 year guarantee is handled for %s ' % self . host_type_id
return 880.0
def get_disk_upgrade_price ( self , asked_disk_capacity ) :
assert 1.9e12 < asked_disk_capacity < 2.1e12 , ' only 2To upgrades are handled for %s ' % self . host_type_id
return 0.0 * self . num_servers
class DellPowerEdgeR640 ( HostType ) :
def __init__ ( self , host_type_id ) :
super ( ) . __init__ ( host_type_id , num_cpu_per_server = 2 , num_servers = 1 )
def get_empty_price ( self ) :
# on 29/09/2017
# (x: price without procs, p3106: price of Bronze-3106, p6126: price of Gold6126)
# we want to know x, given dell's web site, where we can get the price for multiple proc but not 0
# x + p3106 = 1067.0
# x + 2 * p3106 = 1067.0 + 320.0
# => p3106 = 320
# => x = 1067.0 - 320.0 = 747.0
# check if x computation is consistent with p6126
# x + p6126 = 2767
# x + 2 * p6126 = 4787.0
# => p6126 = 2020.0
# => x = 747.0 --> yes !
return 747.0
def get_dimm_price ( self , dimm_capacity ) :
return {
8 : 80.0 ,
16 : 160.0 ,
32 : 320.0 ,
64 : 640.0
} [ dimm_capacity ]
def get_guarantee_price ( self , guarantee_duration ) :
if guarantee_duration > 7 :
assert False , ' guarantee of more than 7 years is not available on %s ' % self . host_type_id
elif guarantee_duration > = 5 :
return 270.0 # from dell matinfo4 online quotation
else :
# 5-year guarantee included in base price
return 0.0 * self . num_servers
@abstractmethod
def get_disk_upgrade_price ( self , asked_disk_capacity ) :
assert 1.9e12 < asked_disk_capacity < 2.1e12 , ' only 2To upgrades are handled for %s ' % self . host_type_id
# Retrait des disques de base (2x600Go 10K SAS 2.5'') : -260.0 €
# Ajout d'un disque dur 1,2 To SAS 10k Tpm 2,5" - hotplug : 165.0 €
base_disks_removal_price = - 260.0
disk_1200g_price = 165.0
return ( base_disks_removal_price + disk_1200g_price * 2 ) * self . num_servers
class DellPowerEdgeR940 ( HostType ) :
def __init__ ( self , host_type_id ) :
super ( ) . __init__ ( host_type_id , num_cpu_per_server = 4 , num_servers = 1 )
def get_empty_price ( self ) :
# price of r940 (with 2x xeon gold 5215 and 32 Go DDR4 @ 2933GHz) on 09/06/2020 : 3784€
# (x: price without procs, p5215: price of gold-5215, p6248: price of Gold6248)
# p6240 = 2684
# p6248 = 3442
# p8280l = 12075
# x + 2 * p5215 = 3784
# x + 4 * p6240 = 11886 => x = 1150
# x + 4 * p6248 = 14918 => x = 1150
# x + 4 * p8280l = 49450 => x = 1150
# => p5215 = 1317 (agrees with proc price on r640)
return 1150.0
def get_dimm_price ( self , dimm_capacity ) :
return {
8 : 80.0 ,
16 : 160.0 ,
32 : 320.0 ,
64 : 640.0
} [ dimm_capacity ]
def get_guarantee_price ( self , guarantee_duration ) :
if guarantee_duration > 7 :
assert False , ' guarantee of more than 7 years is not available on %s ' % self . host_type_id
elif guarantee_duration > = 5 :
return 630.0 # from dell matinfo4 online quotation
else :
# 5-year guarantee included in base price
return 0.0
@abstractmethod
def get_disk_upgrade_price ( self , asked_disk_capacity ) :
assert 1.9e12 < asked_disk_capacity < 2.1e12 , ' only 2To upgrades are handled for %s ' % self . host_type_id
# Retrait des disques de base (2x600Go 10K SAS 2.5'') : -260.0 €
# Ajout d'un disque dur 1,2 To SAS 10k Tpm 2,5" - hotplug : 165.0 €
base_disks_removal_price = - 260.0
disk_1200g_price = 165.0
return ( base_disks_removal_price + disk_1200g_price * 2 ) * self . num_servers
class DellPrecision3630 ( HostType ) :
def __init__ ( self , host_type_id ) :
super ( ) . __init__ ( host_type_id , num_cpu_per_server = 1 , num_servers = 1 )
def get_empty_price ( self ) :
return 449.0
def get_dimm_price ( self , dimm_capacity ) :
return {
8 : 80.0 ,
16 : 160.0 ,
32 : 320.0
} [ dimm_capacity ]
def get_guarantee_price ( self , guarantee_duration ) :
assert guarantee_duration < = 5 , ' only 5 year guarantee is handled for %s ' % self . host_type_id
return 0.0
def get_disk_upgrade_price ( self , asked_disk_capacity ) :
assert 1.9e12 < asked_disk_capacity < 2.1e12 , ' only 2To upgrades are handled for %s ' % self . host_type_id
return 0.0
class DellPowerEdgeC6420 ( HostType ) :
def __init__ ( self , host_type_id ) :
super ( ) . __init__ ( host_type_id , num_cpu_per_server = 2 , num_servers = 4 )
def get_empty_price ( self ) :
# for 4xc6420 on 19/06/2020 (from excel quotation)
#
#
# (x: price without procs, p2630: price of xeon gold 6230)
# x + 4 x (2 x p4210r + p48g) = 5368 € HT
# x + 4 x (2 x p6230r + p192g) = 27213 € HT
#
# p48g = 3 * 160.0 # the price of a 16G ram is 160.0 €
# p4210r = p4210r=978./2 # from r640 prices
#
# >>> p4210r=978./2
# >>> p6230r_upgrade = 13408.0
# >>> p6230r_for_r640 = 2165.0
# >>> num_servers_per_c6000 = 4
# >>> num_cpu_per_server = 2
# >>> p6230r_for_c6420 = (p6230r_upgrade + p4210r * (num_servers_per_c6000 * num_cpu_per_server))/(num_servers_per_c6000 * num_cpu_per_server)
# >>> p6230r_for_c6420
# 2165.0
# => p4210r seems to be the same on r640 and c6420
#
# pc6000 = 5368 - (p4210r * num_cpu_per_server + p48g) * num_servers_per_c6000
# >>> p16g = 160.0
# >>> p48g = p16g * 3
# >>> pc6000 = 5368 - (p4210r * num_cpu_per_server + p48g) * num_servers_per_c6000
# >>> pc6000
# -464.0
# >>> pc6000 + num_servers_per_c6000 * (p6230r_for_c6420 * num_cpu_per_server + p192g)
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# NameError: name 'p192g' is not defined
# >>> p192g = (192/16)*p16g
# >>> p192g
# 1920.0
# >>> pc6000 + num_servers_per_c6000 * (p6230r_for_c6420 * num_cpu_per_server + p192g)
# 24536.0
# >>> pc6000 + num_servers_per_c6000 * (p6230r_for_c6420 * num_cpu_per_server + p192g) + 1159 + 68 + 350 + 1100
# 27213.0
num_servers_per_c6000 = 4
num_cpu_per_server = 2
ram_price_per_gigabyte = 160.0 / 16 # 16G ram price : 160.0 €
xeon_silver_4210r_price = 978.0 / 2 # from r640 prices
basic_config_price = 5368.0
poweredge_c6000_price = basic_config_price - ( xeon_silver_4210r_price * num_cpu_per_server + ram_price_per_gigabyte * 48 ) * num_servers_per_c6000
return poweredge_c6000_price
def get_dimm_price ( self , dimm_capacity ) :
return {
8 : 80.0 ,
16 : 160.0 ,
32 : 320.0 ,
64 : 640.0
} [ dimm_capacity ]
def get_guarantee_price ( self , guarantee_duration ) :
if guarantee_duration > 7 :
assert False , ' guarantee of more than 7 years is not available on %s ' % self . host_type_id
elif guarantee_duration > = 5 :
return 1100.0 # from c6420-20200716-price
else :
# 5-year guarantee included in base price
return 0.0
def get_disk_upgrade_price ( self , asked_disk_capacity ) :
assert 1.9e12 < asked_disk_capacity < 2.1e12 , ' only 2To upgrades are handled for %s ' % self . host_type_id
# from c6420-20200716-price
# | Ajout d'un disque dur 1 To SATA 7200 Tpm 3,5'' pour les 4 serveurs | | 4-3-1-14g096 | C6420 | 361 € | | € - |
return 361.0
def create_host_type ( host_type_id ) :
if host_type_id == ' c6420 ' :
return DellPowerEdgeC6420 ( host_type_id )
if host_type_id == ' c6320 ' :
return DellPowerEdgeC6320 ( host_type_id )
if host_type_id == ' c4130 ' :
return DellPowerEdgeC4130 ( host_type_id )
if host_type_id == ' r620 ' :
return DellPowerEdgeR620 ( host_type_id )
if host_type_id == ' r630 ' :
return DellPowerEdgeR630 ( host_type_id )
if host_type_id == ' r640 ' :
return DellPowerEdgeR640 ( host_type_id )
if host_type_id == ' r940 ' :
return DellPowerEdgeR940 ( host_type_id )
if host_type_id == ' precision3630 ' :
return DellPrecision3630 ( host_type_id )
assert False
class Config ( ) :
def __init__ ( self , host_type_id ) :
self . host_type = create_host_type ( host_type_id )
def get_empty_price ( self ) :
return self . host_type . get_empty_price ( )
def get_ram_update_price ( self , cpu , ram_per_core = None , ram_per_server = None , ram_per_cpu = None ) :
# ramUpgradePrice128Gb = {
# 'c6220':3520.0,
# 'r620':2010.0,
# 'r630':1778.0,
# 'r640':1780.0,
# 'r730':1778.0,
# 'r940':960.0, # 32 Gb 2933 MHz RDIMM : 320 €
# 'c6320':6222.6,
# 'c4310':1778.0,
# 'precision3630': 1536.0 }
if ram_per_cpu :
assert not ram_per_core
assert not ram_per_server
if ram_per_core :
assert not ram_per_server
assert not ram_per_cpu
ram_per_cpu = cpu . num_cores * ram_per_core
if ram_per_server :
assert not ram_per_core
assert not ram_per_cpu
ram_per_cpu = ram_per_server / self . num_cpu_per_server
ram_per_channel = ram_per_cpu / cpu . num_ram_channels
dimm_capacity = None
if ram_per_channel > 64.0e9 :
assert False , ' ram_per_channel is too big ( %f bytes > 64 Gb) ' % ram_per_channel
elif ram_per_channel > 32.0e9 :
dimm_capacity = 64
elif ram_per_channel > 16.0e9 :
dimm_capacity = 32
elif ram_per_channel > 8.0e9 :
dimm_capacity = 16
elif ram_per_channel > 4.0e9 :
dimm_capacity = 8
else :
try :
self . host_type . get_dimm_price ( 4 )
dimm_capacity = 4
except :
# 4Gb dimms are not available, use 8 Gb instead
dimm_capacity = 8
# print('warning : forcing dimm capacity to 16G !!!!!!')
# dimm_capacity = 16
ram_price = self . num_servers * self . num_cpu_per_server * cpu . num_ram_channels * self . host_type . get_dimm_price ( dimm_capacity )
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 ) )
return ram_price
def get_guarantee_price ( self , guarantee_duration ) :
return self . host_type . get_guarantee_price ( guarantee_duration )
def get_disk_upgrade_price ( self , asked_disk_capacity ) :
return self . host_type . get_disk_upgrade_price ( asked_disk_capacity )
@property
def num_servers ( self ) :
return self . host_type . num_servers
@property
def num_cpu_per_server ( self ) :
return self . host_type . num_cpu_per_server
2020-09-15 18:46:53 +02:00
def plotSystemEfficiency ( ) :
cpuTable = numpy . genfromtxt ( ' cpu_table.dat ' , dtype = ( " |U15 " , float , int , float , float , float ) , names = True , delimiter = ' \t ' )
#cpuTable = numpy.genfromtxt('dell_ivybridge_table.dat', dtype=(('id', "|S10"), ('clock', float), ('num_cores', int), ('price', float, float)), names=None, delimiter='\t')
print ( type ( cpuTable ) )
print ( cpuTable . dtype )
print ( cpuTable )
print ( cpuTable [ ' id ' ] )
dellPriceTable = numpy . genfromtxt ( ' dell_procoptions_table.dat ' , dtype = ( " |U15 " , " |U15 " , float ) , names = True , delimiter = ' \t ' )
#cpuTable = numpy.genfromtxt('dell_ivybridge_table.dat', dtype=(('id', "|S10"), ('clock', float), ('num_cores', int), ('price', float, float)), names=None, delimiter='\t')
#for (x, y) in clusters:
serverBasePowerConsumption = 100.0 # rough estimation in watts
def GHzToMHz ( frequency ) :
return frequency * 1000.0
kWHPrice = 0.07 * 1.5
containerLifetime = 7.0 # in years
powerUsageEfficiency = 0.5
2020-09-18 10:38:24 +02:00
2020-09-15 18:46:53 +02:00
def getColorCodeFromItemLabel ( label ) :
generation = label [ - 1 ]
( model , proc_id ) = re . split ( ' _ ' , label )
saturation = {
' sandy bridge ' : 0.0 ,
' ivy bridge ' : 0.2 ,
' haswell ' : 0.2 ,
' broadwell ' : 0.2 ,
' skylake ' : 0.4 ,
' coffeelake ' : 0.6 ,
' cascadelake ' : 1.0
} [ get_proc_architecture ( proc_id ) ]
# if model == 'r620':
# color = 'r'
# elif model == 'r630':
# color = 'g'
# elif model == 'r730':
# color = 'm'
# elif model == 'c6220':
# if generation == '2':
# color = 'b'
# else:
# color = 'y'
hue = {
' r620 ' : 0.6 ,
' r630 ' : 0.6 ,
' r640 ' : 0.6 ,
' c4310 ' : 0.6 ,
' r730 ' : 0.4 ,
' r940 ' : 0.8 ,
' c6220 ' : 1.0 ,
' c6320 ' : 1.0 ,
2020-09-18 10:38:24 +02:00
' c6420 ' : 1.0 ,
2020-09-15 18:46:53 +02:00
' precision3630 ' : 0.2
} [ model ]
value = 0.9
return matplotlib . colors . hsv_to_rgb ( ( hue , saturation , value ) )
def get_marker_from_label ( label ) :
( model , proc_id ) = re . split ( ' _ ' , label )
return get_marker ( proc_id )
itemPrice = numpy . array ( [ ] )
itemPowerConsumption = numpy . array ( [ ] )
itemSpeed = numpy . array ( [ ] )
itemLabel = numpy . array ( [ ] )
itemGeneration = numpy . array ( [ ] )
for hostTypeId , procId , procOptionPrice in zip ( dellPriceTable [ ' host_type_id ' ] , dellPriceTable [ ' proc_id ' ] , dellPriceTable [ ' proc_option_price ' ] ) :
2020-09-18 10:38:24 +02:00
print ( hostTypeId , procId )
2020-09-15 18:46:53 +02:00
#if hostTypeId == 'r630':
# continue
2020-09-18 10:38:24 +02:00
cpu = Cpu ( procId )
if not cpu . architecture in [ ' coffeelake ' , ' skylake ' , ' cascadelake ' ] :
2020-09-15 18:46:53 +02:00
continue
2020-09-18 10:38:24 +02:00
config = Config ( hostTypeId )
2020-09-15 18:46:53 +02:00
itemGeneration = procId [ - 1 ]
itemLabel = numpy . append ( itemLabel , hostTypeId + ' _ ' + procId )
2020-09-18 10:38:24 +02:00
print ( ' procOptionPrice ' , procOptionPrice )
config_price = procOptionPrice
config_price + = config . get_empty_price ( )
print ( ' config.get_empty_price() ' , config . get_empty_price ( ) )
ram_update_price = config . get_ram_update_price ( cpu = cpu , ram_per_core = 6.0e9 )
# ram_update_price = config.get_ram_update_price(cpu=cpu, ram_per_server=192.0e9)
# ram_update_price = config.get_ram_update_price(cpu=cpu, ram_per_cpu=96.0e9)
print ( ' ram_update_price ' , ram_update_price )
config_price + = ram_update_price
config_price + = 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 )
print ( ' config.get_disk_upgrade_price(2.0e12) ' , config . get_disk_upgrade_price ( 2.0e12 ) )
itemPrice = numpy . append ( itemPrice , config_price )
itemPowerConsumption = numpy . append ( itemPowerConsumption , ( cpu . tdp * config . num_cpu_per_server + serverBasePowerConsumption ) * config . num_servers )
2020-09-15 18:46:53 +02:00
# print(hostTypeId, procId, itemPowerConsumption[-1])
2020-09-18 10:38:24 +02:00
itemSpeed = numpy . append ( itemSpeed , cpu . num_dp_flop_per_cycle * cpu . clock * 1.e9 * cpu . num_cores * config . num_cpu_per_server * config . num_servers )
2020-09-15 18:46:53 +02:00
#pylab.plot(x, y, '+')
#pylab.xlabel('speed/price ratio [core.MHz/euros]')
#pylab.ylabel('speed/power consumption ratio [core.MHz/W]')
#pylab.show() # or savefig(<filename>)
#print("items = ")
#print(itemLabel)
markerSize = 50
if False :
plt . subplot ( 1 , 2 , 1 )
plt . subplots_adjust ( bottom = 0.1 )
markersCycler = itertools . cycle ( itertools . product ( markerTypes , markerColors ) )
x = itemSpeed / itemPrice
y = itemSpeed / itemPowerConsumption
for label , x1 , y1 , power , speed , price , in zip ( itemLabel , x , y , itemPowerConsumption , itemSpeed , itemPrice ) :
marker = markersCycler . next ( )
color = getColorCodeFromItemLabel ( label )
plt . scatter ( x1 , y1 , color = color , s = markerSize , marker = marker [ 0 ] , label = label )
#print(x1, y1, color, markerSize, marker[0], label)
if False :
plt . scatter ( x , y , marker = ' o ' )
for label , x1 , y1 , power , speed , price , in zip ( itemLabel , x , y , itemPowerConsumption , itemSpeed , itemPrice ) :
#print(label)
plt . annotate ( u ' %s ( %.1f core.GHz, %.0f W, %.0f €) ' % ( label , speed / 1000.0 , power , price ) ,
xy = ( x1 , y1 ) , xytext = ( - 50 , 50 ) ,
textcoords = ' offset points ' , ha = ' right ' , va = ' bottom ' ,
bbox = dict ( boxstyle = ' round,pad=0.5 ' , fc = ' yellow ' , alpha = 0.5 ) ,
arrowprops = dict ( arrowstyle = ' -> ' , connectionstyle = ' arc3,rad=0 ' ) )
plt . xlabel ( u ' speed/price ratio [core.MHz/€] ' )
plt . ylabel ( u ' speed/power consumption ratio [core.MHz/W] ' )
plt . xlim ( xmin = 0.0 )
plt . ylim ( ymin = 0.0 )
plt . subplot ( 1 , 2 , 1 )
#fig = plt.figure()
#ax = fig.gca()
#ax.set_xticks(numpy.arange(0,1,0.1))
#ax.set_yticks(numpy.arange(0,1.,0.1))
powerUsedInLifetime = ( itemPowerConsumption * containerLifetime * 365 * 24 ) / powerUsageEfficiency
itemTotalCost = itemPrice + ( powerUsedInLifetime / 1000.0 * kWHPrice )
markersCycler = itertools . cycle ( itertools . product ( markerTypes , markerColors ) )
item_flops = itemSpeed
# print item_flops
item_total_num_ops = item_flops * containerLifetime * 365 * 24 * 3600
# print(itemPrice)
x = itemPrice
y = item_total_num_ops / itemTotalCost
for i in range ( len ( itemLabel ) ) :
print ( itemLabel [ i ] , itemPrice [ i ] , y [ i ] )
print ( ' itemTotalCost ' , itemTotalCost [ i ] )
print ( ' flops ' , item_flops [ i ] )
# print y
for label , x1 , y1 , power , speed , price , in zip ( itemLabel , x , y , itemPowerConsumption , itemSpeed , itemPrice ) :
if y1 > 0.0001 :
color = getColorCodeFromItemLabel ( label )
# marker = markersCycler.next()
marker = get_marker_from_label ( label )
#print(x1, y1)
plt . scatter ( x1 , y1 , facecolors = color , s = markerSize , marker = marker [ 0 ] , label = label )
2020-09-18 10:38:24 +02:00
if y1 > 5.0e16 :
2020-09-15 18:46:53 +02:00
plt . annotate ( u ' %s ' % label ,
2020-09-18 10:38:24 +02:00
xy = ( x1 , y1 ) , xytext = ( x1 * 4.0 , ( y1 - 5.2e16 ) * 7.1 ) ,
2020-09-15 18:46:53 +02:00
textcoords = ' data ' , ha = ' right ' , va = ' bottom ' ,
bbox = dict ( boxstyle = ' round,pad=0.5 ' , fc = ' yellow ' , alpha = 0.5 ) ,
arrowprops = dict ( arrowstyle = ' -> ' , connectionstyle = ' arc3,rad=0 ' ) )
plt . xlabel ( u ' purchase price [€] ' )
plt . ylabel ( u ' num total DP operations/total cost [€/^-1] ' )
plt . title ( u ' total cost including electricity ' )
plt . xlim ( xmin = 0.0 )
plt . ylim ( ymin = 0.0 )
plt . minorticks_on ( )
plt . grid ( b = True , which = ' major ' , color = ' b ' , linestyle = ' - ' , linewidth = 0.5 )
plt . grid ( b = True , which = ' minor ' , color = ' b ' , linestyle = ' - ' , linewidth = 0.2 )
plt . legend ( bbox_to_anchor = ( 1.1 , 1.1 ) , ncol = 3 )
plt . draw ( )
plt . show ( )
#plotCpuPassmark():
plotSystemEfficiency ( )