Skip to content

e-footprint quickstart

This notebook provides an example scenario that you can use to get familiar with the Python API of efootprint: the daily video consumption of all French households on a big streaming platform.

You will get to describe:

  • the infrastructure involved (servers with auto-scaling settings, storage and network)
  • the user journey involving 2 steps (Streaming, Upload)
  • the usage pattern and the device population that executes it (the laptops of all French households)

Import the packages

⚠ If this steps fails, remember to run ipython kernel install --user --name=efootprint-kernel inside your python virtual environement (initializable with poetry install) to be able to select efootprint-kernel as the jupyter kernel.

# If this hasn’t been done in virtualenv (useful for Google colab notebook)
!pip install efootprint

pip install logs...

from efootprint.abstract_modeling_classes.source_objects import SourceValue, Sources, SourceObject
from efootprint.abstract_modeling_classes.empty_explainable_object import EmptyExplainableObject
from efootprint.core.usage.usage_journey import UsageJourney
from efootprint.core.usage.usage_journey_step import UsageJourneyStep
from efootprint.core.usage.job import Job
from efootprint.core.hardware.server import Server, ServerTypes
from efootprint.core.hardware.storage import Storage
from efootprint.core.usage.usage_pattern import UsagePattern
from efootprint.core.hardware.network import Network
from efootprint.core.hardware.device import Device
from efootprint.core.system import System
from efootprint.constants.countries import Countries
from efootprint.constants.units import u

Define the infrastructure

Creating objects manually

An e-footprint object has a name and attributes describing its technical and environmental characteristics:

storage = Storage(
    "SSD storage",
    carbon_footprint_fabrication_per_storage_capacity=SourceValue(160 * u.kg / u.TB, Sources.STORAGE_EMBODIED_CARBON_STUDY),
    power_per_storage_capacity=SourceValue(1.3 * u.W / u.TB, Sources.STORAGE_EMBODIED_CARBON_STUDY),
    lifespan=SourceValue(6 * u.years, Sources.HYPOTHESIS),
    idle_power=SourceValue(0 * u.W, Sources.HYPOTHESIS),
    storage_capacity=SourceValue(1 * u.TB, Sources.STORAGE_EMBODIED_CARBON_STUDY),
    data_replication_factor=SourceValue(3 * u.dimensionless, Sources.HYPOTHESIS),
    data_storage_duration = SourceValue(2 * u.year, Sources.HYPOTHESIS),
    base_storage_need = SourceValue(100 * u.TB, Sources.HYPOTHESIS),
    fixed_nb_of_instances = EmptyExplainableObject()
    )

Creating objects from default values

All e-footprint classes also implement default values and a from_defaults method that allows for using a set a pre-defined default attributes and specifying the ones we want to specify through keyword arguments.

Storage.default_values
{'carbon_footprint_fabrication_per_storage_capacity': 160.0 kilogram / terabyte,
 'power_per_storage_capacity': 1.3 watt / terabyte,
 'lifespan': 6.0 year,
 'idle_power': 0.0 watt,
 'storage_capacity': 1.0 terabyte,
 'data_replication_factor': 3.0 dimensionless,
 'base_storage_need': 0.0 terabyte,
 'data_storage_duration': 5.0 year}
# Creating a storage object from defaults while specifying storage capacity using keyword arguments
print(Storage.from_defaults("2 TB SSD storage", storage_capacity=SourceValue(2 * u.TB)))
Storage 9952c0

name: 2 TB SSD storage
lifespan: 6.0 year
fraction_of_usage_time: 1.0 dimensionless
carbon_footprint_fabrication_per_storage_capacity: 160.0 kilogram / terabyte
power_per_storage_capacity: 1.3 watt / terabyte
idle_power: 0.0 watt
storage_capacity: 2.0 terabyte
data_replication_factor: 3.0 dimensionless
data_storage_duration: 5.0 year
base_storage_need: 0.0 terabyte
fixed_nb_of_instances: no value

calculated_attributes:
  carbon_footprint_fabrication: 0.0 kilogram
  power: 0.0 watt
  storage_needed: no value
  storage_freed: no value
  automatic_storage_dumps_after_storage_duration: no value
  storage_delta: no value
  full_cumulative_storage_need: no value
  raw_nb_of_instances: no value
  nb_of_instances: no value
  nb_of_active_instances: no value
  instances_fabrication_footprint: no value
  instances_energy: no value
  energy_footprint: no value

We can see from the above print that e-footprint objects have calculated attributes that are setup as empty and then computed by e-footprint when associated with a usage. More information on e-footprint objects’ calculated_attributes can be found in the object reference section of the e-footprint documentation.

Creating objects from archetypes

Some e-footprint objects (Storage, Network and Hardware) also have archetypes that have their own set of default values:

Storage.archetypes()
[<bound method Storage.ssd of <class 'efootprint.core.hardware.storage.Storage'>>,
 <bound method Storage.hdd of <class 'efootprint.core.hardware.storage.Storage'>>]
print(Storage.hdd())
Storage 1dde83

name: Default HDD storage
lifespan: 4.0 year
fraction_of_usage_time: 1.0 dimensionless
carbon_footprint_fabrication_per_storage_capacity: 20.0 kilogram / terabyte
power_per_storage_capacity: 4.2 watt / terabyte
idle_power: 0.0 watt
storage_capacity: 1.0 terabyte
data_replication_factor: 3.0 dimensionless
data_storage_duration: 5.0 year
base_storage_need: 0.0 terabyte
fixed_nb_of_instances: no value

