Skip to content

Commit

Permalink
Merge pull request #119 from fvaleye/feature/improve-management-optio…
Browse files Browse the repository at this point in the history
…nal-dependencies

Improve the management of the optional dependencies
  • Loading branch information
fvaleye authored Jan 24, 2023
2 parents f67ccc4 + 768d4de commit 929f829
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 136 deletions.
31 changes: 23 additions & 8 deletions tracarbon/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,11 @@
from loguru import logger

from tracarbon.builder import TracarbonBuilder
from tracarbon.conf import KUBERNETES_INSTALLED
from tracarbon.exporters import Exporter, MetricGenerator
from tracarbon.general_metrics import (
CarbonEmissionGenerator,
CarbonEmissionKubernetesGenerator,
EnergyConsumptionGenerator,
EnergyConsumptionKubernetesGenerator,
)
from tracarbon.locations import Country

Expand Down Expand Up @@ -55,6 +54,27 @@ def get_exporter(
return selected_exporter(metric_generators=metric_generators, metric_prefix_name=tracarbon_builder.configuration.metric_prefix_name) # type: ignore


def add_containers_generator(location: Country) -> List[MetricGenerator]:
"""
Add metric generators for containers if available
:param: country for the metric generators of containers
:return: the list of metric generators for containers
"""
if KUBERNETES_INSTALLED:
from tracarbon.general_metrics import (
CarbonEmissionKubernetesGenerator,
EnergyConsumptionKubernetesGenerator,
)

return [
EnergyConsumptionKubernetesGenerator(location=location),
CarbonEmissionKubernetesGenerator(location=location),
]
else:
raise ImportError("kubernetes optional dependency is not installed")


def run_metrics(
exporter_name: str,
country_code_alpha_iso_2: Optional[str] = None,
Expand Down Expand Up @@ -82,12 +102,7 @@ def run_metrics(
),
]
if containers:
metric_generators.extend(
[
EnergyConsumptionKubernetesGenerator(location=location),
CarbonEmissionKubernetesGenerator(location=location),
]
)
metric_generators.extend(add_containers_generator(location=location))
try:
exporter = get_exporter(
exporter_name=exporter_name,
Expand Down
22 changes: 20 additions & 2 deletions tracarbon/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,24 @@
from pydantic import BaseModel


def check_optional_dependency(name: str) -> bool:
import importlib.util

from loguru import logger

try:
importlib.import_module(name)
except ImportError:
logger.debug(f"{name} optional dependency is not installed.")
return False
return True


KUBERNETES_INSTALLED = check_optional_dependency(name="kubernetes")
DATADOG_INSTALLED = check_optional_dependency(name="datadog")
PROMETHEUS_INSTALLED = check_optional_dependency(name="prometheus_client")


def logger_configuration(level: str) -> None:
"""
Configure the logger format.
Expand Down Expand Up @@ -38,7 +56,7 @@ def __init__(
log_level: str = "INFO",
co2signal_api_key: str = "",
env_file_path: Optional[str] = None,
**data: Any
**data: Any,
) -> None:
load_dotenv(env_file_path)
log_level = os.environ.get("TRACARBON_LOG_LEVEL", log_level)
Expand All @@ -54,5 +72,5 @@ def __init__(
co2signal_api_key=os.environ.get(
"TRACARBON_CO2SIGNAL_API_KEY", co2signal_api_key
),
**data
**data,
)
11 changes: 2 additions & 9 deletions tracarbon/exporters/datadog_exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,11 @@

from loguru import logger

from tracarbon.conf import DATADOG_INSTALLED
from tracarbon.exporters.exporter import Exporter, MetricGenerator

try:
from datadog import ThreadStats, initialize

DATADOG_INSTALLED = True
except ImportError:
logger.debug("Datadog optional dependency is not installed.")
DATADOG_INSTALLED = False


if DATADOG_INSTALLED:
from datadog import ThreadStats, initialize

class DatadogExporter(Exporter):
"""
Expand Down
12 changes: 3 additions & 9 deletions tracarbon/exporters/prometheus_exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,12 @@

from loguru import logger

from tracarbon.conf import PROMETHEUS_INSTALLED
from tracarbon.exporters.exporter import Exporter, MetricGenerator

try:
if PROMETHEUS_INSTALLED:
import prometheus_client
from prometheus_client import CollectorRegistry, Gauge, start_http_server

PROMOTHEUS_INSTALLED = True
except ImportError:
logger.debug("Prometheus optional dependency is not installed.")
PROMOTHEUS_INSTALLED = False

if PROMOTHEUS_INSTALLED:
from prometheus_client import Gauge, start_http_server

class PrometheusExporter(Exporter):
"""
Expand Down
202 changes: 103 additions & 99 deletions tracarbon/general_metrics.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from typing import Any, AsyncGenerator, Optional

from tracarbon.conf import KUBERNETES_INSTALLED
from tracarbon.emissions import CarbonEmission, CarbonUsageUnit
from tracarbon.exporters import Metric, MetricGenerator, Tag
from tracarbon.hardwares import EnergyConsumption, EnergyUsageUnit, UsageType
from tracarbon.hardwares.containers import Kubernetes
from tracarbon.locations import Location


Expand Down Expand Up @@ -45,57 +45,6 @@ async def energy_usage_host() -> float:
)


class EnergyConsumptionKubernetesGenerator(MetricGenerator):
"""
Energy consumption generator for energy consumption of the containers.
"""

location: Location
energy_consumption: EnergyConsumption
kubernetes: Kubernetes

def __init__(self, **data: Any) -> None:
if "energy_consumption" not in data:
data["energy_consumption"] = EnergyConsumption.from_platform()
if "kubernetes" not in data:
data["kubernetes"] = Kubernetes()
super().__init__(metrics=[], **data)

async def generate(self) -> AsyncGenerator[Metric, None]:
"""
Generate metrics for the energy consumption with Kubernetes.
:return: an async generator of the metrics
"""
energy_usage = await self.energy_consumption.get_energy_usage()
energy_usage.convert_unit(unit=EnergyUsageUnit.MILLIWATT)
for pod in self.kubernetes.get_pods_usage():
for container in pod.containers:

async def get_container_energy_consumption() -> Optional[float]:
cpu_energy_usage = (
container.memory_usage * energy_usage.memory_energy_usage
)
memory_energy_usage = (
container.cpu_usage * energy_usage.cpu_energy_usage
)
return cpu_energy_usage + memory_energy_usage

yield Metric(
name="energy_consumption_kubernetes",
value=get_container_energy_consumption,
tags=[
Tag(key="pod_name", value=pod.name),
Tag(key="pod_namespace", value=pod.namespace),
Tag(key="container_name", value=container.name),
Tag(key="platform", value=self.platform),
Tag(key="containers", value="kubernetes"),
Tag(key="location", value=self.location.name),
Tag(key="units", value=energy_usage.unit.value),
],
)


class CarbonEmissionGenerator(MetricGenerator):
"""
Carbon emission generator to generate carbon emissions.
Expand Down Expand Up @@ -141,58 +90,113 @@ async def get_host_carbon_emission() -> float:
)


class CarbonEmissionKubernetesGenerator(MetricGenerator):
"""
Carbon emission generator to generate carbon emissions of the containers.
"""
if KUBERNETES_INSTALLED:
from tracarbon.hardwares.containers import Kubernetes

location: Location
carbon_emission: CarbonEmission
kubernetes: Kubernetes
co2signal_api_key: Optional[str] = None
class EnergyConsumptionKubernetesGenerator(MetricGenerator):
"""
Energy consumption generator for energy consumption of the containers.
"""

def __init__(self, location: Location, **data: Any) -> None:
if "carbon_emission" not in data:
data["carbon_emission"] = CarbonEmission(
co2signal_api_key=data["co2signal_api_key"]
if "co2signal_api_key" in data
else location.co2signal_api_key,
location=location,
)
if "kubernetes" not in data:
data["kubernetes"] = Kubernetes()
super().__init__(location=location, metrics=[], **data)
location: Location
energy_consumption: EnergyConsumption
kubernetes: Kubernetes

async def generate(self) -> AsyncGenerator[Metric, None]:
"""
Generate metrics for the carbon emission with Kubernetes.
def __init__(self, **data: Any) -> None:
if "energy_consumption" not in data:
data["energy_consumption"] = EnergyConsumption.from_platform()
if "kubernetes" not in data:
data["kubernetes"] = Kubernetes()
super().__init__(metrics=[], **data)

:return: an async generator of the metrics
async def generate(self) -> AsyncGenerator[Metric, None]:
"""
Generate metrics for the energy consumption with Kubernetes.
:return: an async generator of the metrics
"""
energy_usage = await self.energy_consumption.get_energy_usage()
energy_usage.convert_unit(unit=EnergyUsageUnit.MILLIWATT)
for pod in self.kubernetes.get_pods_usage():
for container in pod.containers:

async def get_container_energy_consumption() -> Optional[float]:
cpu_energy_usage = (
container.memory_usage * energy_usage.memory_energy_usage
)
memory_energy_usage = (
container.cpu_usage * energy_usage.cpu_energy_usage
)
return cpu_energy_usage + memory_energy_usage

yield Metric(
name="energy_consumption_kubernetes",
value=get_container_energy_consumption,
tags=[
Tag(key="pod_name", value=pod.name),
Tag(key="pod_namespace", value=pod.namespace),
Tag(key="container_name", value=container.name),
Tag(key="platform", value=self.platform),
Tag(key="containers", value="kubernetes"),
Tag(key="location", value=self.location.name),
Tag(key="units", value=energy_usage.unit.value),
],
)

class CarbonEmissionKubernetesGenerator(MetricGenerator):
"""
carbon_usage = await self.carbon_emission.get_co2_usage()
carbon_usage.convert_unit(unit=CarbonUsageUnit.CO2_MG_KWH)
Carbon emission generator to generate carbon emissions of the containers.
"""

location: Location
carbon_emission: CarbonEmission
kubernetes: Kubernetes
co2signal_api_key: Optional[str] = None

def __init__(self, location: Location, **data: Any) -> None:
if "carbon_emission" not in data:
data["carbon_emission"] = CarbonEmission(
co2signal_api_key=data["co2signal_api_key"]
if "co2signal_api_key" in data
else location.co2signal_api_key,
location=location,
)
if "kubernetes" not in data:
data["kubernetes"] = Kubernetes()
super().__init__(location=location, metrics=[], **data)

for pod in self.kubernetes.get_pods_usage():
for container in pod.containers:
async def generate(self) -> AsyncGenerator[Metric, None]:
"""
Generate metrics for the carbon emission with Kubernetes.
async def get_container_carbon_emission() -> Optional[float]:
memory_co2 = (
container.memory_usage * carbon_usage.memory_carbon_usage
:return: an async generator of the metrics
"""
carbon_usage = await self.carbon_emission.get_co2_usage()
carbon_usage.convert_unit(unit=CarbonUsageUnit.CO2_MG_KWH)

for pod in self.kubernetes.get_pods_usage():
for container in pod.containers:

async def get_container_carbon_emission() -> Optional[float]:
memory_co2 = (
container.memory_usage * carbon_usage.memory_carbon_usage
)
cpu_co2 = container.cpu_usage * carbon_usage.cpu_carbon_usage
return cpu_co2 + memory_co2

yield Metric(
name="carbon_emission_kubernetes",
value=get_container_carbon_emission,
tags=[
Tag(key="pod_name", value=pod.name),
Tag(key="pod_namespace", value=pod.namespace),
Tag(key="container_name", value=container.name),
Tag(key="platform", value=self.platform),
Tag(key="containers", value="kubernetes"),
Tag(key="location", value=self.location.name),
Tag(
key="source", value=self.location.co2g_kwh_source.value
),
Tag(key="units", value=carbon_usage.unit.value),
],
)
cpu_co2 = container.cpu_usage * carbon_usage.cpu_carbon_usage
return cpu_co2 + memory_co2

yield Metric(
name="carbon_emission_kubernetes",
value=get_container_carbon_emission,
tags=[
Tag(key="pod_name", value=pod.name),
Tag(key="pod_namespace", value=pod.namespace),
Tag(key="container_name", value=container.name),
Tag(key="platform", value=self.platform),
Tag(key="containers", value="kubernetes"),
Tag(key="location", value=self.location.name),
Tag(key="source", value=self.location.co2g_kwh_source.value),
Tag(key="units", value=carbon_usage.unit.value),
],
)
11 changes: 2 additions & 9 deletions tracarbon/hardwares/containers.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,15 @@
from typing import Any, Iterator, List, Optional

from loguru import logger
from pydantic import BaseModel

from tracarbon.conf import KUBERNETES_INSTALLED
from tracarbon.exceptions import TracarbonException
from tracarbon.hardwares.hardware import HardwareInfo

try:
if KUBERNETES_INSTALLED:
from kubernetes import config
from kubernetes.client import CoreV1Api, CustomObjectsApi

KUBERNETES_INSTALLED = True
except ImportError:
logger.debug("Kubernetes optional dependency is not installed.")
KUBERNETES_INSTALLED = False

if KUBERNETES_INSTALLED:

class Container(BaseModel):
"""
Container of Kubernetes.
Expand Down

0 comments on commit 929f829

Please sign in to comment.