diff --git a/baler/baler.py b/baler/baler.py index e37c275d..d90c514f 100644 --- a/baler/baler.py +++ b/baler/baler.py @@ -65,6 +65,7 @@ def main(): elif mode == "train": check_enabled_profilers( perform_training, + output_path, pytorch_profile, energy_profile, output_path, @@ -92,13 +93,14 @@ def main(): def check_enabled_profilers( - f, pytorchProfile=False, energyProfile=False, *args, **kwargs + f, output_path="/", pytorchProfile=False, energyProfile=False, *args, **kwargs ): """ Conditionally apply profiling based on the given boolean flags. Args: f (callable): The function to be potentially profiled. + output_path (str): The path where the profiling logs and reports are to be saved. pytorchProfile (bool): Whether to apply PyTorch profiling. energyProfile (bool): Whether to apply energy profiling. @@ -108,10 +110,10 @@ def check_enabled_profilers( if pytorchProfile and not energyProfile: return pytorch_profile(f, *args, **kwargs) elif energyProfile and not pytorchProfile: - return energy_profiling(f, "baler_training", 1, *args, **kwargs) + return energy_profiling(f, output_path, "baler_training", 1, *args, **kwargs) elif pytorchProfile and energyProfile: return pytorch_profile( - energy_profiling, f, "baler_training", 1, *args, **kwargs + energy_profiling, f, output_path, "baler_training", 1, *args, **kwargs ) else: return f(*args, **kwargs) diff --git a/baler/modules/profile_plotting.py b/baler/modules/profile_plotting.py new file mode 100644 index 00000000..97e5124e --- /dev/null +++ b/baler/modules/profile_plotting.py @@ -0,0 +1,252 @@ +import pandas as pd +import matplotlib.pyplot as plt +from matplotlib.dates import date2num +import seaborn as sns +import os + + +def plot(profiling_path, func): + """ + Visualizes the data that is generated from .CSV logs + of codecarbon by plotting graphs .The codecarbon package + is utilized for tracking amount of electricity that is + consumed by the given function and the amount of CO2 emission. + + Args: + profiling_path (str): The path where the .CSV logs are generated + func (callable): The function to be profiled. + + Returns: + Void. The plots are stored in the `profiling_path` location + """ + + # Load CSV data into a DataFrame + emission_csv_path = os.path.join(profiling_path, "emissions.csv") + data = pd.read_csv(emission_csv_path) + + # Define the scaling factor (adjust this value according to your needs) + scaling_factor = 10**6 + + # List of columns to scale up (you can modify this as per your requirement) + columns_to_scale = [ + "duration", + "emissions", + "emissions_rate", + "cpu_power", + "gpu_power", + "ram_power", + "cpu_energy", + "gpu_energy", + "ram_energy", + "energy_consumed", + ] + + # Scale up the values in the selected columns + data[columns_to_scale] *= scaling_factor + + # Convert the 'timestamp' column to datetime + data["timestamp"] = pd.to_datetime(data["timestamp"], errors="coerce") + + # Plot 1: Time series graph for 'timestamp' vs 'duration' + plt.figure(figsize=(10, 6)) + plt.plot(data["timestamp"], data["duration"], marker="o") + plt.xlabel("Timestamp") + plt.ylabel("Duration (in seconds) x 1e-6") + plt.title("Time Series " + f"{func.__name__}" + " : Duration vs Timestamp") + plt.xticks(rotation=45) + plt.grid(True) + plt.tight_layout() + plt.savefig(os.path.join(profiling_path, f"{func.__name__}" + "_" + "plot1.png")) + plt.close() + # plt.show() + + # Plot 2 and 3: Time series graph for 'timestamp' vs 'emissions' and 'emissions_rate' + fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 10), sharex=True) + + ax1.plot(data["timestamp"], data["emissions"], marker="o") + ax1.set_ylabel("Emissions in CO₂eq (in kg) x 1e-6") + ax1.set_title("Time Series " + f"{func.__name__}" + " : Emissions vs Timestamp") + ax1.grid(True) + + ax2.plot(data["timestamp"], data["emissions_rate"], marker="o") + ax2.set_xlabel("Timestamp") + ax2.set_ylabel("Emissions Rate in CO₂eq in (kg/second) x 1e-6") + ax2.set_title( + "Time Series " + f"{func.__name__}" + " : Emissions Rate vs Timestamp" + ) + ax2.grid(True) + + plt.xticks(rotation=45) + plt.tight_layout() + plt.savefig(os.path.join(profiling_path, f"{func.__name__}" + "_" + "plot2.png")) + plt.close() + # plt.show() + + # Plot 4, 5, 6, and 7: Time series graph for 'timestamp' vs 'ram_power', 'cpu_energy', 'ram_energy', and 'energy_consumed' + fig, axs = plt.subplots(2, 2, figsize=(12, 10), sharex=True) + + axs[0, 0].plot(data["timestamp"], data["ram_power"], marker="o") + axs[0, 0].set_ylabel("RAM Power (in watt)") + axs[0, 0].set_title( + "Time Series " + f"{func.__name__}" + " : RAM Power vs Timestamp" + ) + axs[0, 0].grid(True) + + axs[0, 1].plot(data["timestamp"], data["cpu_energy"], marker="o") + axs[0, 1].set_ylabel("CPU Energy in (kilo-watt) x 1e-6") + axs[0, 1].set_title( + "Time Series " + f"{func.__name__}" + " : CPU Energy vs Timestamp" + ) + axs[0, 1].grid(True) + + axs[1, 0].plot(data["timestamp"], data["ram_energy"], marker="o") + axs[1, 0].set_xlabel("Timestamp") + axs[1, 0].set_ylabel("RAM Energy (in kilo-watt) x 1e-6") + axs[1, 0].set_title( + "Time Series " + f"{func.__name__}" + " : RAM Energy vs Timestamp" + ) + axs[1, 0].grid(True) + + axs[1, 1].plot(data["timestamp"], data["energy_consumed"], marker="o") + axs[1, 1].set_xlabel("Timestamp") + axs[1, 1].set_ylabel("Energy Consumed (in kilo-watt) x 1e-6") + axs[1, 1].set_title( + "Time Series " + f"{func.__name__}" + " : Energy Consumed vs Timestamp" + ) + axs[1, 1].grid(True) + + plt.xticks(rotation=45) + plt.tight_layout() + plt.savefig(os.path.join(profiling_path, f"{func.__name__}" + "_" + "plot3.png")) + plt.close() + # plt.show() + + # Measure central tendencies of the data for each metric + emissions_central = data["emissions"].describe() + emissions_rate_central = data["emissions_rate"].describe() + ram_power_central = data["ram_power"].describe() + cpu_energy_central = data["cpu_energy"].describe() + ram_energy_central = data["ram_energy"].describe() + energy_consumed_central = data["energy_consumed"].describe() + + # Plot histograms for each metric with central tendencies + plt.figure(figsize=(15, 10)) + + # Emissions + plt.subplot(2, 3, 1) + sns.histplot(data["emissions"], bins=20, kde=True, color="skyblue") + plt.axvline(emissions_central["mean"], color="red", linestyle="--", label="Mean") + plt.axvline( + emissions_central["50%"], color="orange", linestyle="--", label="Median (50%)" + ) + plt.axvline( + emissions_central["25%"], color="green", linestyle="--", label="Q1 (25%)" + ) + plt.axvline( + emissions_central["75%"], color="blue", linestyle="--", label="Q3 (75%)" + ) + plt.xlabel("Emissions") + plt.title("Histogram " + f"{func.__name__}" + " : Emissions") + plt.legend() + + # Emissions Rate + plt.subplot(2, 3, 2) + sns.histplot(data["emissions_rate"], bins=20, kde=True, color="salmon") + plt.axvline( + emissions_rate_central["mean"], color="red", linestyle="--", label="Mean" + ) + plt.axvline( + emissions_rate_central["50%"], + color="orange", + linestyle="--", + label="Median (50%)", + ) + plt.axvline( + emissions_rate_central["25%"], color="green", linestyle="--", label="Q1 (25%)" + ) + plt.axvline( + emissions_rate_central["75%"], color="blue", linestyle="--", label="Q3 (75%)" + ) + plt.xlabel("Emissions Rate") + plt.title("Histogram " + f"{func.__name__}" + " Emissions Rate") + plt.legend() + + # RAM Power + plt.subplot(2, 3, 3) + sns.histplot(data["ram_power"], bins=20, kde=True, color="lightgreen") + plt.axvline(ram_power_central["mean"], color="red", linestyle="--", label="Mean") + plt.axvline( + ram_power_central["50%"], color="orange", linestyle="--", label="Median (50%)" + ) + plt.axvline( + ram_power_central["25%"], color="green", linestyle="--", label="Q1 (25%)" + ) + plt.axvline( + ram_power_central["75%"], color="blue", linestyle="--", label="Q3 (75%)" + ) + plt.xlabel("RAM Power") + plt.title("Histogram " + f"{func.__name__}" + " : RAM Power") + plt.legend() + + # CPU Energy + plt.subplot(2, 3, 4) + sns.histplot(data["cpu_energy"], bins=20, kde=True, color="lightcoral") + plt.axvline(cpu_energy_central["mean"], color="red", linestyle="--", label="Mean") + plt.axvline( + cpu_energy_central["50%"], color="orange", linestyle="--", label="Median (50%)" + ) + plt.axvline( + cpu_energy_central["25%"], color="green", linestyle="--", label="Q1 (25%)" + ) + plt.axvline( + cpu_energy_central["75%"], color="blue", linestyle="--", label="Q3 (75%)" + ) + plt.xlabel("CPU Energy") + plt.title("Histogram " + f"{func.__name__}" + " : CPU Energy") + plt.legend() + + # RAM Energy + plt.subplot(2, 3, 5) + sns.histplot(data["ram_energy"], bins=20, kde=True, color="lightblue") + plt.axvline(ram_energy_central["mean"], color="red", linestyle="--", label="Mean") + plt.axvline( + ram_energy_central["50%"], color="orange", linestyle="--", label="Median (50%)" + ) + plt.axvline( + ram_energy_central["25%"], color="green", linestyle="--", label="Q1 (25%)" + ) + plt.axvline( + ram_energy_central["75%"], color="blue", linestyle="--", label="Q3 (75%)" + ) + plt.xlabel("RAM Energy") + plt.title("Histogram " + f"{func.__name__}" + " : RAM Energy") + plt.legend() + + # Energy Consumed + plt.subplot(2, 3, 6) + sns.histplot(data["energy_consumed"], bins=20, kde=True, color="lightyellow") + plt.axvline( + energy_consumed_central["mean"], color="red", linestyle="--", label="Mean" + ) + plt.axvline( + energy_consumed_central["50%"], + color="orange", + linestyle="--", + label="Median (50%)", + ) + plt.axvline( + energy_consumed_central["25%"], color="green", linestyle="--", label="Q1 (25%)" + ) + plt.axvline( + energy_consumed_central["75%"], color="blue", linestyle="--", label="Q3 (75%)" + ) + plt.xlabel("Energy Consumed") + plt.title("Histogram " + f"{func.__name__}" + " : Energy Consumed") + plt.legend() + + plt.tight_layout() + plt.savefig(os.path.join(profiling_path, f"{func.__name__}" + "_" + "plot4.png")) + plt.close() + # plt.show() + + print(f"Your codecarbon profiling plots are available at {profiling_path}") diff --git a/baler/modules/profiling.py b/baler/modules/profiling.py index 4613537e..bcbbfe2a 100644 --- a/baler/modules/profiling.py +++ b/baler/modules/profiling.py @@ -1,10 +1,12 @@ import io +import os import pstats import cProfile from pstats import SortKey import torch from torch.profiler import profile, record_function, ProfilerActivity import codecarbon +from ..modules import profile_plotting def pytorch_profile(f, *args, **kwargs): @@ -54,7 +56,7 @@ def pytorch_profile(f, *args, **kwargs): return result -def energy_profiling(f, project_name, measure_power_secs, *args, **kwargs): +def energy_profiling(f, output_path, project_name, measure_power_secs, *args, **kwargs): """ Energy Profiling measures the amount of electricity that was consumed by the given function f and the amount of CO2 emission. @@ -62,29 +64,62 @@ def energy_profiling(f, project_name, measure_power_secs, *args, **kwargs): Args: f (callable): The function to be profiled. + output_path (str): The path where the profiling logs and reports are to be saved. project_name (str): The name of the project. measure_power_secs (int): The number of seconds to measure power. Returns: result: The result of the function `f` execution. + profile_plotting.plot(profiling_path, f): Subsequently called to generate plots from the codecarbon log files. """ + profiling_path = os.path.join(output_path, "profiling") tracker = codecarbon.EmissionsTracker( - project_name=project_name, measure_power_secs=measure_power_secs + project_name=project_name, + measure_power_secs=measure_power_secs, + save_to_file=True, + output_dir=profiling_path, + co2_signal_api_token="script-overwrite", + experiment_id="235b1da5-aaaa-aaaa-aaaa-893681599d2c", + log_level="DEBUG", + tracking_mode="process", ) tracker.start_task(f"{f.__name__}") # Execute the function and get its result result = f(*args, **kwargs) - emissions = tracker.stop_task() - print("CO2 emission [kg]: ", emissions.emissions) - print("CO2 emission rate [kg/h]: ", 3600 * emissions.emissions_rate) - print("CPU energy consumed [kWh]: ", emissions.cpu_energy) - print("GPU energy consumed [kWh]: ", emissions.gpu_energy) - print("RAM energy consumed [kWh]: ", emissions.ram_energy) + tracker.stop_task() + emissions = tracker.stop() - return result + print( + "----------------------------------Energy Profile-----------------------------------------------" + ) + print( + "-----------------------------------------------------------------------------------------------" + ) + print(f"Emissions : {1000 * emissions} g CO₂") + for task_name, task in tracker._tasks.items(): + print( + f"Emissions : {1000 * task.emissions_data.emissions} g CO₂ for task {task_name} \nEmission Rate : {3600*task.emissions_data.emissions_rate} Kg/h" + ) + print( + "-----------------------------------------------------------------------------------------------" + ) + print("Energy Consumption") + print( + f"CPU : {1000 * task.emissions_data.cpu_energy} Wh \nGPU : {1000 * task.emissions_data.gpu_energy} Wh \nRAM : {1000 * task.emissions_data.ram_energy} Wh" + ) + print( + "-----------------------------------------------------------------------------------------------" + ) + print("Power Consumption") + print( + f"CPU : { task.emissions_data.cpu_power} W \nGPU : { task.emissions_data.gpu_power} W \nRAM : { task.emissions_data.ram_power} W" + + f"\nduring {task.emissions_data.duration} seconds." + ) + + return result, profile_plotting.plot(profiling_path, f) def c_profile(func, *args, **kwargs): diff --git a/poetry.lock b/poetry.lock index 0a557f31..029ac158 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "absl-py" @@ -1088,6 +1088,16 @@ files = [ {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, @@ -2119,6 +2129,11 @@ files = [ {file = "scikit_learn-1.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f66eddfda9d45dd6cadcd706b65669ce1df84b8549875691b1f403730bdef217"}, {file = "scikit_learn-1.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6448c37741145b241eeac617028ba6ec2119e1339b1385c9720dae31367f2be"}, {file = "scikit_learn-1.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:c413c2c850241998168bbb3bd1bb59ff03b1195a53864f0b80ab092071af6028"}, + {file = "scikit_learn-1.3.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ef540e09873e31569bc8b02c8a9f745ee04d8e1263255a15c9969f6f5caa627f"}, + {file = "scikit_learn-1.3.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:9147a3a4df4d401e618713880be023e36109c85d8569b3bf5377e6cd3fecdeac"}, + {file = "scikit_learn-1.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2cd3634695ad192bf71645702b3df498bd1e246fc2d529effdb45a06ab028b4"}, + {file = "scikit_learn-1.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c275a06c5190c5ce00af0acbb61c06374087949f643ef32d355ece12c4db043"}, + {file = "scikit_learn-1.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:0e1aa8f206d0de814b81b41d60c1ce31f7f2c7354597af38fae46d9c47c45122"}, {file = "scikit_learn-1.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:52b77cc08bd555969ec5150788ed50276f5ef83abb72e6f469c5b91a0009bbca"}, {file = "scikit_learn-1.3.1-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:a683394bc3f80b7c312c27f9b14ebea7766b1f0a34faf1a2e9158d80e860ec26"}, {file = "scikit_learn-1.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a15d964d9eb181c79c190d3dbc2fff7338786bf017e9039571418a1d53dab236"}, @@ -2181,6 +2196,27 @@ dev = ["click", "doit (>=0.36.0)", "flake8", "mypy", "pycodestyle", "pydevtool", doc = ["matplotlib (>2)", "numpydoc", "pydata-sphinx-theme (==0.9.0)", "sphinx (!=4.1.0)", "sphinx-design (>=0.2.0)"] test = ["asv", "gmpy2", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] +[[package]] +name = "seaborn" +version = "0.13.0" +description = "Statistical data visualization" +optional = false +python-versions = ">=3.8" +files = [ + {file = "seaborn-0.13.0-py3-none-any.whl", hash = "sha256:70d740828c48de0f402bb17234e475eda687e3c65f4383ea25d0cc4728f7772e"}, + {file = "seaborn-0.13.0.tar.gz", hash = "sha256:0e76abd2ec291c655b516703c6a022f0fd5afed26c8e714e8baef48150f73598"}, +] + +[package.dependencies] +matplotlib = ">=3.3,<3.6.1 || >3.6.1" +numpy = ">=1.20,<1.24.0 || >1.24.0" +pandas = ">=1.2" + +[package.extras] +dev = ["flake8", "flit", "mypy", "pandas-stubs", "pre-commit", "pytest", "pytest-cov", "pytest-xdist"] +docs = ["ipykernel", "nbconvert", "numpydoc", "pydata_sphinx_theme (==0.10.0rc2)", "pyyaml", "sphinx (<6.0.0)", "sphinx-copybutton", "sphinx-design", "sphinx-issues"] +stats = ["scipy (>=1.7)", "statsmodels (>=0.12)"] + [[package]] name = "setuptools" version = "68.2.2" @@ -2729,4 +2765,4 @@ hls4ml = ["hls4ml", "tensorflow"] [metadata] lock-version = "2.0" python-versions = ">=3.8, <3.11" -content-hash = "3a093254d6705133fc660cfebfdc53b22d34e07ec514003bd2d89cd3df23abdf" +content-hash = "38aca329ba09c34ebe82ac5aeffd91a92fd2103d047371b2d88d517a12d180bd" diff --git a/pyproject.toml b/pyproject.toml index f70d7c18..dc658694 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,6 +18,7 @@ hls4ml = { version = "^0.7.1", optional = true } tensorflow = { version = "^2.12.0", optional = true } numpy = "1.23.5" codecarbon = "2.3.1" +seaborn = "^0.13.0" [tool.poetry.group.dev.dependencies] pytest = "^7.2.1" diff --git a/workspaces/CFD_workspace/CFD_project_animation/output/profiling/.gitkeep b/workspaces/CFD_workspace/CFD_project_animation/output/profiling/.gitkeep new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/workspaces/CFD_workspace/CFD_project_animation/output/profiling/.gitkeep @@ -0,0 +1 @@ +