calculated_attributes:
  carbon_footprint_fabrication: 0.0 kilogram
  power: 0.0 watt
  storage_needed: no value
  storage_freed: no value
  automatic_storage_dumps_after_storage_duration: no value
  storage_delta: no value
  full_cumulative_storage_need: no value
  raw_nb_of_instances: no value
  nb_of_instances: no value
  nb_of_active_instances: no value
  instances_fabrication_footprint: no value
  instances_energy: no value
  energy_footprint: no value

Apart from environmental and technical attributes, e-footprint objects can link to other e-footprint objects. For example, server objects have a storage attribute:

server = Server.from_defaults(
    "server",
    server_type=ServerTypes.autoscaling(),
    power_usage_effectiveness=SourceValue(1.2 * u.dimensionless, Sources.HYPOTHESIS),
    average_carbon_intensity=SourceValue(100 * u.g / u.kWh, Sources.HYPOTHESIS),
    utilization_rate=SourceValue(0.9 * u.dimensionless, Sources.HYPOTHESIS),
    base_ram_consumption=SourceValue(300 * u.MB_ram, Sources.HYPOTHESIS),
    base_compute_consumption=SourceValue(2 * u.cpu_core, Sources.HYPOTHESIS),
    storage=storage
)

print(server)
Server 6ec950

name: server
carbon_footprint_fabrication: 600.0 kilogram
power: 300.0 watt
lifespan: 6.0 year
fraction_of_usage_time: 1.0 dimensionless
server_type: autoscaling
idle_power: 50.0 watt
ram: 128.0 gigabyte_ram
compute: 24.0 cpu_core
power_usage_effectiveness: 1.2 dimensionless
average_carbon_intensity: 100.0 gram / kilowatt_hour
utilization_rate: 0.9 dimensionless
base_ram_consumption: 300.0 megabyte_ram
base_compute_consumption: 2.0 cpu_core
fixed_nb_of_instances: no value
storage: 23b03b

calculated_attributes:
  hour_by_hour_ram_need: no value
  hour_by_hour_compute_need: no value
  occupied_ram_per_instance: no value
  occupied_compute_per_instance: no value
  available_ram_per_instance: no value
  available_compute_per_instance: no value
  raw_nb_of_instances: no value
  nb_of_instances: no value
  instances_fabrication_footprint: no value
  instances_energy: no value
  energy_footprint: no value

Creating objects from builders connected to external data sources

Of course only relying on a single set of default values for creating our servers won’t get us far. That’s why e-footprint provides a builder class that connects to Boavizta’s API to allow for the creation of servers from a cloud provider and an instance type.

from efootprint.builders.hardware.boavizta_cloud_server import BoaviztaCloudServer

# Some attributes can only take specific values
for attribute, attribute_list_value in BoaviztaCloudServer.list_values.items():
    print(f"Possible values for {attribute}: {attribute_list_value}")
2025-11-01 15:06:07,000 - INFO - Calling Boavizta API with url https://api.boavizta.org/v1/cloud/instance/all_providers, method GET and params {}
2025-11-01 15:06:07,055 - INFO - Boavizta API call succeeded in 54 ms.
2025-11-01 15:06:07,056 - INFO - Calling Boavizta API with url https://api.boavizta.org/v1/cloud/instance/all_instances, method GET and params {'provider': 'aws'}
2025-11-01 15:06:07,104 - INFO - Boavizta API call succeeded in 48 ms.
2025-11-01 15:06:07,108 - INFO - Calling Boavizta API with url https://api.boavizta.org/v1/cloud/instance/all_instances, method GET and params {'provider': 'azure'}
2025-11-01 15:06:07,151 - INFO - Boavizta API call succeeded in 42 ms.
2025-11-01 15:06:07,152 - INFO - Calling Boavizta API with url https://api.boavizta.org/v1/cloud/instance/all_instances, method GET and params {'provider': 'scaleway'}
2025-11-01 15:06:07,193 - INFO - Boavizta API call succeeded in 40 ms.
2025-11-01 15:06:07,194 - INFO - Calling Boavizta API with url https://api.boavizta.org/v1/cloud/instance/all_instances, method GET and params {'provider': 'gcp'}
2025-11-01 15:06:07,238 - INFO - Boavizta API call succeeded in 43 ms.
2025-11-01 15:06:07,240 - INFO - Imported BoaviztaCloudServer in 0.25827 seconds.


Possible values for server_type: [autoscaling, on-premise, serverless]
Possible values for provider: [aws, azure, scaleway, gcp]
# Moreover, some attributes depend on another attribute for their values
for attribute, attribute_conditional_dict in BoaviztaCloudServer.conditional_list_values.items():
    condition_attribute = attribute_conditional_dict['depends_on']
    print(f"Possible values for {attribute} depend on {condition_attribute}:\n")
    for condition_value, possible_values in attribute_conditional_dict["conditional_list_values"].items():
        if len(possible_values) > 10:
            values_to_print = possible_values[:5] + ["etc."]
        else:
            values_to_print = possible_values
        print(f"    Possible values when {condition_attribute} is {condition_value}: {values_to_print}")
    print("\n")
Possible values for fixed_nb_of_instances depend on server_type:

    Possible values when server_type is autoscaling: [no value]
    Possible values when server_type is serverless: [no value]


Possible values for instance_type depend on provider:

    Possible values when provider is aws: [a1.medium, a1.large, a1.xlarge, a1.2xlarge, a1.4xlarge, 'etc.']
    Possible values when provider is azure: [d2ads_v5, d4ads_v5, d8ads_v5, d16ads_v5, d32ads_v5, 'etc.']
    Possible values when provider is scaleway: [coparm1-16c-64g, coparm1-2c-8g, coparm1-32c-128g, coparm1-4c-16g, coparm1-8c-32g, 'etc.']
    Possible values when provider is gcp: [c4a-standard-1, c4a-standard-2, c4a-standard-4, c4a-standard-8, c4a-standard-16, 'etc.']
