From e8e1d8d11e45e09bdaf830a504df03eeeabddeac Mon Sep 17 00:00:00 2001 From: nikhil153 Date: Fri, 2 Apr 2021 16:41:09 -0400 Subject: [PATCH 1/9] added wrapper code to allow user-specific region and handle errors from get_region_info on CC cluster --- examples/my_experiment.py | 15 +- experiment_impact_tracker/compute_tracker.py | 14 +- .../data_info_and_router.py | 324 +++++++++--------- .../emissions/get_region_metrics.py | 8 + 4 files changed, 196 insertions(+), 165 deletions(-) diff --git a/examples/my_experiment.py b/examples/my_experiment.py index b2d7095..eefb868 100644 --- a/examples/my_experiment.py +++ b/examples/my_experiment.py @@ -1,6 +1,7 @@ import sys import tempfile - +sys.path.append('../') +sys.path.append('./') import torch from experiment_impact_tracker.compute_tracker import ImpactTracker @@ -47,16 +48,19 @@ def train(self): self.w2 -= self.learning_rate * grad_w2 -def my_experiment() -> None: +def my_experiment(REGION_COORDS=None) -> None: tmp_dir = tempfile.mkdtemp() # Init tracker with log path - tracker = ImpactTracker(tmp_dir) + tracker = ImpactTracker(tmp_dir,REGION_COORDS) # Start tracker in a separate process tracker.launch_impact_monitor() + print(tracker.initial_info['region']) + print('') + print(tracker.initial_info['region_carbon_intensity_estimate']) exp = Experiment() - for t in range(100): + for t in range(10): if t % 10 == 9: print(f"Pass: {t}") # Optional. Adding this will ensure that your experiment stops if impact tracker throws an exception and exit. @@ -67,4 +71,5 @@ def my_experiment() -> None: if __name__ == "__main__": - my_experiment() + REGION_COORDS = (45.4972159,-73.6103642) #MTL:(45.4972159,-73.6103642), NYC:(40.741895,-73.989308), Pune:(18.521428,73.8544541), Paris:(48.8566969,2.3514616) + my_experiment(REGION_COORDS) diff --git a/experiment_impact_tracker/compute_tracker.py b/experiment_impact_tracker/compute_tracker.py index 65e695c..d4bc0d9 100644 --- a/experiment_impact_tracker/compute_tracker.py +++ b/experiment_impact_tracker/compute_tracker.py @@ -22,8 +22,10 @@ from experiment_impact_tracker.cpu import rapl from experiment_impact_tracker.cpu.common import get_my_cpu_info from experiment_impact_tracker.cpu.intel import get_intel_power, get_rapl_power -from experiment_impact_tracker.data_info_and_router import (DATA_HEADERS, - INITIAL_INFO) +# from experiment_impact_tracker.data_info_and_router import (DATA_HEADERS, +# INITIAL_INFO) +#import wrapper for DATA_HEADERS, INITIAL_INFO +from experiment_impact_tracker.data_info_and_router import get_initial_info, get_data_headers from experiment_impact_tracker.data_utils import * from experiment_impact_tracker.emissions.common import \ is_capable_realtime_carbon_intensity @@ -175,6 +177,7 @@ def _get_compatible_data_headers(region=None): :return: which headers are compatible """ compatible_headers = [] + DATA_HEADERS = get_data_headers() for header in DATA_HEADERS: compat = True @@ -203,7 +206,7 @@ def _validate_compatabilities(compatabilities, *args, **kwargs): return True -def gather_initial_info(log_dir: str): +def gather_initial_info(log_dir: str, REGION_COORDS=None): """Log one time info For example, CPU/GPU info, version of this package, region, datetime for start of experiment, @@ -217,6 +220,7 @@ def gather_initial_info(log_dir: str): data = {} + INITIAL_INFO = get_initial_info(REGION_COORDS) # Gather all the one-time info specified by the appropriate router for info_ in INITIAL_INFO: key = info_["name"] @@ -239,11 +243,11 @@ def gather_initial_info(log_dir: str): class ImpactTracker(object): - def __init__(self, logdir): + def __init__(self, logdir, REGION_COORDS=None): self.logdir = logdir self._setup_logging() self.logger.info("Gathering system info for reproducibility...") - self.initial_info = gather_initial_info(logdir) + self.initial_info = gather_initial_info(logdir, REGION_COORDS) self.logger.info("Done initial setup and information gathering...") self.launched = False diff --git a/experiment_impact_tracker/data_info_and_router.py b/experiment_impact_tracker/data_info_and_router.py index 71e71fc..fdecbe0 100644 --- a/experiment_impact_tracker/data_info_and_router.py +++ b/experiment_impact_tracker/data_info_and_router.py @@ -12,7 +12,7 @@ from experiment_impact_tracker.emissions.common import ( get_realtime_carbon, is_capable_realtime_carbon_intensity) from experiment_impact_tracker.emissions.get_region_metrics import \ - get_current_region_info_cached + get_current_region_info_cached, get_region_info from experiment_impact_tracker.gpu.nvidia import (get_gpu_info, get_nvidia_gpu_power, is_nvidia_compatible) @@ -25,158 +25,172 @@ get_time_now = lambda *args, **kwargs: datetime.now() all_compatible = lambda *args, **kwargs: True -INITIAL_INFO = [ - { - "name": "python_package_info", - "description": "Python package info.", - "compatability": [all_compatible], - "routing": {"function": get_python_packages_and_versions}, - }, - { - "name": "cpu_info", - "description": "CPU hardware information.", - "compatability": [all_compatible], - "routing": {"function": get_my_cpu_info}, - }, - { - "name": "experiment_start", - "description": "Start time of experiment.", - "compatability": [all_compatible], - "routing": {"function": get_time_now}, - }, - { - "name": "gpu_info", - "description": "GPU hardware information.", - "compatability": [is_nvidia_compatible, is_linux], - "routing": {"function": get_gpu_info}, - }, - { - "name": "experiment_impact_tracker_version", - "description": "Version of experiment-impact-tracker framework.", - "compatability": [all_compatible], - "routing": {"function": get_version_number}, - }, - { - "name": "region", - "description": "The region we determine this experiment to be run in.", - "compatability": [all_compatible], - "routing": {"function": lambda: get_current_region_info_cached()[0]}, - }, - { - "name": "region_carbon_intensity_estimate", - "description": "The average carbon intensity estimated for the region this experiment is in.", - "compatability": [all_compatible], - "routing": {"function": lambda: get_current_region_info_cached()[1]}, - }, -] +# Replaced INITIAL_INFO AND DATA_HEADERS with a function wrapper +def get_initial_info(REGION_COORDS=None): -DATA_HEADERS = [ - { - "name": "timestamp", - "description": "Time at which sample was drawn based on local machine time in timestamp format.", - "compatability": [all_compatible], - "routing": {"function": get_timestamp}, - }, - { - "name": "rapl_power_draw_absolute", - "description": "The absolute power draw reading read from an Intel RAPL package. This is in terms of Watts across the entire machine.", - "compatability": [is_intel_compatible], - "routing": {"function": get_intel_power}, - }, - { - "name": "rapl_estimated_attributable_power_draw", - "description": "This is the estimated attributable power draw to this process and all child processes based on power draw reading read from an Intel RAPL package. This is calculated as (watts used by cpu) * (relative cpu percentage used) + (watts used by dram) * (relative dram percentage used) + (watts used by other package elements) * (relative cpu percentage used).", - "compatability": [is_intel_compatible], - "routing": {"function": get_intel_power}, - }, - { - "name": "nvidia_draw_absolute", - "description": "This is the absolute power draw of all accessible NVIDIA GPUs on the system (as long as the main process or any child process lives on the GPU). Calculated as sum across all GPUs.", - "compatability": [is_nvidia_compatible, is_linux], - "routing": {"function": get_nvidia_gpu_power}, - }, - { - "name": "nvidia_estimated_attributable_power_draw", - "description": "This is the estimated attributable power draw of all accessible NVIDIA GPUs on the system (as long as the main process or any child process lives on the GPU). Calculated as the sum per gpu of (absolute power draw per gpu) * (relative process percent utilization of gpu)", - "compatability": [is_nvidia_compatible, is_linux], - "routing": {"function": get_nvidia_gpu_power}, - }, - { - "name": "cpu_time_seconds", - "description": "This is the total CPU time used so far by the program in seconds.", - "compatability": [is_intel_compatible], - "routing": {"function": get_intel_power}, - }, - { - "name": "average_gpu_estimated_utilization_absolute", - "description": "This is the absolute utilization of the GPUs by the main process and all child processes. Returns an average result across several trials of nvidia-smi pmon -c 10. Averaged across GPUs. Using .05 to indicate 5%.", - "compatability": [is_nvidia_compatible, is_linux], - "routing": {"function": get_nvidia_gpu_power}, - }, - { - "name": "average_gpu_estimated_utilization_relative", - "description": "This is the relative utilization of the GPUs by the main process and all child processes. Returns an average result across several trials of nvidia-smi pmon -c 10 and the percentage that this process and all child process utilize for the gpu. Averaged across GPUs. Using .05 to indicate 5%. ", - "compatability": [is_nvidia_compatible, is_linux], - "routing": {"function": get_nvidia_gpu_power}, - }, - { - "name": "average_relative_cpu_utilization", - "description": "This is the relative CPU utlization compared to the utilization of the whole system at that time. E.g., if the total system is using 50\% of the CPU power, but our program is only using 25\%, this will return .5.", - "compatability": [is_intel_compatible], - "routing": {"function": get_intel_power}, - }, - { - "name": "absolute_cpu_utilization", - "description": "This is the relative CPU utlization compared to the utilization of the whole system at that time. E.g., if the total system is using 50\% of 4 CPUs, but our program is only using 25\% of 2 CPUs, this will return .5 (same as in top). There is no multiplier times the number of cores in this case as top does. ", - "compatability": [is_intel_compatible], - "routing": {"function": get_intel_power}, - }, - { - "name": "per_gpu_performance_state", - "description": "A concatenated string which gives the performance state of every single GPU used by the main process or all child processes. Example formatting looks like ::. E.g., 0::P0", - "compatability": [is_nvidia_compatible, is_linux], - "routing": {"function": get_nvidia_gpu_power}, - }, - { - "name": "relative_mem_usage", - "description": "The percentage of all in-use ram this program is using.", - "compatability": [is_intel_compatible], - "routing": {"function": get_intel_power}, - }, - { - "name": "absolute_mem_usage", - "description": "The amount of memory being used.", - "compatability": [is_intel_compatible], - "routing": {"function": get_intel_power}, - }, - { - "name": "absolute_mem_percent_usage", - "description": "The amount of memory being used as an absolute percentage of total memory (RAM).", - "compatability": [is_intel_compatible], - "routing": {"function": get_intel_power}, - }, - { - "name": "cpu_count_adjusted_average_load", - "description": "Measures the average load on the system for the past 5, 10, 15 minutes divided by number of CPUs (wrapper for psutil method). As fraction (percentage needs multiplication by 100)", - "compatability": [all_compatible], - "routing": {"function": get_cpu_count_adjusted_load_avg}, - }, - { - "name": "cpu_freq", - "description": "Get cpu frequency including realtime in MHz.", - "compatability": [is_linux, is_cpu_freq_compatible], - "routing": {"function": get_cpu_freq}, - }, - { - "name": "realtime_carbon_intensity", - "description": "If available, the realtime carbon intensity in the region.", - "compatability": [is_capable_realtime_carbon_intensity], - "routing": {"function": get_realtime_carbon}, - }, - { - "name": "disk_write_speed", - "description": "The write speed to the disk estimated over .5 seconds.", - "compatability": [all_compatible], - "routing": {"function": measure_disk_speed_at_dir}, - }, -] + INITIAL_INFO = [ + { + "name": "python_package_info", + "description": "Python package info.", + "compatability": [all_compatible], + "routing": {"function": get_python_packages_and_versions}, + }, + { + "name": "cpu_info", + "description": "CPU hardware information.", + "compatability": [all_compatible], + "routing": {"function": get_my_cpu_info}, + }, + { + "name": "experiment_start", + "description": "Start time of experiment.", + "compatability": [all_compatible], + "routing": {"function": get_time_now}, + }, + { + "name": "gpu_info", + "description": "GPU hardware information.", + "compatability": [is_nvidia_compatible, is_linux], + "routing": {"function": get_gpu_info}, + }, + { + "name": "experiment_impact_tracker_version", + "description": "Version of experiment-impact-tracker framework.", + "compatability": [all_compatible], + "routing": {"function": get_version_number}, + }, + { + "name": "region", + "description": "The region we determine this experiment to be run in.", + "compatability": [all_compatible], + # "routing": {"function": lambda: get_current_region_info_cached()[0]}, + + #A wrapper for selecting current vs specific region_info + "routing": {"function": lambda: get_region_info(REGION_COORDS)[0]}, + }, + { + "name": "region_carbon_intensity_estimate", + "description": "The average carbon intensity estimated for the region this experiment is in.", + "compatability": [all_compatible], + # "routing": {"function": lambda: get_current_region_info_cached()[1]}, + + #A wrapper for selecting current vs specific region_info + "routing": {"function": lambda: get_region_info(REGION_COORDS)[1]}, + }, + ] + + return INITIAL_INFO + +def get_data_headers(): + + DATA_HEADERS = [ + { + "name": "timestamp", + "description": "Time at which sample was drawn based on local machine time in timestamp format.", + "compatability": [all_compatible], + "routing": {"function": get_timestamp}, + }, + { + "name": "rapl_power_draw_absolute", + "description": "The absolute power draw reading read from an Intel RAPL package. This is in terms of Watts across the entire machine.", + "compatability": [is_intel_compatible], + "routing": {"function": get_intel_power}, + }, + { + "name": "rapl_estimated_attributable_power_draw", + "description": "This is the estimated attributable power draw to this process and all child processes based on power draw reading read from an Intel RAPL package. This is calculated as (watts used by cpu) * (relative cpu percentage used) + (watts used by dram) * (relative dram percentage used) + (watts used by other package elements) * (relative cpu percentage used).", + "compatability": [is_intel_compatible], + "routing": {"function": get_intel_power}, + }, + { + "name": "nvidia_draw_absolute", + "description": "This is the absolute power draw of all accessible NVIDIA GPUs on the system (as long as the main process or any child process lives on the GPU). Calculated as sum across all GPUs.", + "compatability": [is_nvidia_compatible, is_linux], + "routing": {"function": get_nvidia_gpu_power}, + }, + { + "name": "nvidia_estimated_attributable_power_draw", + "description": "This is the estimated attributable power draw of all accessible NVIDIA GPUs on the system (as long as the main process or any child process lives on the GPU). Calculated as the sum per gpu of (absolute power draw per gpu) * (relative process percent utilization of gpu)", + "compatability": [is_nvidia_compatible, is_linux], + "routing": {"function": get_nvidia_gpu_power}, + }, + { + "name": "cpu_time_seconds", + "description": "This is the total CPU time used so far by the program in seconds.", + "compatability": [is_intel_compatible], + "routing": {"function": get_intel_power}, + }, + { + "name": "average_gpu_estimated_utilization_absolute", + "description": "This is the absolute utilization of the GPUs by the main process and all child processes. Returns an average result across several trials of nvidia-smi pmon -c 10. Averaged across GPUs. Using .05 to indicate 5%.", + "compatability": [is_nvidia_compatible, is_linux], + "routing": {"function": get_nvidia_gpu_power}, + }, + { + "name": "average_gpu_estimated_utilization_relative", + "description": "This is the relative utilization of the GPUs by the main process and all child processes. Returns an average result across several trials of nvidia-smi pmon -c 10 and the percentage that this process and all child process utilize for the gpu. Averaged across GPUs. Using .05 to indicate 5%. ", + "compatability": [is_nvidia_compatible, is_linux], + "routing": {"function": get_nvidia_gpu_power}, + }, + { + "name": "average_relative_cpu_utilization", + "description": "This is the relative CPU utlization compared to the utilization of the whole system at that time. E.g., if the total system is using 50\% of the CPU power, but our program is only using 25\%, this will return .5.", + "compatability": [is_intel_compatible], + "routing": {"function": get_intel_power}, + }, + { + "name": "absolute_cpu_utilization", + "description": "This is the relative CPU utlization compared to the utilization of the whole system at that time. E.g., if the total system is using 50\% of 4 CPUs, but our program is only using 25\% of 2 CPUs, this will return .5 (same as in top). There is no multiplier times the number of cores in this case as top does. ", + "compatability": [is_intel_compatible], + "routing": {"function": get_intel_power}, + }, + { + "name": "per_gpu_performance_state", + "description": "A concatenated string which gives the performance state of every single GPU used by the main process or all child processes. Example formatting looks like ::. E.g., 0::P0", + "compatability": [is_nvidia_compatible, is_linux], + "routing": {"function": get_nvidia_gpu_power}, + }, + { + "name": "relative_mem_usage", + "description": "The percentage of all in-use ram this program is using.", + "compatability": [is_intel_compatible], + "routing": {"function": get_intel_power}, + }, + { + "name": "absolute_mem_usage", + "description": "The amount of memory being used.", + "compatability": [is_intel_compatible], + "routing": {"function": get_intel_power}, + }, + { + "name": "absolute_mem_percent_usage", + "description": "The amount of memory being used as an absolute percentage of total memory (RAM).", + "compatability": [is_intel_compatible], + "routing": {"function": get_intel_power}, + }, + { + "name": "cpu_count_adjusted_average_load", + "description": "Measures the average load on the system for the past 5, 10, 15 minutes divided by number of CPUs (wrapper for psutil method). As fraction (percentage needs multiplication by 100)", + "compatability": [all_compatible], + "routing": {"function": get_cpu_count_adjusted_load_avg}, + }, + { + "name": "cpu_freq", + "description": "Get cpu frequency including realtime in MHz.", + "compatability": [is_linux, is_cpu_freq_compatible], + "routing": {"function": get_cpu_freq}, + }, + { + "name": "realtime_carbon_intensity", + "description": "If available, the realtime carbon intensity in the region.", + "compatability": [is_capable_realtime_carbon_intensity], + "routing": {"function": get_realtime_carbon}, + }, + { + "name": "disk_write_speed", + "description": "The write speed to the disk estimated over .5 seconds.", + "compatability": [all_compatible], + "routing": {"function": measure_disk_speed_at_dir}, + }, + ] + return DATA_HEADERS \ No newline at end of file diff --git a/experiment_impact_tracker/emissions/get_region_metrics.py b/experiment_impact_tracker/emissions/get_region_metrics.py index 573a6a9..4cc1cc1 100644 --- a/experiment_impact_tracker/emissions/get_region_metrics.py +++ b/experiment_impact_tracker/emissions/get_region_metrics.py @@ -42,6 +42,14 @@ def get_current_location(): def get_current_region_info(*args, **kwargs): return get_zone_information_by_coords(get_current_location()) +### Added by nikhil153 to avoid error in get_current_region_info_cached on CC +def get_region_info(region_coords=None): + ''' Wrapper func to grab zone info based on either specific lat-long coordinates or default current region + ''' + if region_coords == None: + return get_current_region_info_cached() + else: + return get_zone_information_by_coords(region_coords) def get_zone_name_by_id(zone_id): zone = ZONE_NAMES["zoneShortName"][zone_id] From 79798793e54cec0908f66b721e863c0eca94a40d Mon Sep 17 00:00:00 2001 From: nikhil153 Date: Mon, 5 Apr 2021 21:30:02 -0400 Subject: [PATCH 2/9] fixed zombie indexing to prevent monitor process from crashing --- experiment_impact_tracker/cpu/intel.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/experiment_impact_tracker/cpu/intel.py b/experiment_impact_tracker/cpu/intel.py index cb7a39a..05adf64 100644 --- a/experiment_impact_tracker/cpu/intel.py +++ b/experiment_impact_tracker/cpu/intel.py @@ -151,6 +151,7 @@ def get_powercap_power(pid_list, logger=None, **kwargs): zombies.append(i) time.sleep(2.0) + for p in process_list: st21 = _timer() @@ -355,6 +356,7 @@ def get_rapl_power(pid_list, logger=None, **kwargs): # Modifying code https://github.com/giampaolo/psutil/blob/c10df5aa04e1ced58d19501fa42f08c1b909b83d/psutil/__init__.py#L1102-L1107 # We want relative percentage of CPU used so we ignore the multiplier by number of CPUs, we want a number from 0-1.0 to give # power credits accordingly + st11 = _timer() # units in terms of cpu-time, so we need the cpu in the last time period that are for the process only system_wide_pt1 = psutil.cpu_times() @@ -363,11 +365,13 @@ def get_rapl_power(pid_list, logger=None, **kwargs): pt1 = p.cpu_times() infos1.append((st11, st12, system_wide_pt1, pt1)) except (psutil.NoSuchProcess, psutil.ZombieProcess): + infos1.append(None) # Maintain the length equal to process_list to avoid index mismatch during power sampling. zombies.append(i) time.sleep(2.0) - for p in process_list: + # Added enumerater "i" to keep track of zombie processes. + for i, p in enumerate(process_list): st21 = _timer() try: pt2 = p.cpu_times() @@ -375,8 +379,9 @@ def get_rapl_power(pid_list, logger=None, **kwargs): system_wide_pt2 = psutil.cpu_times() infos2.append((st21, st22, system_wide_pt2, pt2)) except (psutil.NoSuchProcess, psutil.ZombieProcess): + infos2.append(None) # Maintain the length equal to process_list to avoid index mismatch during power sampling. zombies.append(i) - + # now is a good time to get the power samples that we got the process times for s2 = rapl.RAPLMonitor.sample() diff = s2 - s1 @@ -427,7 +432,8 @@ def get_rapl_power(pid_list, logger=None, **kwargs): if total_gpu_power != 0: raise ValueError("Don't support credit assignment to Intel RAPL GPU yet.") - for i, p in enumerate(process_list): + for i, p in enumerate(process_list): + # Ignore calculations for the zombie processes. They are denoted as None in the infos lists. if i in zombies: continue From e7f8356fe08fdb9c50eb9f20bc185a09c244553d Mon Sep 17 00:00:00 2001 From: Nikhil Bhagwat Date: Mon, 5 Apr 2021 22:37:52 -0400 Subject: [PATCH 3/9] fixed custom location override for _sample_and_log_power --- experiment_impact_tracker/compute_tracker.py | 18 +++++++++++------- .../emissions/get_region_metrics.py | 1 + 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/experiment_impact_tracker/compute_tracker.py b/experiment_impact_tracker/compute_tracker.py index d4bc0d9..e8a5145 100644 --- a/experiment_impact_tracker/compute_tracker.py +++ b/experiment_impact_tracker/compute_tracker.py @@ -30,7 +30,7 @@ from experiment_impact_tracker.emissions.common import \ is_capable_realtime_carbon_intensity from experiment_impact_tracker.emissions.get_region_metrics import \ - get_current_region_info_cached + get_current_region_info_cached, get_region_info from experiment_impact_tracker.gpu.nvidia import (get_gpu_info, get_nvidia_gpu_power) from experiment_impact_tracker.utils import (get_timestamp, processify, @@ -75,7 +75,7 @@ def read_latest_stats(log_dir): return None -def _sample_and_log_power(log_dir, initial_info, logger=None): +def _sample_and_log_power(log_dir, REGION_COORDS, initial_info, logger=None): """ Iterates over compatible metrics and logs the relevant information. @@ -92,7 +92,8 @@ def _sample_and_log_power(log_dir, initial_info, logger=None): set(process_ids) ) # dedupe so that we don't double count by accident - required_headers = _get_compatible_data_headers(get_current_region_info_cached()[0]) + #required_headers = _get_compatible_data_headers(get_current_region_info_cached()[0]) + required_headers = _get_compatible_data_headers(get_region_info(REGION_COORDS)[0]) header_information = {} @@ -137,7 +138,7 @@ def _sample_and_log_power(log_dir, initial_info, logger=None): @processify -def launch_power_monitor(queue, log_dir, initial_info, logger=None): +def launch_power_monitor(queue, log_dir, REGION_COORDS, initial_info, logger=None): """ Launches a separate process which monitors metrics @@ -160,7 +161,7 @@ def launch_power_monitor(queue, log_dir, initial_info, logger=None): pass try: - _sample_and_log_power(log_dir, initial_info, logger=logger) + _sample_and_log_power(log_dir, REGION_COORDS, initial_info, logger=logger) except: ex_type, ex_value, tb = sys.exc_info() logger.error("Encountered exception within power monitor thread!") @@ -219,8 +220,10 @@ def gather_initial_info(log_dir: str, REGION_COORDS=None): info_path = safe_file_path(os.path.join(log_dir, INFOPATH)) data = {} - + + print('Region coords: {}'.format(REGION_COORDS)) INITIAL_INFO = get_initial_info(REGION_COORDS) + # Gather all the one-time info specified by the appropriate router for info_ in INITIAL_INFO: key = info_["name"] @@ -245,6 +248,7 @@ def gather_initial_info(log_dir: str, REGION_COORDS=None): class ImpactTracker(object): def __init__(self, logdir, REGION_COORDS=None): self.logdir = logdir + self.region_coords = REGION_COORDS self._setup_logging() self.logger.info("Gathering system info for reproducibility...") self.initial_info = gather_initial_info(logdir, REGION_COORDS) @@ -296,7 +300,7 @@ def launch_impact_monitor(self): # OS X multiprocessing starts processes with spawn instead of fork multiprocessing.set_start_method("fork") self.p, self.queue = launch_power_monitor( - self.logdir, self.initial_info, self.logger + self.logdir, self.region_coords, self.initial_info, self.logger ) def _terminate_monitor_and_log_final_info(p): diff --git a/experiment_impact_tracker/emissions/get_region_metrics.py b/experiment_impact_tracker/emissions/get_region_metrics.py index 4cc1cc1..e52f7a8 100644 --- a/experiment_impact_tracker/emissions/get_region_metrics.py +++ b/experiment_impact_tracker/emissions/get_region_metrics.py @@ -46,6 +46,7 @@ def get_current_region_info(*args, **kwargs): def get_region_info(region_coords=None): ''' Wrapper func to grab zone info based on either specific lat-long coordinates or default current region ''' + print('Emissions region coords: {}'.format(region_coords)) if region_coords == None: return get_current_region_info_cached() else: From 0013de3175591c3e0e402dc7f31bbb741eaa4e02 Mon Sep 17 00:00:00 2001 From: nikhil153 Date: Tue, 6 Apr 2021 11:25:29 -0400 Subject: [PATCH 4/9] manual merge for the powercap index fix --- experiment_impact_tracker/cpu/intel.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/experiment_impact_tracker/cpu/intel.py b/experiment_impact_tracker/cpu/intel.py index 05adf64..0f2071c 100644 --- a/experiment_impact_tracker/cpu/intel.py +++ b/experiment_impact_tracker/cpu/intel.py @@ -148,12 +148,13 @@ def get_powercap_power(pid_list, logger=None, **kwargs): pt1 = p.cpu_times() infos1.append((st11, st12, system_wide_pt1, pt1)) except (psutil.NoSuchProcess, psutil.ZombieProcess): + infos1.append(None) # Maintain the length equal to process_list to avoid index mismatch during power sampling. zombies.append(i) time.sleep(2.0) - - for p in process_list: + # for p in process_list: + for i, p in enumerate(process_list): st21 = _timer() try: pt2 = p.cpu_times() @@ -161,7 +162,9 @@ def get_powercap_power(pid_list, logger=None, **kwargs): system_wide_pt2 = psutil.cpu_times() infos2.append((st21, st22, system_wide_pt2, pt2)) except (psutil.NoSuchProcess, psutil.ZombieProcess): + infos2.append(None) # Maintain the length equal to process_list to avoid index mismatch during power sampling. zombies.append(i) + # now is a good time to get the power samples that we got the process times for powercap_results = powercap_interface.join() total_intel_power = powercap_results.get("Processor Power_0(Watt)", 0) @@ -173,6 +176,7 @@ def get_powercap_power(pid_list, logger=None, **kwargs): raise ValueError("Don't support credit assignment to Intel RAPL GPU yet.") for i, p in enumerate(process_list): + # Ignore calculations for the zombie processes. They are denoted as None in the infos lists. if i in zombies: continue st1, st12, system_wide_pt1, pt1 = infos1[i] From 0b1531935d48de11c5118dfb199cdbf215e028c0 Mon Sep 17 00:00:00 2001 From: nikhil153 Date: Sat, 24 Apr 2021 14:02:57 -0400 Subject: [PATCH 5/9] fixed python3.6 error with utf encoding --- experiment_impact_tracker/emissions/constants.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/experiment_impact_tracker/emissions/constants.py b/experiment_impact_tracker/emissions/constants.py index 2030418..158ae08 100644 --- a/experiment_impact_tracker/emissions/constants.py +++ b/experiment_impact_tracker/emissions/constants.py @@ -46,8 +46,8 @@ def _load_zone_names(): dict : the loaded json file """ dir_path = os.path.dirname(os.path.realpath(__file__)) - with open(os.path.join(dir_path, "data/zone_names.json"), "rt") as f: - x = json.load(f) + with open(os.path.join(dir_path, "data/zone_names.json"), "rt", encoding='utf-8') as f: + x = json.load(f ) return x From 7e5e00816216fb1d0014152de9080392227949d0 Mon Sep 17 00:00:00 2001 From: nikhil153 Date: Sat, 1 May 2021 13:42:44 -0400 Subject: [PATCH 6/9] minor cleanups before location_overide PR --- examples/my_experiment.py | 10 +++++++--- experiment_impact_tracker/compute_tracker.py | 10 +++++----- experiment_impact_tracker/data_info_and_router.py | 12 +++++------- .../emissions/get_region_metrics.py | 3 +-- 4 files changed, 18 insertions(+), 17 deletions(-) diff --git a/examples/my_experiment.py b/examples/my_experiment.py index eefb868..5a1ccdb 100644 --- a/examples/my_experiment.py +++ b/examples/my_experiment.py @@ -48,7 +48,7 @@ def train(self): self.w2 -= self.learning_rate * grad_w2 -def my_experiment(REGION_COORDS=None) -> None: +def my_experiment(region_coords=None) -> None: tmp_dir = tempfile.mkdtemp() # Init tracker with log path tracker = ImpactTracker(tmp_dir,REGION_COORDS) @@ -60,7 +60,7 @@ def my_experiment(REGION_COORDS=None) -> None: print(tracker.initial_info['region_carbon_intensity_estimate']) exp = Experiment() - for t in range(10): + for t in range(100): if t % 10 == 9: print(f"Pass: {t}") # Optional. Adding this will ensure that your experiment stops if impact tracker throws an exception and exit. @@ -71,5 +71,9 @@ def my_experiment(REGION_COORDS=None) -> None: if __name__ == "__main__": - REGION_COORDS = (45.4972159,-73.6103642) #MTL:(45.4972159,-73.6103642), NYC:(40.741895,-73.989308), Pune:(18.521428,73.8544541), Paris:(48.8566969,2.3514616) + + # Example latitude and longitude by city + # MTL:(45.4972159,-73.6103642), NYC:(40.741895,-73.989308), Pune:(18.521428,73.8544541), Paris:(48.8566969,2.3514616) + + REGION_COORDS = (45.4972159,-73.6103642) my_experiment(REGION_COORDS) diff --git a/experiment_impact_tracker/compute_tracker.py b/experiment_impact_tracker/compute_tracker.py index e8a5145..8fcad16 100644 --- a/experiment_impact_tracker/compute_tracker.py +++ b/experiment_impact_tracker/compute_tracker.py @@ -22,10 +22,10 @@ from experiment_impact_tracker.cpu import rapl from experiment_impact_tracker.cpu.common import get_my_cpu_info from experiment_impact_tracker.cpu.intel import get_intel_power, get_rapl_power -# from experiment_impact_tracker.data_info_and_router import (DATA_HEADERS, -# INITIAL_INFO) + #import wrapper for DATA_HEADERS, INITIAL_INFO from experiment_impact_tracker.data_info_and_router import get_initial_info, get_data_headers + from experiment_impact_tracker.data_utils import * from experiment_impact_tracker.emissions.common import \ is_capable_realtime_carbon_intensity @@ -246,12 +246,12 @@ def gather_initial_info(log_dir: str, REGION_COORDS=None): class ImpactTracker(object): - def __init__(self, logdir, REGION_COORDS=None): + def __init__(self, logdir, region_coords=None): self.logdir = logdir - self.region_coords = REGION_COORDS + self.region_coords = region_coords self._setup_logging() self.logger.info("Gathering system info for reproducibility...") - self.initial_info = gather_initial_info(logdir, REGION_COORDS) + self.initial_info = gather_initial_info(logdir, region_coords) self.logger.info("Done initial setup and information gathering...") self.launched = False diff --git a/experiment_impact_tracker/data_info_and_router.py b/experiment_impact_tracker/data_info_and_router.py index fdecbe0..3594e98 100644 --- a/experiment_impact_tracker/data_info_and_router.py +++ b/experiment_impact_tracker/data_info_and_router.py @@ -26,7 +26,7 @@ all_compatible = lambda *args, **kwargs: True # Replaced INITIAL_INFO AND DATA_HEADERS with a function wrapper -def get_initial_info(REGION_COORDS=None): +def get_initial_info(region_coords=None): INITIAL_INFO = [ { @@ -63,19 +63,17 @@ def get_initial_info(REGION_COORDS=None): "name": "region", "description": "The region we determine this experiment to be run in.", "compatability": [all_compatible], - # "routing": {"function": lambda: get_current_region_info_cached()[0]}, - + #A wrapper for selecting current vs specific region_info - "routing": {"function": lambda: get_region_info(REGION_COORDS)[0]}, + "routing": {"function": lambda: get_region_info(region_coords)[0]}, }, { "name": "region_carbon_intensity_estimate", "description": "The average carbon intensity estimated for the region this experiment is in.", "compatability": [all_compatible], - # "routing": {"function": lambda: get_current_region_info_cached()[1]}, - + #A wrapper for selecting current vs specific region_info - "routing": {"function": lambda: get_region_info(REGION_COORDS)[1]}, + "routing": {"function": lambda: get_region_info(region_coords)[1]}, }, ] diff --git a/experiment_impact_tracker/emissions/get_region_metrics.py b/experiment_impact_tracker/emissions/get_region_metrics.py index e52f7a8..5107503 100644 --- a/experiment_impact_tracker/emissions/get_region_metrics.py +++ b/experiment_impact_tracker/emissions/get_region_metrics.py @@ -42,11 +42,10 @@ def get_current_location(): def get_current_region_info(*args, **kwargs): return get_zone_information_by_coords(get_current_location()) -### Added by nikhil153 to avoid error in get_current_region_info_cached on CC +### Added by nikhil153 to avoid error in get_current_region_info_cached on offline clusters def get_region_info(region_coords=None): ''' Wrapper func to grab zone info based on either specific lat-long coordinates or default current region ''' - print('Emissions region coords: {}'.format(region_coords)) if region_coords == None: return get_current_region_info_cached() else: From 12f37fe9a927a44e43ecb0db514d12fda1a9122b Mon Sep 17 00:00:00 2001 From: nikhil153 Date: Sat, 1 May 2021 13:51:23 -0400 Subject: [PATCH 7/9] minor cleanups before location_overide PR --- experiment_impact_tracker/compute_tracker.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/experiment_impact_tracker/compute_tracker.py b/experiment_impact_tracker/compute_tracker.py index 8fcad16..aa56206 100644 --- a/experiment_impact_tracker/compute_tracker.py +++ b/experiment_impact_tracker/compute_tracker.py @@ -75,7 +75,7 @@ def read_latest_stats(log_dir): return None -def _sample_and_log_power(log_dir, REGION_COORDS, initial_info, logger=None): +def _sample_and_log_power(log_dir, region_coords, initial_info, logger=None): """ Iterates over compatible metrics and logs the relevant information. @@ -93,7 +93,7 @@ def _sample_and_log_power(log_dir, REGION_COORDS, initial_info, logger=None): ) # dedupe so that we don't double count by accident #required_headers = _get_compatible_data_headers(get_current_region_info_cached()[0]) - required_headers = _get_compatible_data_headers(get_region_info(REGION_COORDS)[0]) + required_headers = _get_compatible_data_headers(get_region_info(region_coords)[0]) header_information = {} @@ -138,7 +138,7 @@ def _sample_and_log_power(log_dir, REGION_COORDS, initial_info, logger=None): @processify -def launch_power_monitor(queue, log_dir, REGION_COORDS, initial_info, logger=None): +def launch_power_monitor(queue, log_dir, region_coords, initial_info, logger=None): """ Launches a separate process which monitors metrics @@ -161,7 +161,7 @@ def launch_power_monitor(queue, log_dir, REGION_COORDS, initial_info, logger=Non pass try: - _sample_and_log_power(log_dir, REGION_COORDS, initial_info, logger=logger) + _sample_and_log_power(log_dir, region_coords, initial_info, logger=logger) except: ex_type, ex_value, tb = sys.exc_info() logger.error("Encountered exception within power monitor thread!") @@ -207,7 +207,7 @@ def _validate_compatabilities(compatabilities, *args, **kwargs): return True -def gather_initial_info(log_dir: str, REGION_COORDS=None): +def gather_initial_info(log_dir: str, region_coords=None): """Log one time info For example, CPU/GPU info, version of this package, region, datetime for start of experiment, @@ -221,8 +221,8 @@ def gather_initial_info(log_dir: str, REGION_COORDS=None): data = {} - print('Region coords: {}'.format(REGION_COORDS)) - INITIAL_INFO = get_initial_info(REGION_COORDS) + print('Region coords: {}'.format(region_coords)) + INITIAL_INFO = get_initial_info(region_coords) # Gather all the one-time info specified by the appropriate router for info_ in INITIAL_INFO: From 47fee6c83ce5398616f400305de0dbba6a66a6fc Mon Sep 17 00:00:00 2001 From: nikhil153 Date: Sat, 1 May 2021 13:59:30 -0400 Subject: [PATCH 8/9] minor cleanups before location_overide PR --- examples/my_experiment.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/my_experiment.py b/examples/my_experiment.py index 5a1ccdb..93be6d2 100644 --- a/examples/my_experiment.py +++ b/examples/my_experiment.py @@ -51,7 +51,7 @@ def train(self): def my_experiment(region_coords=None) -> None: tmp_dir = tempfile.mkdtemp() # Init tracker with log path - tracker = ImpactTracker(tmp_dir,REGION_COORDS) + tracker = ImpactTracker(tmp_dir,region_coords) # Start tracker in a separate process tracker.launch_impact_monitor() @@ -74,6 +74,5 @@ def my_experiment(region_coords=None) -> None: # Example latitude and longitude by city # MTL:(45.4972159,-73.6103642), NYC:(40.741895,-73.989308), Pune:(18.521428,73.8544541), Paris:(48.8566969,2.3514616) - REGION_COORDS = (45.4972159,-73.6103642) my_experiment(REGION_COORDS) From 2aabcf2d4523c315ec1dede8325301dc7b725d14 Mon Sep 17 00:00:00 2001 From: nikhil153 Date: Wed, 9 Jun 2021 10:35:03 -0400 Subject: [PATCH 9/9] added example code for recalculating emissions based on PUE --- examples/compute_emissons.ipynb | 328 ++++++++++++++++++++++++++++++++ 1 file changed, 328 insertions(+) create mode 100644 examples/compute_emissons.ipynb diff --git a/examples/compute_emissons.ipynb b/examples/compute_emissons.ipynb new file mode 100644 index 0000000..48ac0bb --- /dev/null +++ b/examples/compute_emissons.ipynb @@ -0,0 +1,328 @@ +{ + "metadata": { + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.9" + }, + "orig_nbformat": 2, + "kernelspec": { + "name": "python379jvsc74a57bd0e5f8cee7ddba11edeefb1347c6536a4ac2b361bd4eba89a8b32d7cb85bbef9ea", + "display_name": "Python 3.7.9 64-bit ('green_compute': conda)" + } + }, + "nbformat": 4, + "nbformat_minor": 2, + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "loading region bounding boxes for computing carbon emissions region, this may take a moment...\n", + " 454/454... rate=490.32 Hz, eta=0:00:00, total=0:00:00, wall=21:59 EST\n", + "Done!\n", + "../experiment_impact_tracker/data_interface.py:37: FutureWarning: Passing a negative integer is deprecated in version 1.0 and will not be supported in future version. Instead, use None to not limit the column width.\n", + " pd.set_option(\"display.max_colwidth\", -1)\n" + ] + } + ], + "source": [ + "from __future__ import print_function\n", + "\n", + "import argparse\n", + "import json\n", + "import os\n", + "import re\n", + "import sys\n", + "from datetime import datetime\n", + "from importlib import import_module\n", + "from itertools import combinations\n", + "from pprint import pprint\n", + "from shutil import copyfile\n", + "\n", + "import numpy as np\n", + "import pandas as pd\n", + "import scipy\n", + "from deepdiff import DeepDiff # For Deep Difference of 2 objects\n", + "from jinja2 import Environment, FileSystemLoader\n", + "\n", + "from dask import compute, delayed\n", + "import sys\n", + "import glob\n", + "import matplotlib.pyplot as plt\n", + "import seaborn as sns\n", + "sys.path.append('../')\n", + "sys.path.append('../experiment-impact-tracker/')\n", + "\n", + "import experiment_impact_tracker\n", + "from experiment_impact_tracker.create_graph_appendix import (\n", + " create_graphs, create_scatterplot_from_df)\n", + "from experiment_impact_tracker.data_interface import DataInterface\n", + "from experiment_impact_tracker.data_utils import (load_data_into_frame,\n", + " load_initial_info,\n", + " zip_data_and_info)\n", + "from experiment_impact_tracker.emissions.common import \\\n", + " get_realtime_carbon_source\n", + "from experiment_impact_tracker.emissions.constants import PUE\n", + "from experiment_impact_tracker.emissions.get_region_metrics import \\\n", + " get_zone_name_by_id\n", + "from experiment_impact_tracker.stats import (get_average_treatment_effect,\n", + " run_test)\n", + "from experiment_impact_tracker.utils import gather_additional_info\n", + "\n", + "# pd.set_option('display.max_colwidth', -1)" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "project_dir = '../../watts_up_compute/'\n", + "fastsurfer_exp_dir = '{}/FastSurfer_experiments/'.format(project_dir)\n", + "preproc_exp_dir = '{}/preproc_pipeline_experiments/'.format(project_dir)\n", + "fastsurfer_results_dir = '{}results/exp_impact_tracker/'.format(fastsurfer_exp_dir)\n", + "preproc_results_dir = '{}results/exp_impact_tracker/'.format(preproc_exp_dir)\n", + "subject_lists = '{}subject_lists/ukb_pilot_subjects.csv'.format(project_dir)\n", + "\n", + "FastCNN_dir = fastsurfer_results_dir + 'ukb/FastCNN/'\n", + "FastRecon_dir = fastsurfer_results_dir + 'ukb/FastRecon/'" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "# parser = argparse.ArgumentParser(\n", + "# description=__doc__,\n", + "# formatter_class=argparse.RawDescriptionHelpFormatter)\n", + "\n", + "# parser.add_argument('logdirs', nargs='+',\n", + "# help=\"Input directories\", type=str)\n", + "# parser.add_argument('ISO3_COUNTRY_CODE')\n", + "# args = parser.parse_args(arguments)\n", + "\n", + "def get_footprint(logdirs, PUE, ISO3_COUNTRY_CODE):\n", + "\n", + " data_interface = DataInterface(logdirs)\n", + "\n", + " total_power = data_interface.total_power\n", + " kg_carbon = data_interface.kg_carbon\n", + " # PUE = data_interface.PUE\n", + " total_wall_clock_time = data_interface.exp_len_hours\n", + "\n", + " cscc_filepath = os.path.join(os.path.dirname(experiment_impact_tracker.__file__),\n", + " 'emissions/data/cscc_db_v2.csv')\n", + "\n", + " ssc = pd.read_csv(cscc_filepath)\n", + "\n", + " # only use short-run model\n", + " ssc = ssc[ssc[\"run\"] == \"bhm_sr\"]\n", + " ssc = ssc[ssc[\"SSP\"] == \"SSP2\"]\n", + " ssc = ssc[ssc[\"ISO3\"] == ISO3_COUNTRY_CODE]\n", + " ssc = ssc[np.isnan(ssc[\"dr\"])] # use only growth adjusted models\n", + " ssc = ssc[ssc[\"prtp\"] == 2] # a growth adjusted discount rate with 2% pure rate of time preference\n", + " ssc = ssc[ssc[\"eta\"] == \"1p5\"] # IES of 1.5\n", + " ssc = ssc[ssc[\"RCP\"] == \"rcp60\"] # rcp 6, middle of the road\n", + " ssc = ssc[ssc[\"dmgfuncpar\"] == \"bootstrap\"]\n", + " ssc = ssc[ssc[\"climate\"] == \"uncertain\"] \n", + "\n", + " median = ssc[\"50%\"]\n", + " lower = ssc[\"16.7%\"]\n", + " upper = ssc[\"83.3%\"]\n", + "\n", + " median_carbon_cost = (kg_carbon / 1000.) * float(median)\n", + " upper_carbon_cost = (kg_carbon / 1000.) * float(upper)\n", + " lower_carbon_cost = (kg_carbon / 1000.) * float(lower)\n", + "\n", + " footprint = pd.DataFrame(columns = ['PUE','ISO3_COUNTRY_CODE','total_wall_clock_time','total_power','kg_carbon','carbon_cost'])\n", + " footprint.loc[0] = [PUE, ISO3_COUNTRY_CODE,total_wall_clock_time,total_power,kg_carbon,median_carbon_cost]\n", + "\n", + " return footprint\n", + "\n", + "def get_footprint_exp_set(exp_dirs, PUE, ISO3_COUNTRY_CODE):\n", + "\n", + " values = [delayed(get_footprint)(exp_dir, PUE, ISO3_COUNTRY_CODE) for exp_dir in exp_dirs]\n", + " df_list = compute(*values, scheduler='processes', num_workers=4)\n", + "\n", + " footprint_df = pd.DataFrame()\n", + " for exp_dir, df in zip(exp_dirs, df_list): \n", + " df['exp_name'] = exp_dir.rsplit('/',1)[1]\n", + " footprint_df = footprint_df.append(df)\n", + "\n", + " return footprint_df" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "exp_set: recon-all_run_1, exp_dirs: 73\n", + "PUE: 1\n", + "PUE: 1.5\n", + "PUE: 2\n", + "exp_set: fastsurferCNN_run1_gpu_prune_0, exp_dirs: 73\n", + "PUE: 1\n", + "PUE: 1.5\n", + "PUE: 2\n", + "exp_set: fastsurferCNN_run3_cpu_prune_0, exp_dirs: 73\n", + "PUE: 1\n", + "PUE: 1.5\n", + "PUE: 2\n", + "exp_set: fastsurferRecon_run1_cpu_prune_0, exp_dirs: 73\n", + "PUE: 1\n", + "PUE: 1.5\n", + "PUE: 2\n" + ] + } + ], + "source": [ + "experiment_sets = {\n", + " ## FreeSurfer baseline\n", + " 'recon-all_run_1':(preproc_results_dir + 'ukb/run_1/', False), # log_dir, use_cuda\n", + " \n", + " ## FastSurferCNN run1 gpu\n", + " 'fastsurferCNN_run1_gpu_prune_0':(FastCNN_dir + 'run_1/gpu/prune_0/', True), # log_dir, use_cuda\n", + " \n", + " ## FastSurferCNN run3 cpu\n", + " 'fastsurferCNN_run3_cpu_prune_0':(FastCNN_dir + 'run_3/cpu/prune_0/', False), # log_dir, use_cuda \n", + " \n", + " ## FastSurferRecon run1 cpu\n", + " 'fastsurferRecon_run1_cpu_prune_0':(FastRecon_dir + 'run_1/', False), # log_dir, use_cuda\n", + "\n", + " }\n", + "\n", + "# PUE = 1.4 #computer canada: 1.2\n", + "PUE_list = [1,1.5,2]\n", + "ISO3_COUNTRY_CODE = 'CAN'\n", + "\n", + "footprint_df = pd.DataFrame()\n", + "for exp_set, exp_set_config in experiment_sets.items():\n", + " exp_set_dir = exp_set_config[0]\n", + " exp_set_dirs = glob.glob(f'{exp_set_dir}/sub*')\n", + " print(f'exp_set: {exp_set}, exp_dirs: {len(exp_set_dirs)}')\n", + "\n", + " for PUE in PUE_list:\n", + " print(f'PUE: {PUE}')\n", + " os.environ[\"OVERRIDE_PUE\"] = str(PUE)\n", + "\n", + " df = get_footprint_exp_set(exp_set_dirs, PUE, ISO3_COUNTRY_CODE)\n", + " df['exp_set'] = exp_set\n", + "\n", + " footprint_df = footprint_df.append(df)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "(876, 8)" + ] + }, + "metadata": {}, + "execution_count": 7 + } + ], + "source": [ + "# footprint_df.to_csv('/home/nikhil/projects/green_comp_neuro/watts_up_compute/PUE/ukb_pilot_run.csv')\n", + "footprint_df.shape" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "(219, 8) (438, 8) (438, 8)\n" + ] + } + ], + "source": [ + "# Start time 3pm\n", + "\n", + "footprint_df_recon_all = footprint_df[footprint_df['exp_set']=='recon-all_run_1'].copy()\n", + "footprint_df_recon_all['exp_set'] = 'FreeSurfer'\n", + "\n", + "footprint_df_fastsurfer_cpu = footprint_df[footprint_df['exp_set'].isin(['fastsurferCNN_run3_cpu_prune_0','fastsurferRecon_run1_cpu_prune_0'])].copy()\n", + "footprint_df_fastsurfer_gpu = footprint_df[footprint_df['exp_set'].isin(['fastsurferCNN_run1_gpu_prune_0','fastsurferRecon_run1_cpu_prune_0'])].copy()\n", + "\n", + "print(footprint_df_recon_all.shape,footprint_df_fastsurfer_cpu.shape,footprint_df_fastsurfer_gpu.shape)\n", + "\n", + "### This is sanity-checked\n", + "footprint_df_fastsurfer_cpu = footprint_df_fastsurfer_cpu.groupby(['exp_name','PUE']).sum().reset_index()\n", + "footprint_df_fastsurfer_gpu = footprint_df_fastsurfer_gpu.groupby(['exp_name','PUE']).sum().reset_index()\n", + "\n", + "footprint_df_fastsurfer_cpu['exp_set'] = 'FastSurfer_cpu'\n", + "footprint_df_fastsurfer_gpu['exp_set'] = 'FastSurfer_gpu'\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "output_type": "display_data", + "data": { + "text/plain": "
", + "image/svg+xml": "\n\n\n\n \n \n \n \n 2021-06-08T10:12:39.459707\n image/svg+xml\n \n \n Matplotlib v3.3.2, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n", + "image/png": "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4rRixYoyHZObm+voIc7Ly9PevXtLfbNm3759FRERUab2goODy7S/VQU3ngY3ruD2GlfeawLg3iwF4rS0NM2fP18rVqxQQkKCTNO84P5bt251PPb09FTHjh3Vr18/RUZGyt/f30oJAFDleXt7y8vLSzabTYGBgfL29q7Q9iprxpKCUD9t2rRKaQ8AKlqZAvGhQ4c0a9YsLV26VNnZ2ZL0t2H4r2w2mzZt2qRNmzZp0qRJuuuuu/TAAw+oadOmZToPAFSmiIiIMvfYStKIESN04MABzZw5kxkmAKCKKlUgPn36tN566y3Nnz9feXl5RUJw06ZNFRISolatWqlu3bqqW7eu/Pz8lJ2drXPnzunYsWM6cuSIdu3apSNHjjiOz8zM1Ny5czVv3jzdc889GjZsGD8wANQo3t7eCg4O5v82AKjC/jYQz58/X//+9791+vRpR5Bt2rSpbrzxRvXs2VOdO3cu07CH1NRUrVu3TmvWrFF8fLzS0tKUm5urmJgYLV68WM8884zuvvtu668IAAAAKIMSA3FKSorGjRunH374QaZpysvLS71799bdd9+t6667znKDAQEBuvnmm3XzzTcrNzdXP/zwg7788kv9/PPPOnPmjF555RUtX75cb775pho2bGi5HQAAAKA0SgzEd9xxh5KTk2UYhu644w499dRTatGihVMb9/b2Vu/evdW7d28lJiZq5syZWr58udauXat+/frpp59+cmp7qN5SU1Nlq4QppSqLLUVK9Ul1dRkAALi9EpPFqVOn1LlzZ82fP19Tp051ehj+q+DgYE2bNk2LFi3Sddddp+Tk5AptDwAAAJAu0EP81ltv6eabb67MWiRJ7dq106effqqVK1dWetuo2gICAnQ050CNWro5ICDA1WUAAOD2SuwhdkUYPl+fPn1c2j4AAADcA0s3A4Abs7ICX8FKdaVdda+AldX3AKAyEIgBAGXCnMoAahoCMQC4Masr8AFATWIpEIeHh5evUS8v+fj4qF69errooosUGhqq7t27KywsrFznBQAAAMrKUiA+fPiwDMMosoSzJBmGUeS5v9tvxYoVevvtt3XNNddo6tSpatKkiZWyAAAAgDKzvMJBQcg1DMPxVfD8X79Ku9+GDRs0cOBAHTt2rLyvCwAAACgVSz3EP/30k7KysjRy5Ejt2LFDpmmqcePG+sc//qGOHTuqSZMm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lZaW5tjepUsXSfkr002cOFF5eXmObW+99ZZjlorzb8ADAAAAXMFSIG7RooX69esn0zRls9n03nvvKTIy0rH9pptuUmBgoCQpPj5effr00dNPP61bb71Vc+bMcezXq1ev8lUPAAAAlJOlQCxJr7zyiq655hqZpinTNB030kn5s0+MGjVKpmlKkg4fPqwVK1YoMTHRsU+TJk109913l6N0AAAAoPwsB2IfHx99/vnneuGFF9SqVSu1aNGi0PYBAwZo5MiR8vDIb6IgHJumqUaNGumDDz5gDDEAAABczqs8B3t4eOj+++/X/fff71is43yPP/64wsPDNX/+fP3555/y8fHR1VdfrbvuuoswDAAAgCqhXIH4fPXq1Sv2+Xbt2mns2LHOagYAAABwKstDJgAAAICaoEIDcWJiojZt2lSRTQAAAADlUupAbJqmvv76a/Xv319xcXGlOuabb77R/fffr169eum///1vofmIAQAAgKqgVIE4KSlJ/fr108svv6xdu3Zpy5YtpTr5xo0bZZqmjhw5oldeeUV33323Dh48WK6CAQAAAGf620D822+/6e6779aePXscU6dt3rz5b0+clZWlXbt2SZIMw5BpmtqxY4fuvvvuQvMRAwAAAK50wUB88uRJPfHEE4WmVGvZsqVuueWWvz1xrVq1tGbNGk2cOFHt2rWTlB+MU1NT9eijj+rs2bPlLB0AAAAovwsG4jfeeEOnTp2SYRjy9vbWuHHjtGzZMj344IOlOnlAQIDuuusuLVq0SGPGjJGHh4cMw9DRo0f173//2xn1AwAAAOVSYiA+ePCgli5d6gjDH374oQYPHixPT88yN2IYhoYMGaI33njDsdTz/PnzdfLkyXIVDwAAAJRXiYF48eLFjjHDjz32mK699tpyN3brrbeqf//+kqS8vDwtWrSo3OcEAAAAyqPEQFwwk4SPj48GDx7stAaHDRsmwzAkiTmKAQAA4HIlBuLExEQZhqGwsLASl2W2okWLFmrbtq1M09SePXucdl4AAADAihIDcVpamiSpefPmTm+0bdu2kqTU1FSnnxsAAAAoixIDsc1mkyR5eXk5vdE6depIknJzc51+bgAAAKAsSgzEBcMkCnqKnalgXuOCYAwAAAC4SomBuGXLljJNU7t373Z6owXnvOiii5x+bgAAAKAsSgzEYWFhkqRDhw7pjz/+cFqD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5+Tn56dLLrlEHh4eOnz4sFavXq3Vq1drzpw5+vjjj3XRRRdVSk0jR47UunXr5O3trXbt2ik7O1vNmjWrlLbdTVV8/+bm5ur999/XJ5984ngfnG/v3r165JFHdPToUXl7e6tly5Zq1qyZjh8/ri1btmjz5s367LPP9Pbbb6tLly6VUvOsWbP03nvvSZLatm0rDw8PNWnSpFLaRsXiGnEOrpEqzgQsCgkJMUNCQsx169a5uhTLdu7caV5++eXmpZdean755ZdmTk5Ooe2//PKL2adPHzMkJMTs37+/abfbK7ymlJQUx/f2559/rvD23FVVfv8ePHjQUV96enqhbVlZWeZNN91khoSEmC+99JKZlpZWaPu+ffvMhx9+2AwJCTE7depkHj16tFJqvu2228yQkBBz+vTpldIeKh7XiHNxjVRtbjOGGCjO559/rtzcXA0ePFgDBw4s0stw7bXXaubMmfLx8dGOHTv0448/VnhNKSkpjsedO3eu8PZQvSxbtkyHDx/W5ZdfrldffbXIR76tWrXSe++9pzZt2ig9PV1z586tlLoK3re8Z+FqXCOwgkAMt7Zjxw5J0hVXXFHiPsHBwerUqZOk/JswK1peXp7jsY+PT4W3h+ql4D0bFhZW4iqctWrV0m233Sapct6zkhxzvPOehatxjcAKAjEqXa9evRQaGqoDBw7o6aef1lVXXaVrrrlGzz33XKH9Vq1apYcfflhdu3bVFVdcod69e+u1117TiRMnSjz3wYMH9fLLL6tXr14KCwtT165dNXToUK1du7bY/b288ofR//DDDxes+fXXX9eKFSs0ZMgQx3OHDh1SaGioQkNDlZGRUeSYPXv2OLaf7/7771doaKi2bNmi8ePHq1OnTurUqZMefPBBhYaG6vbbb3fsW3D8ggULHM/Z7XYtWLBAUVFR6ty5szp06KBbbrlFM2bM0NmzZ4vUMXbsWIWGhmr58uV699131bVrV1111VW68847lZ6efsHXjeKlp6fro48+0sCBA9W1a1ddfvnl6tKliwYNGqSvv/5adru9yDEHDx7Uiy++qNtuu01XXXWVOnfurLvuukuzZ8/WuXPnHPuNHTtW4eHhjr936tRJoaGhOnTokCQ5PsVYv369srKySqwxKipKS5Ys0dtvv13o+YLr73//+1+xx3Xt2lWhoaFav36947l3331XoaGh+uSTTzRv3jz17NlTHTp00G233aaePXsqNDRUp0+flpR/c1VoaKjGjh1b6LwbN27Uk08+qeuuu05hYWG64YYbNG7cOO3fv79IDQsWLFBoaKgmTJiglStXKiIiQldccYX69OmjzZs3l/iaSyM3N1cxMTG655571KVLF3Xo0EG33367PvzwwyKrmRZ8r06ePKkFCxbo9ttvV4cOHXTjjTdqzJgx2rdvX5HzF1xvU6dOLbb9ESNGKDQ0VO+++265XkdVxzVSfa+RtLQ0vf322+rbt686dOignj17auLEiTp9+rTj51dxr/2jjz5SYmKiHn/8cV1zzTW65pprdN9992nFihVF2li/fr1CQ0PVtWvXYmv43//+p9DQUPXq1atcr6Us3OqmOlQtzz77rH777TeFhITo2LFjatq0qSTJNE29/PLL+u9//ytJatSokdq1a6d9+/bpiy++0NKlS/Xxxx8X6dVds2aNRowYoczMTNWuXVvt2rVTSkqKvv/+e33//fcaPny4nnrqqULHXHvttdqxY4cWL16sjIwM3XffferatWuRoRMVcUPb1KlTtW3bNoWEhOj06dNq1KiROnXqpKysLP3++++S5OiZLpgBJScnR8OHD9f333/vqKt+/fr6448/9MEHH2jp0qX67LPPir3BZc6cOdq2bZtatWolm82mWrVq/e2NMijqyJEjGjx4sA4ePCgfHx+1bNlSTZo00cGDB7Vx40Zt3LhRv/32myZMmOA4JjExUffee6/S0tJUv359tWnTRpmZmfrtt9+0fft2xcXFKTo6Wt7e3mrVqpXCwsIc00N27NhRhmHI19dXktStWzd9+umnSkpK0j333KOHHnpI4eHhRf4tGzRooAYNGjj1ta9cuVLbtm1T06ZN1axZM2VmZioiIkK///67tm/fLpvN5rgBq1WrVo7jZs6c6QgdAQEBCgkJ0cGDBzV//nwtW7ZMb7/9drHzwP/666/66quv1KBBA7Vq1crxS6hVaWlpevzxx7VlyxZJUps2beTp6anExERNnz5da9eu1ccff1zk+v/ggw8UExOjunXrql27dkpKStLChQu1atUqffjhh3wE/hdcI9X3Gjl+/LgefPBB/fnnn/L29nb8fIqOjtaaNWuKnc2jwB9//KEPP/xQGRkZCgkJUXp6ujZv3qzNmzfrgQce0Lhx4yzXVSlcPYgZ1ZfVGy4KbnYICwszt2zZYpqmaebk5Jhnz541TdM0P/30UzMkJMTs3r27+csvvziOy8jIMF999VUzJCTEvOGGGxz7m2b+DRadOnUyQ0JCzBkzZpjZ2dmObatWrXJsi4uLK1RLSkqK2bt3b8drCQkJMa+66irzoYceMmfNmmVu3769xBvpLnRTh2ma5u7dux3bzzdo0CDH8ytXrjRN0zTz8vLM1NTUCx5nmqY5ceJEMyQkxLz55pvNHTt2FHodTz31lOPmv7y8PMe2MWPGOM732WefOZ5PTk4u9nW5C6vv34Lvc1RUVKHvYXZ2tjllyhQzJCTEDA0NNU+cOOHYNnz4cDMkJMR87bXXCt24uWPHDrNr165mSEiI+e233zqev9B7y263m08++WSh92z79u3NAQMGmG+88Ya5Zs2aQu//vyq4/lavXl3s9i5duhT5vrzzzjuOtiZNmuS4Js5//cUdZ5qmGRsb67h56bvvvnM8n5OTY77//vuObYcPH3Zsmz9/vqO94cOHO75n5X3PFlwLffr0MXfv3u14fteuXeb1119vhoSEmDNnznQ8X/C9CgkJMZ977jkzMzPTNE3TTE9PN59++mkzJCTE7Nmzp+P589uYMmVKsTUUvBfeeeedcr2WysA14n7XyGOPPWaGhISYAwYMMI8cOVKoxg4dOhT7njj/td90002Frq0lS5aYl19+uRkSEmLGx8c7nl+3bp0ZEhJidunSpdg6Vq9e7ThfZWHIBMqt4OOfkr6KWx1Qkvr06aOOHTtKyv+Iy9/fX9nZ2Zo1a5Yk6c033yw0HZ6fn59eeeUVXXnllTp69Kjmz5/v2PbJJ58oPT1d/fr107/+9a9CY7TCw8M1evRoSXJMeVMgICBAX375pXr37u14LjMzUz/99JOmT5+uAQMGKDw8XDExMcV+xFceHTt2VEREhCTJw8Pjb3sqjh8/rv/85z/y9vbWu+++q/bt2xd6Hf/+97/VtGlT7dixQ6tXry5yfOPGjTV48GDH3wMDA53zQqq5srx/s7Oz9euvv8owDI0fP77Q99DHx0fPPPOMfHx8ZJqm/vzzT8e2PXv2SMpfIOj8Hpb27dtr+PDhioyMdPRu/R3DMDR9+nQ99thjjnPZbDZt375ds2fP1sMPP6zrrrtOU6ZMcfqQGG9vb/3rX/+SYRiSSvceKuj1GjdunG655ZZC5xo2bJhuvvlmpaena86cOcUe//TTTzteZ3nes8ePH9fChQtlGIbee+89hYSEOLaFhobqxRdflCQtWrSoyLFhYWGaPHmyateuLUmqU6eOpkyZolatWunYsWNavHix5bqqA66R0qvO18jvv/+u77//Xn5+fnr//fcLTQnXp08fPfvssxc83jAMvf/++4Wurdtuu02PPfaYJOmjjz6yXFtlIBCj3EJCQhzjYIv7KinoXXXVVUWe27Jli06fPq2GDRuqW7duxR5X8B/G+TM+FIz1uvXWW4s95tZbb5VhGNq5c2eRMcgNGzbU+++/r+XLl2vkyJHq3Llzof+QDx8+rAkTJuihhx5STk5Oid+Hsiru9V/Ijz/+qNzcXLVv377YBTp8fX0dwb642TCuvPLKEm8wcWdlef/6+vrqxx9/1LZt24r9N8jOzlb9+vUlqdDYxZYtW0qSXn31VW3YsMFxc42UP47xnXfe0c0331zqmn18fDR69Gj9+OOPGj9+vHr16lXo4+CzZ8/qs88+06233uoYV+kMISEhqlOnTqn3P3DggPbu3SsPD49CP+jPV3BjU3Hv2QYNGqh169bWiv2LH374QaZp6sorr1S7du2KbA8PD9eiRYu0cOHCItuioqKKXDs+Pj76xz/+IUkljjWtKbhGSq86XyMFHSk33nhjsXPuDxgw4II3BF5zzTW67LLLijx/9913S8of3nH+LEpVDWOIUW4vvvhiiQPjL6RRo0ZFntu7d6+k/F7ae++9t9jj0tLSJMlxQ0t6erqOHj0qSXrrrbf0wQcfFHucp6enbDabkpKSir3Y27RpoyeeeEJPPPGEzp07p02bNumHH37QwoULdfbsWa1du1YzZswocvOfVcW9/gtJTEyUJO3fv7/E783Jkyclqdibfcranruw8v6tVauWDh06pK1bt2r//v06dOiQ/vjjD+3evVu5ubmSVOgThSeffFLr1q3Ttm3bdP/996tu3brq1q2bevbsqV69eqlhw4aWag8MDNTAgQM1cOBA5eXlOaYGXLhwoQ4ePKhjx45p5MiR+uabbyyd/6/K+h4quJ49PDz00EMPFbtPQSjav3+/TNN09KxZae9CDhw4IEnFhmEpvzfu0ksvLXZbWFhYsc8X9IQVnLum4hopvep8jRT02Jc0BrlWrVpq3bq1du/eXez2kq6Tiy++WHXr1tXZs2d16NChKvvpJIEYLlPcx18FH19lZmY6bnwpScG+58/wUHAz2oUUNxPDX9WuXVs9evRQjx499OSTT+rJJ5/U5s2b9d///tdpgbi0H/8VKHi9p0+fLvX3pjztoXhHjhzRpEmTFB8fL9M0Hc83atRIkZGRWrNmjeOXtgJXXnmlvv32W33wwQdavXq1zp49q7i4OMXFxenVV1/VLbfcoldeeUV169a1XJenp6c6dOigDh066PHHH9eUKVP0xRdf6LffftPOnTuL7bkpK6vvWZvN9rfvWbvdroyMjEK9eM6cnqrgDn8/P78yH1vQo/lXBT2Bpfk/xZ1wjZReVbxGCoYGFedCvd8XWga7Tp06Onv2bJW+VgjEqFIKLsSbbrrJMZa4tMdI0rp16xQQEFCq45YuXaqZM2eqbdu2euedd0rcLyAgQC+99JL69euns2fPKiUlpchvuOf/p1/gQtP9WFHwOgcNGqSXXnrJqedG6Zw7d04PPvig9u/fr+bNm+vee+9VWFiYgoODHT013bt3L/bY4OBg/fvf/1ZOTo62bdumtWvX6ocfftCOHTu0ZMkSnTt3Tu+///4F29+3b59Gjhyps2fPauXKlY5pA//Ky8tLY8aM0dKlS5WamqqkpKQiP+yLe89Kzn3fFoTPkJAQLVmyxGnntaJWrVqSVGj6rtIq6ZiCMFPc/zklfX+ttF+dcI2UTVW6Rgp+xhQ3jWiBC2270PflQtdKcVxxnTCgEFVKwTQ0BcMDinPo0CFt27ZNycnJkvJ/Ky0IqCUdl5eXp19++UX79+93LHzh7e2txMRE/fTTT397Y0XBtGeenp6O387P/4+2uLHFF5ov2YrSfG8SExP122+/Fel9gXOsWrVK+/fvV4MGDfTNN9/okUceUbdu3Rw/6LOzsx29LAXsdrsOHjyoDRs2SMrv0enSpYv+9a9/acGCBZo0aZLj3Bf6YSPljxfctWuXDh8+/Le9Sd7e3o6ezfPHeHp6ekoq/j175swZp/6wv+SSSyTlzy9b0vj7U6dOadOmTTp+/LjT2i1OwTjLgo+o/8pms2ngwIEaMWKETp06VWhbScfs2rVLktS2bVvHcxf6/kr/N6yppuIaKZuqdI0UvI8LbnD8q5ycnGLnRC5Q0nVy6NAhpaeny9vb2zFWvCpeJwRiVCmdO3eWn5+fDhw4oF9++aXYfV544QXdc889mjJliuO5gvkZ//Of/xR7zJIlSzRkyBD169dPmZmZkqTrr79e9erVU0ZGxt9Okl8wsXiXLl0cH1Gd//FQcWN2i5vpoTx69uwpDw8PbdiwodDd2QVsNpuGDRumAQMG6LPPPnNq28h3+PBhSVLTpk2L7elYtGiRY3xkwS9eJ0+eVEREhB544IFif6Bdd911jscFYyrPv4Hr/F6qgIAAx8wr06dPv+BNnjt37tT+/ftVt27dQnN2F7xvK+M927ZtWzVr1kznzp0rdvYGSZo2bZqioqL09NNPO7Xtv7r++utlGIa2bdumpKSkItvXrl2rrVu3av369UX+bYu70S4nJ8fxms6fpeZC399Dhw6VOP6ypuAaKZuqdI0ULHbyww8/ODqczrd06dIL/jLw888/Fxtkv/76a0n58/4XdCgV/CKSmZmpY8eOFTnG2d/n0iAQo0rx9/fXgw8+KEl65plnCoXirKwsvf7661q3bp08PT31wAMPOLY98sgj8vX11ZIlS/TWW28VWnHqp59+ckwAf9dddznGoPn7+2vkyJGS8hetGD16dJHe1/T0dM2ePVtvvPGGvL29NWLECMc2Pz8/x80Hb731lmNslM1m09y5c/Xtt9866buSr0WLFrr99tuVl5enxx9/3LE8qZTfa/Hss88qKSlJfn5+Jd50h/Ip6KXfvXt3of+wc3Nz9fXXX+v11193PFfwHmzcuLG6dOkiu92uZ555ptAP/IyMDE2fPl1S/jR8Be/N88e5HjlypFANo0ePVq1atbR161bde++9Wr9+faGbk2w2m1auXKlHH31UpmnqiSeeKDTmsGCqw+jo6ELv919++UWTJ0+29o0pgWEYGjZsmKT81R6/++67QnXOnj3bsQpjSTcUOUurVq3Ut29f2e12DR8+vFBP165du/TKK69Iyl9JsqD3qkBcXJxmzpzpCHDp6el65plndPDgQV166aWKjIx07FuwmM66desUGxvreP7AgQMaMWKE06dvrGq4RsqmKl0jHTp0UPfu3ZWZmamnnnqq0L/Dzz//XOjfrjjnzp3T8OHDC306+u233+qTTz6Rh4dHoYWx2rRp4+iVnzJliuO9kJ2drWnTpmndunVOfGWlwxhiVDlPPvmk/vzzT8dSyc2aNVODBg20f/9+x9CG8ePHF7qjtW3btpo6daqee+45zZo1S1988YVat26t1NRUR4/Fddddp2eeeaZQW1FRUTp37pzefvttLV26VEuXLtXFF1+sRo0aKSsrS0lJScrNzVXdunX12muvOX7YFRgxYoSGDx+ujRs36oYbblCrVq109OhRpaSk6IEHHtCCBQucehPByy+/rCNHjmjjxo2688471apVK/n5+Wnfvn06d+6cvL299c4776hx48ZOaxP/p3fv3rr88su1Y8cOPfHEE2rRooXq1aungwcP6syZM2rQoIFatmyp3bt3F/qhMGnSJA0YMEAbNmxQeHi4WrZsKW9vbx04cECZmZlq0KCBJk6c6Ni/QYMGuvjii3Xs2DENGjRILVq00JQpU9SuXTtdccUVev/99zV27FglJCRo8ODBatCggZo2bSrTNHXw4EGlp6fLw8NDjz32mB5++OFCr+HBBx/UkiVLdOrUKd1xxx1q27at0tPTdejQIXXs2FF16tTRTz/95LTv2YABA/THH39ozpw5GjVqlCZPnqzGjRvr0KFDjo/On3zyyUK9rBVl/PjxOnjwoBISEtS3b1+1bdtWubm52r9/v+x2u3r06OGYM/V87dq109tvv63o6Gg1bdpUiYmJyszMVJMmTTR9+vRCAbpXr17q0KGDtm/frhEjRqhVq1by8fFRYmKi6tevr/vuu0/z5s2r8NfqKlwjZVeVrpHXX39d9957r7Zs2aLw8HDHinP79+9XaGio/vzzT+Xm5hb5pVHKnzpvx44d6tWrl0JCQpSamqojR47Iw8ND48aN05VXXunY19PTU0899ZRee+01LV++XGvXrlWzZs104MABnT17VsOHD6/05c3pIUaV4+XlpRkzZuitt97S9ddfr4yMDO3evVu+vr6KiIhQTEyM7rrrriLH3XzzzVq4cKEGDBigBg0aaPfu3UpNTdUVV1yhcePG6aOPPir2jtxHHnlE3333nZ544gldeeWVysvL065du3TixAm1a9dOTz75pL777jv17du3yLG9e/fW559/rh49esjDw0N//vmnmjdvrjfeeKNClqn09/fXZ599pgkTJujqq69WcnKy9uzZo3r16un222/XN998ox49eji9XeTz8vLSF198oSeffFLt2rXTqVOn9Oeff6pRo0Z66KGHtGTJEt13332SCn/k16JFC82fP18DBw5U06ZNdfDgQSUlJalx48Z68MEHtXTp0iLTgb3zzju64oorlJWVpYMHDxaa2qt79+5asWKFnn/+eXXv3l21a9fWn3/+qf3796thw4a699579c033zgWpDlf8+bN9c0336h///4KCAhQYmKivLy8NHz4cM2dO7dCZiN5/vnn9cknn6hXr16y2+2Osbfdu3fXzJkzC33yUpHq16+vefPmacyYMbrssst08OBBHTlyRJdddplefvllffjhh8X+HzFq1Ci99NJLql+/vnbv3q2GDRvq4Ycf1vz584vMtevp6ak5c+bo8ccf1yWXXKLDhw8rNTVV/fv318KFCx1jRmsqrhFrqso10rhxYy1YsEAPPPCALrroIu3Zs0fZ2dl68MEHFRMT49iv4CbV81122WWaN2+eunbtqn379ikrK0s33XSTvvjiC91///1F9r///vv13nvvqXPnzsrJyXHc2PjRRx8VWkSqshhmSbdRAgDgxnr16qXDhw9r1qxZuummm1xdDuBSWVlZjl7eH3/80fFJ5Lvvvqv33ntPkZGRF5yxqaqjhxgAAMDN/fDDD4qIiNCrr75a7PY1a9ZIyl/spCYOyyMQAwAAuLn27dvr8OHD+uqrrwrd3CdJ27dv1/jx4yVJAwcOdEV5FY6b6gAA1cLvv/9e6Maq0mrfvj2L2cAtlPcaeeqpp/T2228Xurnv/JvTe/bsqSeeeMLZZVcJBGIAQLVw9uzZv11soTglrVYG1DTlvUaGDRuma665Rp9//rl27dqlPXv2qG7duurSpYv69++vfv36FZoDuibhpjoAAAC4tZoZ8wEAAIBSIhADAADArRGIAQAA4NYIxAAAAHBrBGIAAAC4tf8HLoklEXAU1/AAAAAASUVORK5CYII=\n" + }, + "metadata": {} + } + ], + "source": [ + "plot_df = footprint_df_recon_all.append(footprint_df_fastsurfer_cpu).append(footprint_df_fastsurfer_gpu).copy()\n", + "\n", + "palette = sns.color_palette(\"husl\",3)\n", + "\n", + "sns.set(font_scale = 2)\n", + "with sns.axes_style(\"whitegrid\"):\n", + " # fig, ax = plt.subplots(figsize=(10,10),sharex=False,sharey=False)\n", + " \n", + " g = sns.catplot(y='kg_carbon', x='exp_set', hue='PUE', data=plot_df, kind='box', palette=palette, height=10, legend=False)\n", + " g.set_xticklabels(rotation=0,fontsize=24)\n", + " g.ax.set_ylabel(\"Carbon Emission (kg)\", fontsize = 36)\n", + " g.set(xlabel = \"\", yscale='log', ylim=[1e-4, 1e-2]) \n", + " g.ax.legend(loc='upper right', fontsize=20, title='PUE')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ] +} \ No newline at end of file