From 8ebacfd9db26d074f86e5a7c44306e6d49a32f07 Mon Sep 17 00:00:00 2001 From: beiyonder <86228410+beiyonder@users.noreply.github.com> Date: Mon, 28 Apr 2025 00:28:57 +0530 Subject: [PATCH 1/3] Improved number_of_cpu func logic and error handling --- eco2ai/__init__.py | 5 +- eco2ai/tools/tools_cpu.py | 132 ++------------------------------------ eco2ai/tools/tools_gpu.py | 109 +++++++++++++++++-------------- eco2ai/tools/tools_ram.py | 20 +++--- eco2ai/utils.py | 15 +++-- 5 files changed, 92 insertions(+), 189 deletions(-) diff --git a/eco2ai/__init__.py b/eco2ai/__init__.py index ba7f092..d487669 100644 --- a/eco2ai/__init__.py +++ b/eco2ai/__init__.py @@ -2,7 +2,6 @@ Tracker, track, __version__ - ) from eco2ai.tools.tools_cpu import ( @@ -16,12 +15,12 @@ ) from eco2ai.tools.tools_ram import ( - RAM, + RAM ) from eco2ai.utils import ( available_devices, set_params, get_params, - summary, + summary ) diff --git a/eco2ai/tools/tools_cpu.py b/eco2ai/tools/tools_cpu.py index a991895..abc8d01 100644 --- a/eco2ai/tools/tools_cpu.py +++ b/eco2ai/tools/tools_cpu.py @@ -29,7 +29,7 @@ class CPU(): The CPU class is not intended for separate usage, outside the Tracker class """ - def __init__(self, cpu_processes="current", ignore_warnings=False): + def __init__(self, cpu_processes: str = "current", ignore_warnings: bool = False): """ This class method initializes CPU object. Creates fields of class object. All the fields are private variables @@ -51,7 +51,7 @@ def __init__(self, cpu_processes="current", ignore_warnings=False): self._ignore_warnings = ignore_warnings self._cpu_processes = cpu_processes self._cpu_dict = get_cpu_info() - self._name = self._cpu_dict["brand_raw"] + self._name = self._cpu_dict.get("brand_raw", "") # safer extraction self._tdp = find_tdp_value(self._name, CPU_TABLE_NAME, self._ignore_warnings) self._consumption = 0 self._cpu_num = number_of_cpu(self._ignore_warnings) @@ -107,7 +107,7 @@ def get_consumption(self): self.calculate_consumption() return self._consumption - def get_cpu_percent(self,): + def get_cpu_percent(self): """ This class method calculates CPU utilization taking into account only python processes. @@ -185,7 +185,7 @@ def all_available_cpu(): print("There is no any available cpu device(s)") -def number_of_cpu(ignore_warnings=True): +def number_of_cpu(ignore_warnings: bool = True) -> int: """ This function returns number of CPU sockets(physical CPU processors) If the body of the function runs with error, number of available cpu devices will be set to 1 @@ -244,7 +244,9 @@ def number_of_cpu(ignore_warnings=True): processor_string = dictionary['Џа®жҐбб®а(л)'] if 'Процессор(ы)' in dictionary: processor_string = dictionary['Процессор(ы)'] - cpu_num = int(re.findall(r'- (\d)\.', processor_string)[0]) + # Use regex for multi-digit CPU numbers + match = re.findall(r'- (\d+)\.', processor_string) + cpu_num = int(match[0]) if match else 1 except: if not ignore_warnings: warnings.warn( @@ -316,45 +318,6 @@ def transform_cpu_name(cpu_name): patterns.remove('') return cpu_name, patterns -def transform_cpu_name_2(cpu_name): - """ - This function drops all the waste tokens, and words from a cpu name - It finds patterns. Patterns include processor's family and - some certain specifications like 9400F in Intel Core i5-9400F - - Parameters - ---------- - cpu_name: str - A string, containing CPU name, taken from psutil library - - Returns - ------- - cpu_name: str - Modified CPU name, containing patterns only - patterns: list of str - Array with all the patterns - - """ - # dropping all the waste tokens and patterns: - cpu_name = re.sub(r'(\(R\))|(®)|(™)|(\(TM\))|(@.*)|(\S*GHz\S*)|(\[.*\])|( \d-Core)|(\(.*\))', '', cpu_name) - - # dropping all the waste words: - array = re.split(" ", cpu_name) - for i in array[::-1]: - if ("CPU" in i) or ("Processor" in i) or (i == ''): - array.remove(i) - cpu_name = " ".join(array) - patterns = re.findall(r"(\S*\d+\S*)", cpu_name) - for i in re.findall( - "(Ryzen Threadripper)|(Ryzen)|(EPYC)|(Athlon)|(Xeon Gold)|(Xeon Bronze)|(Xeon Silver)|(Xeon Platinum)|(Xeon)|(Core)|(Celeron)|(Atom)|(Pentium)", - cpu_name - ): - patterns += i - patterns = list(set(patterns)) - if '' in patterns: - patterns.remove('') - return cpu_name, patterns - def get_patterns(cpu_name): """ @@ -494,87 +457,6 @@ def find_tdp_value(cpu_name, f_table_name, constant_value=CONSTANT_CONSUMPTION, tmp_elements.append(element[0]) return find_max_tdp(tmp_elements) -# searching cpu name in cpu table -def find_tdp_value_2(cpu_name, f_table_name, constant_value=CONSTANT_CONSUMPTION, ignore_warnings=True): - """ - This function finds and returns TDP of user CPU device. - - Parameters - ---------- - cpu_name: str - Name of user CPU device, taken from psutil library - - f_table_name: str - A file name of CPU TDP values Database - - constant_value: constant_value - The value, that is assigned to CPU TDP if - user CPU device is not found in CPU TDP database - The default is CONSTANT_CONSUMPTION(a global value, initialized in the beginning of the file) - - ignore_warnings: bool - If true, then user will be notified of all the warnings. If False, there won't be any warnings. - The default is True. - - Returns - ------- - CPU TDP: float - TDP of user CPU device - - """ - # firstly, we try to find transformed cpu name in the cpu table: - f_table = pd.read_csv(f_table_name) - cpu_name_mod, patterns = transform_cpu_name(cpu_name) - f_table = f_table[["Model", "TDP"]].values - suitable_elements = f_table[f_table[:, 0] == cpu_name_mod] - if suitable_elements.shape[0] > 0: - # if there are more than one suitable elements, return one with maximum TDP value - return find_max_tdp(suitable_elements), cpu_name_mod, "in_table" - # secondly, if needed element isn't found in the table, - # then we try to find patterns in cpu names and return suitable values: - # if there is no any patterns in cpu name, we simply return constant consumption value - if len(patterns) == 0: - if not ignore_warnings: - warnings.warn( - message="\n\nYour CPU device is not found in our database\nCPU TDP is set to constant value 100\n", - category=NoCPUinTableWarning - ) - return constant_value, cpu_name_mod, "not_in_table" - # appending to array all suitable for at least one of the patterns elements - suitable_elements = [] - for element in f_table: - flag = 0 - tmp_patterns = get_patterns(element[0]) - for pattern in patterns: - if pattern in tmp_patterns: - flag += 1 - if flag: - # suitable_elements.append(element) - suitable_elements.append((element, flag)) - - # if there is only one suitable element, we return this element. - # If there is no suitable elements, we return constant value - # If there are more than one element, we check existence of elements suitable for all the patterns simultaneously. - # If there are such elements(one or more), we return the value with maximum TDP among them. - # If there is no, we return the value with maximum TDP among all the suitable elements - if len(suitable_elements) == 0: - if not ignore_warnings: - warnings.warn( - message="\n\nYour CPU device is not found in our database\nCPU TDP is set to constant value 100\n", - category=NoCPUinTableWarning - ) - return CONSTANT_CONSUMPTION, cpu_name_mod, "no_suitable_elements" - elif len(suitable_elements) == 1: - return float(suitable_elements[0][0][1]), cpu_name_mod, "one_in_table" - else: - suitable_elements.sort(key=lambda x: x[1], reverse=True) - max_coincidence = suitable_elements[0][1] - - tmp_elements = [] - for element in suitable_elements: - if element[1] == max_coincidence: - tmp_elements.append(element[0]) - return find_max_tdp(tmp_elements), cpu_name_mod, "many_in_table" def get_cpu_percent_mac_os(cpu_processes="current"): """ diff --git a/eco2ai/tools/tools_gpu.py b/eco2ai/tools/tools_gpu.py index e4fbc19..c6b9d86 100644 --- a/eco2ai/tools/tools_gpu.py +++ b/eco2ai/tools/tools_gpu.py @@ -46,7 +46,7 @@ def __init__(self, ignore_warnings=False): if self.is_gpu_available: self._start = time.time() - def calculate_consumption(self): + def calculate_consumption(self) -> float: """ This class method calculates GPU power consumption. @@ -71,7 +71,7 @@ def calculate_consumption(self): self._consumption += consumption return consumption - def get_consumption(self): + def get_consumption(self) -> float: """ This class method returns GPU power consupmtion amount. @@ -105,14 +105,17 @@ def gpu_memory(self): """ if not self.is_gpu_available: return None - pynvml.nvmlInit() - deviceCount = pynvml.nvmlDeviceGetCount() - gpus_memory = [] - for i in range(deviceCount): - handle = pynvml.nvmlDeviceGetHandleByIndex(i) - gpus_memory.append(pynvml.nvmlDeviceGetMemoryInfo(handle)) - pynvml.nvmlShutdown() - return gpus_memory + try: + pynvml.nvmlInit() + deviceCount = pynvml.nvmlDeviceGetCount() + gpus_memory = [] + for i in range(deviceCount): + handle = pynvml.nvmlDeviceGetHandleByIndex(i) + gpus_memory.append(pynvml.nvmlDeviceGetMemoryInfo(handle)) + pynvml.nvmlShutdown() + return gpus_memory + except Exception: + return None # standardized error return def gpu_temperature(self): """ @@ -130,14 +133,17 @@ def gpu_temperature(self): """ if not self.is_gpu_available: return None - pynvml.nvmlInit() - deviceCount = pynvml.nvmlDeviceGetCount() - gpus_temps = [] - for i in range(deviceCount): - handle = pynvml.nvmlDeviceGetHandleByIndex(i) - gpus_temps.append(pynvml.nvmlDeviceGetTemperature(handle, pynvml.NVML_TEMPERATURE_GPU)) - pynvml.nvmlShutdown() - return gpus_temps + try: + pynvml.nvmlInit() + deviceCount = pynvml.nvmlDeviceGetCount() + gpus_temps = [] + for i in range(deviceCount): + handle = pynvml.nvmlDeviceGetHandleByIndex(i) + gpus_temps.append(pynvml.nvmlDeviceGetTemperature(handle, pynvml.NVML_TEMPERATURE_GPU)) + pynvml.nvmlShutdown() + return gpus_temps + except Exception: + return None def gpu_power(self): """ @@ -155,14 +161,17 @@ def gpu_power(self): """ if not self.is_gpu_available: return None - pynvml.nvmlInit() - deviceCount = pynvml.nvmlDeviceGetCount() - gpus_powers = [] - for i in range(deviceCount): - handle = pynvml.nvmlDeviceGetHandleByIndex(i) - gpus_powers.append(pynvml.nvmlDeviceGetPowerUsage(handle)) - pynvml.nvmlShutdown() - return gpus_powers + try: + pynvml.nvmlInit() + deviceCount = pynvml.nvmlDeviceGetCount() + gpus_powers = [] + for i in range(deviceCount): + handle = pynvml.nvmlDeviceGetHandleByIndex(i) + gpus_powers.append(pynvml.nvmlDeviceGetPowerUsage(handle)) + pynvml.nvmlShutdown() + return gpus_powers + except Exception: + return None def gpu_power_limit(self): """ @@ -180,18 +189,19 @@ def gpu_power_limit(self): """ if not self.is_gpu_available: return None - pynvml.nvmlInit() - deviceCount = pynvml.nvmlDeviceGetCount() - gpus_limits = [] - for i in range(deviceCount): - handle = pynvml.nvmlDeviceGetHandleByIndex(i) - gpus_limits.append(pynvml.nvmlDeviceGetEnforcedPowerLimit(handle)) - pynvml.nvmlShutdown() - return gpus_limits + try: + pynvml.nvmlInit() + deviceCount = pynvml.nvmlDeviceGetCount() + gpus_limits = [] + for i in range(deviceCount): + handle = pynvml.nvmlDeviceGetHandleByIndex(i) + gpus_limits.append(pynvml.nvmlDeviceGetEnforcedPowerLimit(handle)) + pynvml.nvmlShutdown() + return gpus_limits + except Exception: + return None - def name( - self, - ): + def name(self) -> str: """ This class method returns GPU name if there are any GPU visible or it returns empty string. All the GPU devices are intended to be of the same model @@ -216,11 +226,14 @@ def name( pynvml.nvmlDeviceGetPowerUsage(handle) gpus_name.append(pynvml.nvmlDeviceGetName(handle)) pynvml.nvmlShutdown() - return gpus_name[0].encode().decode("UTF-8") - except: + if gpus_name: + return gpus_name[0].encode().decode("UTF-8") + else: + return "" + except Exception: return "" - def gpu_num(self): + def gpu_num(self) -> int: """ This class method returns number of visible GPU devices. Pynvml library is used. @@ -243,11 +256,11 @@ def gpu_num(self): pynvml.nvmlDeviceGetPowerUsage(handle) pynvml.nvmlShutdown() return deviceCount - except: + except Exception: return 0 -def is_gpu_available(): +def is_gpu_available() -> bool: """ This function checks if there are any available GPU devices All the GPU devices are intended to be of the same model @@ -272,7 +285,7 @@ def is_gpu_available(): gpus_powers.append(pynvml.nvmlDeviceGetPowerUsage(handle)) pynvml.nvmlShutdown() return True - except pynvml.NVMLError: + except Exception: # catch all exceptions for robustness return False @@ -298,10 +311,12 @@ def all_available_gpu(): handle = pynvml.nvmlDeviceGetHandleByIndex(i) pynvml.nvmlDeviceGetPowerUsage(handle) gpus_name.append(pynvml.nvmlDeviceGetName(handle)) - string = f"""Seeable gpu device(s): + if gpus_name: + string = f"""Seeable gpu device(s): {gpus_name[0].decode("UTF-8")}: {deviceCount} device(s)""" - print(string) + print(string) + else: + print("There is no any available gpu device(s)") pynvml.nvmlShutdown() - except: + except Exception: print("There is no any available gpu device(s)") - \ No newline at end of file diff --git a/eco2ai/tools/tools_ram.py b/eco2ai/tools/tools_ram.py index d985b71..128acec 100644 --- a/eco2ai/tools/tools_ram.py +++ b/eco2ai/tools/tools_ram.py @@ -12,7 +12,7 @@ class RAM(): The RAM class is not intended for separate usage, outside the Tracker class """ - def __init__(self, ignore_warnings=False): + def __init__(self, ignore_warnings: bool = False): """ This class method initializes RAM object. Creates fields of class object. All the fields are private variables @@ -33,7 +33,7 @@ def __init__(self, ignore_warnings=False): self._start = time.time() - def get_consumption(self): + def get_consumption(self) -> float: """ This class method returns RAM power consupmtion amount. @@ -51,7 +51,7 @@ def get_consumption(self): return self._consumption - def _get_memory_used(self,): + def _get_memory_used(self) -> float: """ This class method calculates amount of virtual memory(RAM) used. @@ -80,10 +80,13 @@ def _get_memory_used(self,): return memory_percent * total_memory / 100 - def calculate_consumption(self): + def calculate_consumption(self) -> float: """ This class method calculates RAM power consumption. - + The formula used is: + RAM_used_GB * (3/8) * time_period_seconds / FROM_WATTs_TO_kWATTh + where (3/8) is an empirical factor for RAM power estimation. + See: https://github.com/sb-ai-lab/Eco2AI/issues/34 Parameters ---------- No parameters @@ -97,7 +100,8 @@ def calculate_consumption(self): time_period = time.time() - self._start self._start = time.time() consumption = self._get_memory_used() * (3 / 8) * time_period / FROM_WATTs_TO_kWATTh - + if consumption < 0: # ensure no negative values + consumption = 0 self._consumption += consumption - # print(self._consumption) - return consumption \ No newline at end of file + return consumption +``` \ No newline at end of file diff --git a/eco2ai/utils.py b/eco2ai/utils.py index 1d720f9..4f82a83 100644 --- a/eco2ai/utils.py +++ b/eco2ai/utils.py @@ -20,7 +20,7 @@ class NotNeededExtensionError(Exception): pass -def available_devices(): +def available_devices() -> None: """ This function prints all the available CPU & GPU devices @@ -35,7 +35,8 @@ def available_devices(): """ all_available_cpu() all_available_gpu() - # need to add RAM + # TODO: Add RAM reporting in the future + # print("RAM device(s): reporting not implemented yet") def is_file_opened( @@ -118,11 +119,11 @@ def define_carbon_index( carbon_index_table_name = resource_stream('eco2ai', 'data/carbon_index.csv').name if alpha_2_code is None: try: - ip_dict = eval(requests.get("https://ipinfo.io/").content) + ip_dict = json.loads(requests.get("https://ipinfo.io/").content) # safer than eval except: - ip_dict = eval(requests.get("https://ipinfo.io/").content.decode('ascii')) - country = ip_dict['country'] - region = ip_dict['region'] + ip_dict = json.loads(requests.get("https://ipinfo.io/").content.decode('ascii')) + country = ip_dict.get('country', None) + region = ip_dict.get('region', None) else: country = alpha_2_code if emission_level is not None: @@ -507,6 +508,8 @@ def summary( if not filename.endswith('.csv'): raise NotNeededExtensionError('File need to be with extension \'.csv\'') df = pd.read_csv(filename) + if df.empty: + raise ValueError("CSV file is empty, cannot summarize.") projects = np.unique(df['project_name'].values) summary_data = [] columns = [ From d053fea882bb63d668a33f0cde1fd1b85f12564a Mon Sep 17 00:00:00 2001 From: beiyonder <86228410+beiyonder@users.noreply.github.com> Date: Mon, 28 Apr 2025 00:31:21 +0530 Subject: [PATCH 2/3] improved number_of_cpu func logic and error handling --- eco2ai/tools/tools_cpu.py | 145 +++++++++++++++++++++++++++++++++++++- eco2ai/utils.py | 3 +- 2 files changed, 143 insertions(+), 5 deletions(-) diff --git a/eco2ai/tools/tools_cpu.py b/eco2ai/tools/tools_cpu.py index abc8d01..358df69 100644 --- a/eco2ai/tools/tools_cpu.py +++ b/eco2ai/tools/tools_cpu.py @@ -29,7 +29,7 @@ class CPU(): The CPU class is not intended for separate usage, outside the Tracker class """ - def __init__(self, cpu_processes: str = "current", ignore_warnings: bool = False): + def __init__(self, cpu_processes="current", ignore_warnings: bool = False): """ This class method initializes CPU object. Creates fields of class object. All the fields are private variables @@ -51,7 +51,7 @@ def __init__(self, cpu_processes: str = "current", ignore_warnings: bool = False self._ignore_warnings = ignore_warnings self._cpu_processes = cpu_processes self._cpu_dict = get_cpu_info() - self._name = self._cpu_dict.get("brand_raw", "") # safer extraction + self._name = self._cpu_dict["brand_raw"] self._tdp = find_tdp_value(self._name, CPU_TABLE_NAME, self._ignore_warnings) self._consumption = 0 self._cpu_num = number_of_cpu(self._ignore_warnings) @@ -107,7 +107,7 @@ def get_consumption(self): self.calculate_consumption() return self._consumption - def get_cpu_percent(self): + def get_cpu_percent(self,): """ This class method calculates CPU utilization taking into account only python processes. @@ -273,7 +273,26 @@ def number_of_cpu(ignore_warnings: bool = True) -> int: # message="\nIt's impossible to determine cpu number correctly\nFor now, number of cpu devices is set to 1\n\n", # category=NoNeededLibrary # ) + try: + # Try to get the number of physical CPU packages + out = subprocess.check_output( + ["sysctl", "-n", "hw.packages"], text=True).strip() + cpu_num = int(out) + except (subprocess.CalledProcessError, ValueError): cpu_num = 0 + + if cpu_num <= 0: + try: + # Fallback: number of physical cores + out = subprocess.check_output(["sysctl", "-n", "hw.physicalcpu"], text=True).strip() + cpu_num = int(out) + except (subprocess.CalledProcessError, ValueError): + if not ignore_warnings: + warnings.warn( + "Unable to determine the number of CPU sockets on Darwin; defaulting to 1", + category=UserWarning + ) cpu_num = 1 + else: cpu_num = 1 return cpu_num @@ -318,6 +337,45 @@ def transform_cpu_name(cpu_name): patterns.remove('') return cpu_name, patterns +def transform_cpu_name_2(cpu_name): + """ + This function drops all the waste tokens, and words from a cpu name + It finds patterns. Patterns include processor's family and + some certain specifications like 9400F in Intel Core i5-9400F + + Parameters + ---------- + cpu_name: str + A string, containing CPU name, taken from psutil library + + Returns + ------- + cpu_name: str + Modified CPU name, containing patterns only + patterns: list of str + Array with all the patterns + + """ + # dropping all the waste tokens and patterns: + cpu_name = re.sub(r'(\(R\))|(®)|(™)|(\(TM\))|(@.*)|(\S*GHz\S*)|(\[.*\])|( \d-Core)|(\(.*\))', '', cpu_name) + + # dropping all the waste words: + array = re.split(" ", cpu_name) + for i in array[::-1]: + if ("CPU" in i) or ("Processor" in i) or (i == ''): + array.remove(i) + cpu_name = " ".join(array) + patterns = re.findall(r"(\S*\d+\S*)", cpu_name) + for i in re.findall( + "(Ryzen Threadripper)|(Ryzen)|(EPYC)|(Athlon)|(Xeon Gold)|(Xeon Bronze)|(Xeon Silver)|(Xeon Platinum)|(Xeon)|(Core)|(Celeron)|(Atom)|(Pentium)", + cpu_name + ): + patterns += i + patterns = list(set(patterns)) + if '' in patterns: + patterns.remove('') + return cpu_name, patterns + def get_patterns(cpu_name): """ @@ -457,6 +515,87 @@ def find_tdp_value(cpu_name, f_table_name, constant_value=CONSTANT_CONSUMPTION, tmp_elements.append(element[0]) return find_max_tdp(tmp_elements) +# searching cpu name in cpu table +def find_tdp_value_2(cpu_name, f_table_name, constant_value=CONSTANT_CONSUMPTION, ignore_warnings=True): + """ + This function finds and returns TDP of user CPU device. + + Parameters + ---------- + cpu_name: str + Name of user CPU device, taken from psutil library + + f_table_name: str + A file name of CPU TDP values Database + + constant_value: constant_value + The value, that is assigned to CPU TDP if + user CPU device is not found in CPU TDP database + The default is CONSTANT_CONSUMPTION(a global value, initialized in the beginning of the file) + + ignore_warnings: bool + If true, then user will be notified of all the warnings. If False, there won't be any warnings. + The default is True. + + Returns + ------- + CPU TDP: float + TDP of user CPU device + + """ + # firstly, we try to find transformed cpu name in the cpu table: + f_table = pd.read_csv(f_table_name) + cpu_name_mod, patterns = transform_cpu_name(cpu_name) + f_table = f_table[["Model", "TDP"]].values + suitable_elements = f_table[f_table[:, 0] == cpu_name_mod] + if suitable_elements.shape[0] > 0: + # if there are more than one suitable elements, return one with maximum TDP value + return find_max_tdp(suitable_elements), cpu_name_mod, "in_table" + # secondly, if needed element isn't found in the table, + # then we try to find patterns in cpu names and return suitable values: + # if there is no any patterns in cpu name, we simply return constant consumption value + if len(patterns) == 0: + if not ignore_warnings: + warnings.warn( + message="\n\nYour CPU device is not found in our database\nCPU TDP is set to constant value 100\n", + category=NoCPUinTableWarning + ) + return constant_value, cpu_name_mod, "not_in_table" + # appending to array all suitable for at least one of the patterns elements + suitable_elements = [] + for element in f_table: + flag = 0 + tmp_patterns = get_patterns(element[0]) + for pattern in patterns: + if pattern in tmp_patterns: + flag += 1 + if flag: + # suitable_elements.append(element) + suitable_elements.append((element, flag)) + + # if there is only one suitable element, we return this element. + # If there is no suitable elements, we return constant value + # If there are more than one element, we check existence of elements suitable for all the patterns simultaneously. + # If there are such elements(one or more), we return the value with maximum TDP among them. + # If there is no, we return the value with maximum TDP among all the suitable elements + if len(suitable_elements) == 0: + if not ignore_warnings: + warnings.warn( + message="\n\nYour CPU device is not found in our database\nCPU TDP is set to constant value 100\n", + category=NoCPUinTableWarning + ) + return CONSTANT_CONSUMPTION, cpu_name_mod, "no_suitable_elements" + elif len(suitable_elements) == 1: + return float(suitable_elements[0][0][1]), cpu_name_mod, "one_in_table" + else: + suitable_elements.sort(key=lambda x: x[1], reverse=True) + max_coincidence = suitable_elements[0][1] + + tmp_elements = [] + for element in suitable_elements: + if element[1] == max_coincidence: + tmp_elements.append(element[0]) + return find_max_tdp(tmp_elements), cpu_name_mod, "many_in_table" def get_cpu_percent_mac_os(cpu_processes="current"): """ diff --git a/eco2ai/utils.py b/eco2ai/utils.py index 4f82a83..fce879a 100644 --- a/eco2ai/utils.py +++ b/eco2ai/utils.py @@ -35,8 +35,7 @@ def available_devices() -> None: """ all_available_cpu() all_available_gpu() - # TODO: Add RAM reporting in the future - # print("RAM device(s): reporting not implemented yet") + # need to add RAM def is_file_opened( From d9e5030fc8c2e404069353c7557fb9b39806adac Mon Sep 17 00:00:00 2001 From: beiyonder <86228410+beiyonder@users.noreply.github.com> Date: Mon, 28 Apr 2025 00:48:31 +0530 Subject: [PATCH 3/3] Improved number_of_cpu logic and added some error handling --- eco2ai/__init__.py | 5 +++-- eco2ai/tools/tools_cpu.py | 4 ++-- eco2ai/tools/tools_gpu.py | 6 +++--- eco2ai/tools/tools_ram.py | 15 ++++++--------- 4 files changed, 14 insertions(+), 16 deletions(-) diff --git a/eco2ai/__init__.py b/eco2ai/__init__.py index d487669..ba7f092 100644 --- a/eco2ai/__init__.py +++ b/eco2ai/__init__.py @@ -2,6 +2,7 @@ Tracker, track, __version__ + ) from eco2ai.tools.tools_cpu import ( @@ -15,12 +16,12 @@ ) from eco2ai.tools.tools_ram import ( - RAM + RAM, ) from eco2ai.utils import ( available_devices, set_params, get_params, - summary + summary, ) diff --git a/eco2ai/tools/tools_cpu.py b/eco2ai/tools/tools_cpu.py index 358df69..9958d48 100644 --- a/eco2ai/tools/tools_cpu.py +++ b/eco2ai/tools/tools_cpu.py @@ -29,7 +29,7 @@ class CPU(): The CPU class is not intended for separate usage, outside the Tracker class """ - def __init__(self, cpu_processes="current", ignore_warnings: bool = False): + def __init__(self, cpu_processes="current", ignore_warnings=False): """ This class method initializes CPU object. Creates fields of class object. All the fields are private variables @@ -185,7 +185,7 @@ def all_available_cpu(): print("There is no any available cpu device(s)") -def number_of_cpu(ignore_warnings: bool = True) -> int: +def number_of_cpu(ignore_warnings=True): """ This function returns number of CPU sockets(physical CPU processors) If the body of the function runs with error, number of available cpu devices will be set to 1 diff --git a/eco2ai/tools/tools_gpu.py b/eco2ai/tools/tools_gpu.py index c6b9d86..063d6a0 100644 --- a/eco2ai/tools/tools_gpu.py +++ b/eco2ai/tools/tools_gpu.py @@ -46,7 +46,7 @@ def __init__(self, ignore_warnings=False): if self.is_gpu_available: self._start = time.time() - def calculate_consumption(self) -> float: + def calculate_consumption(self): """ This class method calculates GPU power consumption. @@ -71,7 +71,7 @@ def calculate_consumption(self) -> float: self._consumption += consumption return consumption - def get_consumption(self) -> float: + def get_consumption(self): """ This class method returns GPU power consupmtion amount. @@ -201,7 +201,7 @@ def gpu_power_limit(self): except Exception: return None - def name(self) -> str: + def name(self,): """ This class method returns GPU name if there are any GPU visible or it returns empty string. All the GPU devices are intended to be of the same model diff --git a/eco2ai/tools/tools_ram.py b/eco2ai/tools/tools_ram.py index 128acec..b7bba5c 100644 --- a/eco2ai/tools/tools_ram.py +++ b/eco2ai/tools/tools_ram.py @@ -12,7 +12,7 @@ class RAM(): The RAM class is not intended for separate usage, outside the Tracker class """ - def __init__(self, ignore_warnings: bool = False): + def __init__(self, ignore_warnings=False): """ This class method initializes RAM object. Creates fields of class object. All the fields are private variables @@ -33,7 +33,7 @@ def __init__(self, ignore_warnings: bool = False): self._start = time.time() - def get_consumption(self) -> float: + def get_consumption(self): """ This class method returns RAM power consupmtion amount. @@ -51,7 +51,7 @@ def get_consumption(self) -> float: return self._consumption - def _get_memory_used(self) -> float: + def _get_memory_used(self,): """ This class method calculates amount of virtual memory(RAM) used. @@ -83,10 +83,7 @@ def _get_memory_used(self) -> float: def calculate_consumption(self) -> float: """ This class method calculates RAM power consumption. - The formula used is: - RAM_used_GB * (3/8) * time_period_seconds / FROM_WATTs_TO_kWATTh - where (3/8) is an empirical factor for RAM power estimation. - See: https://github.com/sb-ai-lab/Eco2AI/issues/34 + Parameters ---------- No parameters @@ -103,5 +100,5 @@ def calculate_consumption(self) -> float: if consumption < 0: # ensure no negative values consumption = 0 self._consumption += consumption - return consumption -``` \ No newline at end of file + # print(self._consumption) + return consumption \ No newline at end of file