# BoaviztaCloudServer still has quite a lot of default values but ones that are much easier to make hypothesis on, 
# like lifespan, server utilisation rate or power usage effectiveness
BoaviztaCloudServer.default_values
{'provider': scaleway,
 'instance_type': ent1-s,
 'server_type': autoscaling,
 'average_carbon_intensity': 400.0 gram / kilowatt_hour,
 'lifespan': 6.0 year,
 'idle_power': 0.0 watt,
 'power_usage_effectiveness': 1.2 dimensionless,
 'utilization_rate': 0.9 dimensionless,
 'base_ram_consumption': 0.0 gigabyte_ram,
 'base_compute_consumption': 0.0 cpu_core,
 'fixed_nb_of_instances': no value}
# The most difficult environmental and technical attributes are retrieved from a call to BoaviztAPI:
print(BoaviztaCloudServer.from_defaults("Default Boavizta cloud server", storage=Storage.ssd(storage_capacity=SourceValue(32 * u.GB))))
BoaviztaCloudServer f3246d

name: Default Boavizta cloud server
lifespan: 6.0 year
fraction_of_usage_time: 1.0 dimensionless
server_type: autoscaling
idle_power: 0.0 watt
power_usage_effectiveness: 1.2 dimensionless
average_carbon_intensity: 400.0 gram / kilowatt_hour
utilization_rate: 0.9 dimensionless
base_ram_consumption: 0.0 gigabyte_ram
base_compute_consumption: 0.0 cpu_core
fixed_nb_of_instances: no value
storage: fdf468
provider: scaleway
instance_type: ent1-s

calculated_attributes:
  api_call_response: no value
  carbon_footprint_fabrication: 0.0 kilogram
  power: 0.0 watt
  ram: 0.0 gigabyte_ram
  compute: 0.0 cpu_core
  hour_by_hour_ram_need: no value
  hour_by_hour_compute_need: no value
  occupied_ram_per_instance: no value
  occupied_compute_per_instance: no value
  available_ram_per_instance: no value
  available_compute_per_instance: no value
  raw_nb_of_instances: no value
  nb_of_instances: no value
  instances_fabrication_footprint: no value
  instances_energy: no value
  energy_footprint: no value

[Optional] Install services on your server

Manually creating job objects can get tricky because you have to specify how much RAM and compute the job uses on the server it runs on during its duration. That’s why e-footprint allows for the installation of services on servers, that will give access to higher-level job classes that compute these very technical attributes from simpler ones. For example, let’s install a video streaming service on our server:

Video streaming service

from efootprint.builders.services.video_streaming import VideoStreaming

VideoStreaming.default_values
{'base_ram_consumption': 2.0 gigabyte_ram,
 'bits_per_pixel': 0.1 dimensionless,
 'static_delivery_cpu_cost': 4.0 cpu_core * second / gigabyte,
 'ram_buffer_per_user': 50.0 megabyte_ram}
video_streaming_service = VideoStreaming.from_defaults("Video streaming service", server=server)
2025-11-01 15:06:07,259 - INFO - Computing calculated attributes for VideoStreaming Video streaming service
2025-11-01 15:06:07,259 - INFO - Computing calculated attributes for Server server
# All services have a list of compatible job types, let’s check out the ones for video streaming:
VideoStreaming.compatible_jobs()
[efootprint.builders.services.video_streaming.VideoStreamingJob]
# There’s only one so let’s use it !
from efootprint.builders.services.video_streaming import VideoStreamingJob

VideoStreamingJob.default_values
{'resolution': 1080p (1920 x 1080),
 'video_duration': 1.0 hour,
 'refresh_rate': 30.0 / second,
 'data_stored': 0.0 megabyte}
print(VideoStreamingJob.list_values)
{'resolution': [480p (640 x 480), 720p (1280 x 720), 1080p (1920 x 1080), 1440p (2560 x 1440), 2K (2048 x 1080), 4K (3840 x 2160), 8K (7680 x 4320)]}
# Now it’s easy to add a 1 hour 1080p streaming job to our streaming service
streaming_job = VideoStreamingJob.from_defaults(
    "streaming job", service=video_streaming_service, resolution=SourceObject("1080p (1920 x 1080)"), 
    video_duration=SourceValue(20 * u.min))

# For optimization purposes calculations are only made when usage data has been entered but we can force
# some of them to see what the VideoStreamingJob does.
streaming_job.update_dynamic_bitrate()
streaming_job.update_data_transferred()
streaming_job.update_compute_needed()
streaming_job.update_ram_needed()

print(streaming_job)
VideoStreamingJob 454805

name: streaming job
data_stored: 0.0 megabyte
service: f66cba
video_duration: 20.0 minute
resolution: 1080p (1920 x 1080)
refresh_rate: 30.0 / second

calculated_attributes:
  request_duration: 0.0 second
  dynamic_bitrate: 0.78 megabyte / second
  data_transferred: 0.0 gigabyte
  compute_needed: 0.0 cpu_core
  ram_needed: 50.0 megabyte_ram
  hourly_occurrences_per_usage_pattern: {}
  hourly_avg_occurrences_per_usage_pattern: {}
  hourly_data_transferred_per_usage_pattern: {}
  hourly_data_stored_per_usage_pattern: {}
  hourly_avg_occurrences_across_usage_patterns: no value
  hourly_data_transferred_across_usage_patterns: no value
  hourly_data_stored_across_usage_patterns: no value

