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 diff --git a/examples/my_experiment.py b/examples/my_experiment.py index b2d7095..93be6d2 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,13 +48,16 @@ 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): @@ -67,4 +71,8 @@ def my_experiment() -> None: if __name__ == "__main__": - my_experiment() + + # 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 65e695c..aa56206 100644 --- a/experiment_impact_tracker/compute_tracker.py +++ b/experiment_impact_tracker/compute_tracker.py @@ -22,13 +22,15 @@ 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 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, @@ -73,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. @@ -90,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 = {} @@ -135,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 @@ -158,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!") @@ -175,6 +178,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 +207,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, @@ -216,7 +220,10 @@ def gather_initial_info(log_dir: str): 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"] @@ -239,11 +246,12 @@ 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.region_coords = region_coords 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 @@ -292,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/data_info_and_router.py b/experiment_impact_tracker/data_info_and_router.py index 71e71fc..3594e98 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,170 @@ 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], + + #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], + + #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/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 diff --git a/experiment_impact_tracker/emissions/get_region_metrics.py b/experiment_impact_tracker/emissions/get_region_metrics.py index 573a6a9..5107503 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 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 + ''' + 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]