Logo Wael's Digital Garden

Arista - Custom Fan Speed Controller

Arista - Custom Fan Speed Controller#

Problem#

The automatic fan speed controller is too conservative which leads to a lot of noise coming out of my switch. For instance, in an ambient temperature of 15℃ the fan is running at 60% which is absurd.

Solution#

The solution is to run a custom Python script that controls the speed of the fans based on the temperature of the system.

Python script#

#!/usr/bin/env python
import json
import logging
import logging.handlers
import time

from EapiClientLib import EapiClient

# --- Configure Logging ---
# Set up a logger to send messages to the local syslog service
logger = logging.getLogger("FanControlScript")
logger.setLevel(logging.INFO)  # Set the minimum level of messages to log
# The SysLogHandler sends logs to the standard '/dev/log' socket
handler = logging.handlers.SysLogHandler(address="/dev/log")
# Add a formatter to make the log messages clean
formatter = logging.Formatter("%(name)s: %(message)s")
handler.setFormatter(formatter)
logger.addHandler(handler)
# --- End Logging Configuration ---


# Use this to connect to the switch's eAPI
switch = EapiClient(disableAaa=True)

# --- Define Your Custom Algorithm Here ---
# Temperature sensor to monitor. Sensor '9' (Switch Bottom Right Inner) is a good choice.
SENSOR_TO_WATCH = "9"

# Define temperature thresholds (in Celsius) and corresponding fan speeds (in percentage).
TEMP_THRESHOLDS = {
    50: 70,  # If temp is over 50C, set fans to 70%
    45: 50,  # If temp is between 45C and 50C, set fans to 50%
    40: 35,  # If temp is between 40C and 45C, set fans to 35%
    0: 30,  # If temp is below 40C, set fans to 30% (base speed)
}
CRITICAL_TEMP = 75
CHECK_INTERVAL = 30


def get_current_temp(sensor_id):
    """Gets the temperature from a specific sensor."""
    try:
        response = switch.runCmds(1, ["show environment temperature"], "json")
        sensors = response["result"][0]["tempSensors"]
        for sensor in sensors:
            if sensor["name"].endswith(sensor_id):
                return sensor["currentTemperature"]
        return None  # Sensor not found
    except Exception as e:
        logger.error("Error getting temperature: %s", e)
        return 99  # Return safe high value on error


def set_fan_speed(speed):
    """Sets the fan speed using a flat list of sequential commands."""
    logger.info("Setting fan speed to %s%%", speed)
    cmd = "environment fan-speed override {}".format(speed)
    try:
        switch.runCmds(1, ["enable", "configure", cmd, "exit"], "text")
    except Exception as e:
        logger.error("Error setting fan speed: %s", e)


if __name__ == "__main__":
    logger.info("Starting custom fan control script...")
    current_speed = 0

    try:
        response = switch.runCmds(1, ["show environment cooling"], "text")[0]["output"]
        for line in response.split("\n"):
            if "Fan speed override mode enabled at" in line:
                current_speed = int(line.split("%")[0].split()[-1])
                logger.info("Initial fan speed detected as %s%%", current_speed)
                break
    except Exception as e:
        logger.warning("Could not detect initial fan speed. Error: %s", e)

    while True:
        temp_raw = get_current_temp(SENSOR_TO_WATCH)

        if temp_raw is None:
            logger.error("Could not find sensor %s. Exiting.", SENSOR_TO_WATCH)
            set_fan_speed(80)  # Set to a safe speed and exit
            break

        temp = int(round(temp_raw))
        logger.debug("Current temperature for sensor %s is %sC", SENSOR_TO_WATCH, temp)

        if temp >= CRITICAL_TEMP:
            logger.critical(
                "CRITICAL TEMPERATURE (%sC) DETECTED! Setting fans to 100%% and exiting.",
                temp,
            )
            set_fan_speed(100)
            break

        target_speed = 0
        for temp_threshold in sorted(TEMP_THRESHOLDS.keys(), reverse=True):
            if temp >= temp_threshold:
                target_speed = TEMP_THRESHOLDS[temp_threshold]
                break

        if target_speed != current_speed:
            set_fan_speed(target_speed)
            current_speed = target_speed
        else:
            # This message is useful for debugging but can be noisy, so we log it at the DEBUG level.
            logger.debug("Temperature is stable. Keeping speed at %s%%.", current_speed)

        time.sleep(CHECK_INTERVAL)

Install the custom fan controller#

Place the Python script within the flash partition#

Save the above script as /mnt/flash/custom_fan_control.py, feel free to use scp or vi directly on the switch.

Install it as an event-handler#

Run the following command within an Arista - Configure Terminal

! Create the event-handler and give it a name
event-handler CUSTOM-FAN-CONTROL
   ! Set the trigger to run after the switch boots up
   trigger on-boot

   ! Define the command to run. We use 'sudo' for permissions,
   ! redirect output to a log file, and use '&' to run it in the background.
   action bash nohup /usr/bin/python /mnt/flash/custom_fan_control.py &

   ! (Optional but Recommended) Add a delay to let the switch fully initialize
   ! before running the script. 300 seconds (5 minutes) is a safe value.
   delay 300
end

Related#