Web application service

In the same vein, we can install a web application service on our server. e-footprint’s WebApplication service relies on the analysis of Boavizta’s ecobenchmark project.

from efootprint.builders.services.web_application import WebApplication, WebApplicationJob

web_app_service = WebApplication("Web app", server=server, technology=SourceObject("php-symfony"))
web_app_job = WebApplicationJob.from_defaults("fetching web view", service=web_app_service)
web_app_job.update_compute_needed()
web_app_job.update_ram_needed()

print(web_app_service)
print(web_app_job)
2025-11-01 15:06:07,321 - INFO - Computing calculated attributes for WebApplication Web app
2025-11-01 15:06:07,321 - INFO - Computing calculated attributes for Server server


WebApplication 33b583

name: Web app
server: 6ec950
base_ram_consumption: no value
base_compute_consumption: no value
technology: php-symfony

WebApplicationJob d4c453

name: fetching web view
data_transferred: 2.2 megabyte
data_stored: 100.0 kilobyte
service: 33b583
implementation_details: default

calculated_attributes:
  request_duration: 0.0 second
  compute_needed: 0.08 cpu_core
  ram_needed: 6.15 megabyte
  hourly_occurrences_per_usage_pattern: {}
  hourly_avg_occurrences_per_usage_pattern: {}
  hourly_data_transferred_per_usage_pattern: {}
  hourly_data_stored_per_usage_pattern: {}
  hourly_avg_occurrences_across_usage_patterns: no value
  hourly_data_transferred_across_usage_patterns: no value
  hourly_data_stored_across_usage_patterns: no value

Define the user journey

This is the modeling of the average daily usage of the streaming platform in France:

streaming_step = UsageJourneyStep(
    "20 min streaming",
    user_time_spent=SourceValue(20 * u.min, Sources.USER_DATA),
    jobs=[web_app_job, streaming_job])

video_upload_job = Job(
    "upload job", server=server, data_transferred=SourceValue(20 * u.MB, Sources.USER_DATA),
    data_stored=SourceValue(20 * u.MB, Sources.USER_DATA),
    request_duration=SourceValue(2 * u.s, Sources.HYPOTHESIS),
    compute_needed=SourceValue(1 * u.cpu_core, Sources.HYPOTHESIS),
    ram_needed=SourceValue(50 * u.MB_ram, Sources.HYPOTHESIS))

upload_step = UsageJourneyStep(
    "1 min video capture then upload",
    user_time_spent=SourceValue(70 * u.s, Sources.USER_DATA),
    jobs=[web_app_job, video_upload_job])

The user journey is then simply a list of user journey steps:

user_journey = UsageJourney("Mean video consumption user journey", uj_steps=[streaming_step, upload_step])
2025-11-01 15:06:07,332 - INFO - Computing calculated attributes for UsageJourney Mean video consumption user journey

Describe usage

An e-footprint usage pattern links a user journey to devices that run it, a network, a country, and the number of times the user journey gets executed hour by hour.

# Let’s build synthetic usage data by summing a linear growth with a sinusoidal fluctuation components, then adding daily variation
from datetime import datetime, timedelta

from efootprint.builders.time_builders import linear_growth_hourly_values

start_date = datetime.strptime("2025-01-01", "%Y-%m-%d")
timespan = 3 * u.year

linear_growth = linear_growth_hourly_values(timespan, start_value=5000, end_value=100000, start_date=start_date)
linear_growth.set_label("Hourly user journeys linear growth component")

linear_growth.plot()
(<Figure size 1000x400 with 1 Axes>,
 <Axes: title={'center': 'Hourly user journeys linear growth component from e-footprint hypothesis'}, ylabel='occurrence'>)

png

from efootprint.builders.time_builders import sinusoidal_fluct_hourly_values

sinusoidal_fluct = sinusoidal_fluct_hourly_values(
    timespan, sin_fluct_amplitude=3000, sin_fluct_period_in_hours=3 * 30 * 24, start_date=start_date)

lin_growth_plus_sin_fluct = (linear_growth + sinusoidal_fluct).set_label("Hourly user journeys linear growth with sinusoidal fluctuations")

lin_growth_plus_sin_fluct.plot()
(<Figure size 1000x400 with 1 Axes>,
 <Axes: title={'center': 'Hourly user journeys linear growth with sinusoidal fluctuations'}, ylabel='occurrence'>)

png

# Let’s add daily variations because people use the system less at night
from efootprint.builders.time_builders import daily_fluct_hourly_values

daily_fluct = daily_fluct_hourly_values(timespan, fluct_scale=0.8, hour_of_day_for_min_value=4, start_date=start_date)
daily_fluct.set_label("Daily volume fluctuation")

daily_fluct.plot(xlims=[start_date, start_date+timedelta(days=1)])
(<Figure size 1000x400 with 1 Axes>,
 <Axes: title={'center': 'Daily volume fluctuation from e-footprint hypothesis'}, ylabel='occurrence'>)

png

hourly_user_journey_starts = lin_growth_plus_sin_fluct * daily_fluct
hourly_user_journey_starts.set_label("Hourly number of user journey started")

hourly_user_journey_starts.plot(xlims=[start_date, start_date + timedelta(days=7)])
(<Figure size 1000x400 with 1 Axes>,
 <Axes: title={'center': 'Hourly number of user journey started'}, ylabel='occurrence ** 2'>)

png

# Over 3 years the daily fluctuations color the area between daily min and max number of hourly user journeys
hourly_user_journey_starts.plot()
(<Figure size 1000x400 with 1 Axes>,
 <Axes: title={'center': 'Hourly number of user journey started'}, ylabel='occurrence ** 2'>)

