Search:  
Gentoo Wiki

HARDWARE_Nvidia_Fanspeed_Auto-Adjustment

This article is part of the Hardware series.
Laptops TV Tuner Cards Wireless Servers Storage Other Hardware Motherboards Related

Introduction

Here is a script to control the fanspeed of nvidia video cards using nvclock, to perform regular adjustments for reasonable noise levels, while keeping within the required level of cooling.

To test the script after running it, run a full-screen first-person-shooter game for 10 minutes, and you should hear the fan get louder as its speed increases to cool down the video card. Then, a few minutes after the game is ended, the fan speed will automatically reduce as the video card gets cooler.

Nvidia cards should really control the fanspeed in hardware. This script is basically the Linux equivalent of the Windows programs nTune and SpeedFan.

Disclaimer: It's up to you to test this program on your hardware. nvclock -i will show the status of the video card. If a video card overheats, it will hang the PC, although it is unlikely to do any permanent damage.

Installation

Firstly, check that nvclock is able to read and alter the fanspeed. If your hardware does not allow the fanspeed to be altered, or if nvclock does not support that ability on your video card, then the script will be useless.

emerge nvclock, version >= 0.8b. Try e.g. nvclock -f -F 75 to change the fanspeed to 75%, staying within the range of 70-100%. The best way of confirming the change is by the noise level of the fan noticeably altering. Warning: treat this command with care - video cards can get hot in under a minute, if the fan is turned off or to a very low speed!!!

Ensure that /usr/bin/python is version >= 2.3 - python -V will show the version.

emerge alsa-utils, for the overheating warning sound and player.

Add the following line to:

Code: /etc/conf.d/local.start
/usr/local/bin/nvidiafanspeed &

Save the script in the section below, then run as root:

Code: Final setup
chmod 700 /usr/local/bin/nvidiafanspeed
/usr/local/bin/nvidiafanspeed &

Script

File: /usr/local/bin/nvidiafanspeed
#!/usr/bin/python

"""Controls the fan speed of Nvidia video cards. Runs continually."""

# From [[HARDWARE Nvidia Fanspeed Auto-Adjustment]]
# To see nvidia info:  nvclock -i

import time, commands


def pauseForSeconds(dblSeconds):

    """Waits for the specified number of seconds before continuing."""

    if dblSeconds > 0:
        time.sleep(dblSeconds)


def outputFromCommand(strCmd):

    """Runs the specified command, waits for its completion, and returns the output."""

    strResult = commands.getoutput(strCmd)
    return strResult


def inStr(strWhole, strFind, lngStart = 0, fCaseSensitive = False):

    """The zeroth character is the first.  Otherwise, is same as in VBA.
    Returns -1 if strFind is not in strWhole.
    Note that lngStart parameter is 3rd parameter rather than 1st."""

    if lngStart < 0:
        lngStart = 0

    # Do a quick check to see whether case sensitivity can be turned on, which is faster.
    if not fCaseSensitive and len(strFind) == 1 and not strFind.isalpha():
        fCaseSensitive = True

    if fCaseSensitive:
        return strWhole.find(strFind, lngStart)
    else:
        return strWhole.lower().find(strFind.lower(), lngStart)


def stringBegins(strChars, strCompare, fCaseSensitive = False):

    """Returns whether strChars begins with strCompare."""

    fStringBegins = False               # Default.
    lngLen = len(strCompare)
    if lngLen > 0:
        strStart = leftString(strChars, lngLen)
        fStringBegins = stringsAreSame(strStart, strCompare, fCaseSensitive = fCaseSensitive)

    return fStringBegins


def stringEnds(strChars, strCompare, fCaseSensitive = False):

    """Returns whether strChars ends with strCompare."""

    fStringEnds = False         # Default.
    lngLen = len(strCompare)
    if lngLen > 0:
        strEnd = rightString(strChars, lngLen)
        fStringEnds = stringsAreSame(strEnd, strCompare, fCaseSensitive = fCaseSensitive)

    return fStringEnds


def stringsAreSame(str1, str2, fCaseSensitive = False):

    """Returns whether the strings are the same."""

    if fCaseSensitive:
        return str1 == str2
    else:
        return str1.lower() == str2.lower()


def midString(strChars, lngStart, lngLen = None):

    """The zeroth character is the first."""

    strMid = ''
    if lngStart >= 0 and lngStart < len(strChars):
        if lngLen == None:
            # Return everything from lngStart onwards.
            strMid = strChars[lngStart:]
        else:
            if lngLen >= 1:
                strMid = strChars[lngStart : (lngStart + lngLen)]

    return strMid


def leftString(strChars, lngLen):

    """leftString('ab', 1) returns 'a'."""

    strLeft = ''
    if lngLen >= 1:
        strLeft = strChars[0 : lngLen]

    return strLeft


def rightString(strChars, lngLen):

    """The zeroth character is the first.  Otherwise, is same as in VBA."""

    strRight = ''
    if lngLen >= 1:
        strRight = strChars[-lngLen:]

    return strRight


def rightCharRemoved(strChars):

    return rightCharsRemoved(strChars, 1)


def rightCharsRemoved(strChars, lngChars):

    """Returns the string with the specified number of characters removed from the right-hand side."""

    return (leftString(strChars, len(strChars) - lngChars))