png

network = Network(
        "WIFI network",
        bandwidth_energy_intensity=SourceValue(0.05 * u("kWh/GB"), Sources.TRAFICOM_STUDY))

usage_pattern = UsagePattern(
    "Daily video streaming consumption",
    usage_journey=user_journey,
    devices=[Device.laptop()],
    network=network,
    country=Countries.FRANCE(),
    hourly_usage_journey_starts=hourly_user_journey_starts)

system = System("System", usage_patterns=[usage_pattern], edge_usage_patterns=[])
2025-11-01 15:06:07,848 - INFO - Starting computing System modeling
2025-11-01 15:06:07,849 - INFO - Computing calculated attributes for UsagePattern Daily video streaming consumption
2025-11-01 15:06:07,850 - INFO - Computing calculated attributes for WebApplicationJob fetching web view
2025-11-01 15:06:07,851 - INFO - Computing calculated attributes for VideoStreamingJob streaming job
2025-11-01 15:06:07,852 - INFO - Computing calculated attributes for Job upload job
2025-11-01 15:06:07,852 - INFO - Computing calculated attributes for Server server
2025-11-01 15:06:07,854 - INFO - Computing calculated attributes for Network WIFI network
2025-11-01 15:06:07,855 - INFO - Computing calculated attributes for Storage SSD storage
2025-11-01 15:06:07,857 - INFO - Computing calculated attributes for System System
2025-11-01 15:06:07,858 - INFO - Computed 62 calculated attributes over 14 objects in 0.009 seconds or 0.15 ms per computation

Results

Computed attributes

Now all calculated_attributes have been computed:

print(server)
Server 6ec950

name: server
carbon_footprint_fabrication: 600.0 kilogram
power: 300.0 watt
lifespan: 6.0 year
fraction_of_usage_time: 1.0 dimensionless
server_type: autoscaling
idle_power: 50.0 watt
ram: 128.0 gigabyte_ram
compute: 24.0 cpu_core
power_usage_effectiveness: 1.2 dimensionless
average_carbon_intensity: 100.0 gram / kilowatt_hour
utilization_rate: 0.9 dimensionless
base_ram_consumption: 300.0 megabyte_ram
base_compute_consumption: 2.0 cpu_core
fixed_nb_of_instances: no value
storage: 23b03b

calculated_attributes:
  hour_by_hour_ram_need: 26298 values from 2024-12-31 23:00:00+00:00 to 2028-01-01 17:00:00+00:00 in GB_ram:
    first 10 vals [50.09, 36.35, 25.77, 19.11, 16.86, 19.21, 26.03, 36.89, 51.08, 67.67],
    last 10 vals [1027.92, 1358.57, 1713.46, 2068.39, 2399.2, 2683.34, 2901.45, 3038.65, 3085.61, 3039.1]
  hour_by_hour_compute_need: 26298 values from 2024-12-31 23:00:00+00:00 to 2028-01-01 17:00:00+00:00 in cpu_core:
    first 10 vals [4.9, 3.56, 2.52, 1.87, 1.65, 1.88, 2.55, 3.61, 5.0, 6.62],
    last 10 vals [100.61, 132.97, 167.7, 202.44, 234.82, 262.63, 283.98, 297.41, 302.0, 297.45]
  occupied_ram_per_instance: 2.3 gigabyte_ram
  occupied_compute_per_instance: 2.0 cpu_core
  available_ram_per_instance: 112.9 gigabyte_ram
  available_compute_per_instance: 19.6 cpu_core
  raw_nb_of_instances: 26298 values from 2024-12-31 23:00:00+00:00 to 2028-01-01 17:00:00+00:00 in concurrent:
    first 10 vals [0.44, 0.32, 0.23, 0.17, 0.15, 0.17, 0.23, 0.33, 0.45, 0.6],
    last 10 vals [9.1, 12.03, 15.18, 18.32, 21.25, 23.77, 25.7, 26.91, 27.33, 26.92]
  nb_of_instances: 26298 values from 2024-12-31 23:00:00+00:00 to 2028-01-01 17:00:00+00:00 in concurrent:
    first 10 vals [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0],
    last 10 vals [10.0, 13.0, 16.0, 19.0, 22.0, 24.0, 26.0, 27.0, 28.0, 27.0]
  instances_fabrication_footprint: 26298 values from 2024-12-31 23:00:00+00:00 to 2028-01-01 17:00:00+00:00 in kg:
    first 10 vals [0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01],
    last 10 vals [0.11, 0.15, 0.18, 0.22, 0.25, 0.27, 0.3, 0.31, 0.32, 0.31]
  instances_energy: 26298 values from 2024-12-31 23:00:00+00:00 to 2028-01-01 17:00:00+00:00 in kWh:
    first 10 vals [0.19, 0.16, 0.13, 0.11, 0.1, 0.11, 0.13, 0.16, 0.2, 0.24],
    last 10 vals [3.33, 4.39, 5.51, 6.64, 7.7, 8.57, 9.27, 9.69, 9.88, 9.7]
  energy_footprint: 26298 values from 2024-12-31 23:00:00+00:00 to 2028-01-01 17:00:00+00:00 in kg:
    first 10 vals [0.02, 0.02, 0.01, 0.01, 0.01, 0.01, 0.01, 0.02, 0.02, 0.02],
    last 10 vals [0.33, 0.44, 0.55, 0.66, 0.77, 0.86, 0.93, 0.97, 0.99, 0.97]

System footprint overview

system.plot_footprints_by_category_and_object("System footprints.html")
print("placeholder")

Object relationships graph

Hover over a node to get the numerical values of its environmental and technical attributes. For simplifying the graph the Network and Hardware nodes are not shown.

from efootprint.utils.object_relationships_graphs import USAGE_PATTERN_VIEW_CLASSES_TO_IGNORE, INFRA_VIEW_CLASSES_TO_IGNORE

usage_pattern.object_relationship_graph_to_file("object_relationships_graph_up_view.html", width="800px", height="500px",
    classes_to_ignore=USAGE_PATTERN_VIEW_CLASSES_TO_IGNORE, notebook=True)
object_relationships_graph_up_view.html

usage_pattern.object_relationship_graph_to_file("object_relationships_graph_infra_view.html", width="800px", height="500px",
    classes_to_ignore=INFRA_VIEW_CLASSES_TO_IGNORE, notebook=True)
object_relationships_graph_infra_view.html

usage_pattern.object_relationship_graph_to_file("object_relationships_graph_all_objects.html", width="800px", height="500px",
    classes_to_ignore=[], notebook=True)
object_relationships_graph_all_objects.html

Calculus graph

Any e-footprint calculation can generate its calculation graph for full auditability. Hover on a calculus node to display its formula and numeric value.

usage_pattern.devices_fabrication_footprint.calculus_graph_to_file(
    "device_population_fab_footprint_calculus_graph.html", width="800px", height="500px", notebook=True)

Plotting an object’s hourly and cumulated CO2 emissions

server.energy_footprint.plot()
(<Figure size 1000x400 with 1 Axes>,
 <Axes: title={'center': 'Hourly server energy footprint'}, ylabel='kg'>)

png

server.energy_footprint.plot(cumsum=True)
(<Figure size 1000x400 with 1 Axes>,
 <Axes: title={'center': 'Cumulative hourly server energy footprint'}, ylabel='kg'>)

png

system.total_footprint.plot(cumsum=True)
(<Figure size 1000x400 with 1 Axes>,
 <Axes: title={'center': 'Cumulative system total carbon footprint'}, ylabel='kg'>)

png

Analysing the impact of a change

Numeric input change

Any input change automatically triggers the computation of calculations that depend on the input. For example, let’s say that the average data download consumption of the streaming step decreased because of a change in default video quality:

streaming_job.resolution = SourceObject("720p (1280 x 720)", Sources.USER_DATA)
2025-11-01 15:06:08,739 - INFO - 1 changes lead to 13 update computations done in 3.8 ms (avg 0.29 ms per computation).
system.plot_emission_diffs("bandwith reduction.png")
(<Figure size 1000x500 with 3 Axes>,
 <Axes: xlabel='Category', ylabel='tonnes CO2 emissions'>)

png

System structure change

Now let’s make a more complex change, like adding a conversation with a generative AI chatbot before streaming the video. We will use e-footprint’s GenAIModel object that fetches LLM data like number of parameters from EcoLogits.

# GenAI models need a GPU server
from efootprint.core.hardware.gpu_server import GPUServer
from efootprint.builders.services.generative_ai_ecologits import GenAIModel

llm_server = GPUServer.from_defaults("Inference GPU server", server_type=ServerTypes.serverless(), compute=SourceValue(16 * u.gpu),
                                    storage=Storage.ssd(storage_capacity=SourceValue(1 * u.TB)))
genai_model = GenAIModel.from_defaults(
    "Openai’s gpt-4o", server=llm_server, 
    provider=SourceObject("openai"), model_name=SourceObject("gpt-4o"))
2025-11-01 15:06:08,856 - INFO - Computing calculated attributes for GenAIModel Openai’s gpt-4o
2025-11-01 15:06:08,857 - INFO - Computing calculated attributes for GPUServer Inference GPU server
from efootprint.builders.services.generative_ai_ecologits import GenAIJob

genai_job = GenAIJob("LLM API call", genai_model, output_token_count= SourceValue(1000 * u.dimensionless))
llm_chat_step = UsageJourneyStep(
    "Chat with LLM to select video", user_time_spent=SourceValue(1 * u.min, Sources.HYPOTHESIS),
    jobs=[genai_job])
# Adding the new step is simply an attribute update.
user_journey.uj_steps.append(llm_chat_step)
2025-11-01 15:06:08,870 - INFO - 13 recomputed objects: ['Chat with LLM to select video', 'Mean video consumption user journey', 'Daily video streaming consumption', 'LLM API call', 'fetching web view', 'streaming job', 'upload job', 'WIFI network', 'Inference GPU server', 'server', 'Default SSD storage', 'SSD storage', 'System']
2025-11-01 15:06:08,881 - INFO - 1 changes lead to 104 update computations done in 13.5 ms (avg 0.13 ms per computation).
system.plot_emission_diffs("LLM chat addition.png")
(<Figure size 1000x500 with 3 Axes>,
 <Axes: xlabel='Category', ylabel='tonnes CO2 emissions'>)

png

We can see that server energy footprint has been multiplied by more than 1000 and the rest of the impact is quite negligible. Good to know to make informed decisions ! Of course the impact is very much dependent on assumptions. If the LLM server ran on low-carbon electricity for example:

llm_server.average_carbon_intensity = SourceValue(50 * u.g / u.kWh, Sources.HYPOTHESIS)
system.plot_emission_diffs("lower LLM inference carbon intensity.png")
2025-11-01 15:06:09,013 - INFO - 1 changes lead to 3 update computations done in 2.7 ms (avg 0.9 ms per computation).





(<Figure size 1000x500 with 3 Axes>,
 <Axes: xlabel='Category', ylabel='tonnes CO2 emissions'>)