def nvClockBin():

    return '/usr/bin/nvclock'


def main():

    # Pause on startup, otherwise may get error about not being able to open /dev/nvidia0 on bootup.
    pauseForSeconds(20)
    # Set immediately to a reasonably safe speed.
    strCmd = nvClockBin() + ' -f -F 75'
    strOutput = outputFromCommand(strCmd)

    # Run forever.
    while True:
        try:
            strCmd = nvClockBin() + ' -i'
            lngAttempt = 0
            while lngAttempt <= 15:
                lngAttempt += 1
                strBoardLine = ''
                strGPULine = ''
                strDutyCycleLine = ''

                strTempLines = outputFromCommand(strCmd)
                lstTempLines = strTempLines.split('\n')
                for strTempLine in lstTempLines:
                    if stringBegins(strTempLine, 'Board temperature:', fCaseSensitive = True):
                        strBoardLine = strTempLine
                    elif stringBegins(strTempLine, 'GPU temperature:', fCaseSensitive = True):
                        strGPULine = strTempLine
                    elif stringBegins(strTempLine, 'PWM duty cycle:', fCaseSensitive = True) \
                        or (stringBegins(strTempLine, 'Fanspeed:', fCaseSensitive = True) and stringEnds(strTempLine, '%', fCaseSensitive = True)):
                        strDutyCycleLine = strTempLine

                # Don't care about the board temperature, and some cards cannot read it.
                if strBoardLine == '':
                    strBoardLine = strGPULine

                # Sometimes the temperature lines are missing.
                if strBoardLine <> '' and strGPULine <> '' and strDutyCycleLine <> '':
                    strBoardTemp = midString(strBoardLine, inStr(strBoardLine, ':') + 1).strip()
                    strGPUTemp = midString(strGPULine, inStr(strGPULine, ':') + 1).strip()
                    strDutyCyclePerc = midString(strDutyCycleLine, inStr(strDutyCycleLine, ':') + 1).strip()

                    lngBoardTemp = int(rightCharsRemoved(strBoardTemp, 1))
                    lngGPUTemp = int(rightCharsRemoved(strGPUTemp, 1))
                    dblDutyCyclePerc = float(rightCharsRemoved(strDutyCyclePerc, 1))

                    # Sanity-check the figures - required because sometimes the temperatures are wacky.
                    if 30 <= lngBoardTemp <= 110 and 30 <= lngGPUTemp <= 110 and 9 <= dblDutyCyclePerc <= 101:
                        # Ranges are OK.
                        break

            if rightString(strBoardTemp, 1) <> 'C' or rightString(strGPUTemp, 1) <> 'C' or rightString(strDutyCyclePerc, 1) <> '%':
                print 'Video card params wrong: ' + strTempLines

            # The GPU gets hotter than the board.
            if dblDutyCyclePerc >= 96 and lngGPUTemp >= 95:
                print 'Video card is melting - temperature is ' + str(lngGPUTemp)
                # Attempt to alert the user by noise. Noise.wav and aplay are in alsa-utils ebuild.
                for lngAlertLoop in range(5):
                    strAlertOutput = outputFromCommand('/usr/bin/aplay -q /usr/share/sounds/alsa/Noise.wav')
                    pauseForSeconds(1)

            # Calculate desired fan speed based on the temperature.
            dblDutyCyclePercNew = 0
            if lngGPUTemp <= 63:
                dblDutyCyclePercNew = 55
            elif lngGPUTemp <= 70:
                dblDutyCyclePercNew = 60
            elif lngGPUTemp <= 75:
                dblDutyCyclePercNew = 64
            elif lngGPUTemp <= 80:
                dblDutyCyclePercNew = 67
            elif lngGPUTemp <= 85:
                dblDutyCyclePercNew = 75
            elif lngGPUTemp <= 89:
                dblDutyCyclePercNew = 80
            elif lngGPUTemp <= 92:
                dblDutyCyclePercNew = 85
            else:
                # Is overheating - go full speed.
                dblDutyCyclePercNew = 100

            if dblDutyCyclePercNew > 0 and (dblDutyCyclePercNew >= (dblDutyCyclePerc + 1) or dblDutyCyclePercNew <= (dblDutyCyclePerc - 1)):
                # Set new duty cycle.
                strCmd = nvClockBin() + ' -f -F ' + str(int(dblDutyCyclePercNew))
                strOutput = outputFromCommand(strCmd)
                if inStr(strOutput, 'Changing', fCaseSensitive = True) < 0:
                    print 'Could not change video card temperature: ' + strOutput

            # Sanity check on ranges.
            if not (30 <= lngBoardTemp <= 150 and 30 <= lngGPUTemp <= 150 and 9 <= dblDutyCyclePerc <= 101):
                print 'Video card ranges exceeded: ' + strTempLines
        except:
            print 'Video card daemon error: ' + strTempLines
            pauseForSeconds(60)

        # Go to sleep.
        pauseForSeconds(180)

    return 0    # To keep pychecker happy.


if __name__ == '__main__':
    main()
Retrieved from "http://www.gentoo-wiki.info/HARDWARE_Nvidia_Fanspeed_Auto-Adjustment"

Last modified: Mon, 15 Oct 2007 23:44:00 +0000 Hits: 18,218