png

Recap of all System changes

system.plot_emission_diffs("All system diffs.png", from_start=True)
(<Figure size 1000x500 with 3 Axes>,
 <Axes: xlabel='Category', ylabel='tonnes CO2 emissions'>)

png

Making simulations of changes in the future

We’ve seen that you can make changes in your modeling and analyse the differences, but most likely the changes you’re contemplating will happen at some point in the future. Let’s model a change in the future thanks to e-footprint’s ModelingUpdate object !

# Let’s first revert the system to its state before changes
# We can make optimized batch changes by using the ModelingUpdate object, that is also used to make simulations of changes in the future
from efootprint.abstract_modeling_classes.modeling_update import ModelingUpdate

ModelingUpdate([
    [user_journey.uj_steps, [streaming_step, upload_step]],
    [llm_server.average_carbon_intensity, SourceValue(300 * u.g / u.kWh, Sources.HYPOTHESIS)],
    [streaming_job.resolution, SourceObject("1080p (1920 x 1080)")]
])

system.plot_footprints_by_category_and_object("System footprints after reset.html")
2025-11-01 15:06:09,263 - INFO - Optimized modeling object computation chain from 16 to 12 modeling object calculated attributes recomputations.
2025-11-01 15:06:09,264 - INFO - 13 recomputed objects: ['Chat with LLM to select video', 'Mean video consumption user journey', 'Daily video streaming consumption', 'fetching web view', 'streaming job', 'upload job', 'LLM API call', 'WIFI network', 'server', 'Inference GPU server', 'SSD storage', 'Default SSD storage', 'System']
2025-11-01 15:06:09,264 - INFO - Optimized update function chain from 120 to 104 calculations
2025-11-01 15:06:09,272 - INFO - 3 changes lead to 104 update computations done in 10.5 ms (avg 0.1 ms per computation).
# To create a simulation, which is a change in the future, simply set ModelingUpdate’s simulation_date parameter
import pytz

simulation = ModelingUpdate([[user_journey.uj_steps, [streaming_step, upload_step, llm_chat_step]]],
                           simulation_date=pytz.utc.localize(start_date) + timedelta(days=365))
2025-11-01 15:06:09,319 - INFO - 13 recomputed objects: ['Chat with LLM to select video', 'Mean video consumption user journey', 'Daily video streaming consumption', 'LLM API call', 'fetching web view', 'streaming job', 'upload job', 'WIFI network', 'Inference GPU server', 'server', 'Default SSD storage', 'SSD storage', 'System']
2025-11-01 15:06:09,361 - INFO - Simulation specific operations done.
2025-11-01 15:06:09,371 - INFO - Resetting direct changes from 1 updated values
2025-11-01 15:06:09,371 - INFO - Resetting filtered hourly quantities from 1 updated values
2025-11-01 15:06:09,371 - INFO - Resetting replaced ancestors copies from 81 updated values
2025-11-01 15:06:09,372 - INFO - Resetting recomputed values from 104 updated values
2025-11-01 15:06:09,372 - INFO - 1 changes lead to 104 update computations done in 55.9 ms (avg 0.54 ms per computation).
system.total_footprint.plot(cumsum=True)
(<Figure size 1000x400 with 1 Axes>,
 <Axes: title={'center': 'Cumulative system total carbon footprint'}, ylabel='kg'>)

png

llm_server.energy_footprint.plot(cumsum=True)
(<Figure size 1000x400 with 1 Axes>,
 <Axes: title={'center': 'Cumulative hourly Inference GPU server energy footprint'}, ylabel='kg'>)

png

# The system state is reset to baseline after the simulation.
# For example, our LLM server has no energy footprint since it is not used in the baseline modeling
llm_server.energy_footprint
no value
# To set simulation values, use ModelingUpdate’s set_updated_values method
simulation.set_updated_values()
llm_server.energy_footprint
2025-11-01 15:06:09,587 - INFO - Setting direct changes from 1 previous values
2025-11-01 15:06:09,587 - INFO - Setting filtered hourly quantities from 1 previous values
2025-11-01 15:06:09,588 - INFO - Setting replaced ancestors copies from 81 previous values
2025-11-01 15:06:09,588 - INFO - Setting recomputed values from 104 previous values





17537 values from 2026-01-01 00:00:00+00:00 to 2028-01-01 17:00:00+00:00 in kg:
    first 10 vals [358.02, 253.3, 187.45, 165.02, 187.57, 253.61, 358.69, 495.68, 655.28, 826.65],
    last 10 vals [1346.61, 1779.79, 2244.7, 2709.68, 3143.05, 3515.28, 3801.01, 3980.76, 4042.27, 3981.35]
# Conversely, pre-update values are reset using ModelingUpdate’s reset_values method
simulation.reset_values()
llm_server.energy_footprint
2025-11-01 15:06:09,591 - INFO - Resetting direct changes from 1 updated values
2025-11-01 15:06:09,591 - INFO - Resetting filtered hourly quantities from 1 updated values
2025-11-01 15:06:09,591 - INFO - Resetting replaced ancestors copies from 81 updated values
2025-11-01 15:06:09,592 - INFO - Resetting recomputed values from 104 updated values





no value

[Optional] Model edge devices

The above modeling reflects a centralized web logic: the input usage is a business input (typically, the number of visits on a website), and the infrastructure has to be sized to meet the demand. But there are cases where the usage is actually proportional to a number of devices, typically when trying to model the impact of selling digital devices, like servers, smartphones or video game consoles. In this case, e-footprint edge objects allow for the description of usage per device, and number of devices sold / produced over the modeling period.

Define the edge device

The EdgeStorage and EdgeDevice objects are similar to the Storage and Server objects, but simpler because they don’t have to include any autoscaling logic.

from efootprint.core.hardware.edge.edge_storage import EdgeStorage
from efootprint.builders.hardware.edge.edge_computer import EdgeComputer

edge_storage = EdgeStorage(
    "Edge SSD storage",
    carbon_footprint_fabrication_per_storage_capacity=SourceValue(160 * u.kg / u.TB),
    power_per_storage_capacity=SourceValue(1.3 * u.W / u.TB),
    lifespan=SourceValue(6 * u.years),
    idle_power=SourceValue(0.1 * u.W),
    storage_capacity=SourceValue(256 * u.GB),
    base_storage_need=SourceValue(10 * u.GB),
)

edge_computer = EdgeComputer(
    "Edge device",
    carbon_footprint_fabrication=SourceValue(60 * u.kg),
    power=SourceValue(30 * u.W),
    lifespan=SourceValue(8 * u.year),
    idle_power=SourceValue(5 * u.W),
    ram=SourceValue(16 * u.GB_ram),
    compute=SourceValue(8 * u.cpu_core),
    base_ram_consumption=SourceValue(1 * u.GB_ram),
    base_compute_consumption=SourceValue(0.1 * u.cpu_core),
    storage=edge_storage
)

Define the edge usage journey with the processes that run on the edge device

The EdgeUsageJourney object associate a list of RecurrentEdgeProcesses to an EdgeDevice object, over a usage span. The RecurrentEdgeProcess object describes the usage of compute, RAM and storage on the EdgeDevice object by describing hourly usage in a canonical week starting at midnight on a Monday local time.

import numpy as np
from pint import Quantity

from efootprint.abstract_modeling_classes.source_objects import SourceRecurrentValues
from efootprint.builders.usage.edge.recurrent_edge_process import RecurrentEdgeProcess
from efootprint.core.usage.edge.edge_function import EdgeFunction
from efootprint.core.usage.edge.edge_usage_journey import EdgeUsageJourney

edge_process = RecurrentEdgeProcess(
    "Edge process",
    edge_device=edge_computer,
    recurrent_compute_needed=SourceRecurrentValues(
        Quantity(np.array([1] * 168, dtype=np.float32), u.cpu_core)), # 7 * 24 = 168 hours in the canonical week
    recurrent_ram_needed=SourceRecurrentValues(
        Quantity(np.array([2] * 168, dtype=np.float32), u.GB_ram)),
    recurrent_storage_needed=SourceRecurrentValues(
        Quantity(np.array([200] * 168, dtype=np.float32), u.kB))
)

# edge functions are analogous to usage journey steps in that they allow the grouping of edge processes that serve the same purpose.
edge_function = EdgeFunction("Edge function", recurrent_edge_device_needs=[edge_process])

edge_usage_journey = EdgeUsageJourney(
    "Edge usage journey",
    edge_functions=[edge_function],
    usage_span=SourceValue(6 * u.year)
)
2025-11-01 15:06:09,600 - INFO - Computing calculated attributes for EdgeUsageJourney Edge usage journey

Define the edge usage pattern

The EdgeUsagePattern object specifies the number of edge devices that are emitted in a given countrey across time, through its hourly_edge_usage_journey_starts parameter. It could be for example the sales projection of a video game console seller. It is then linked to a System through System’s edge_usage_patterns parameter, just like web usage patterns are linked to a System through the usage_patterns parameter. That way, web and edge objects can coexist in the same simulation.

from efootprint.builders.time_builders import create_hourly_usage_from_frequency
from efootprint.core.usage.edge.edge_usage_pattern import EdgeUsagePattern

edge_usage_pattern = EdgeUsagePattern(
    "Edge usage pattern",
    edge_usage_journey=edge_usage_journey,
    country=Countries.FRANCE(),
    hourly_edge_usage_journey_starts=create_hourly_usage_from_frequency(
        timespan=5 * u.year, input_volume=10, frequency='weekly',
        active_days=[0, 1, 2, 3, 4, 5], hours=[9, 10, 11, 12, 15, 16, 17, 18, 19])
)

system.edge_usage_patterns = [edge_usage_pattern]

system.plot_footprints_by_category_and_object("System with edge objects footprints.html")
2025-11-01 15:06:09,636 - INFO - 17 recomputed objects: ['Daily video streaming consumption', 'Edge usage pattern', 'Edge process', 'Edge process RAM need', 'Edge process CPU need', 'Edge process storage need', 'Edge device RAM', 'Edge device CPU', 'Edge SSD storage', 'Edge device', 'fetching web view', 'streaming job', 'upload job', 'WIFI network', 'server', 'SSD storage', 'System']
2025-11-01 15:06:09,650 - INFO - 1 changes lead to 118 update computations done in 15.3 ms (avg 0.13 ms per computation).
system.plot_emission_diffs("Add edge objects.png")
(<Figure size 1000x500 with 3 Axes>,
 <Axes: xlabel='Category', ylabel='tonnes CO2 emissions'>)

png

system.object_relationship_graph_to_file("system_with_edge_objects_graph_up_view.html", width="800px", height="500px",
    classes_to_ignore=USAGE_PATTERN_VIEW_CLASSES_TO_IGNORE, notebook=True)
system_with_edge_objects_graph_up_view.html

system.object_relationship_graph_to_file("system_with_edge_objects_graph_infra_view.html", width="800px", height="500px",
    classes_to_ignore=INFRA_VIEW_CLASSES_TO_IGNORE, notebook=True)
system_with_edge_objects_graph_infra_view.html