1

I would like to write a short script that gets launched manually once and then should:

  • run a command if all temperatures are below x °C.
  • pause the command if any temperature rises above y °C.
  • continue the command as soon as all temperatures fall below x °C again.

Of course it's x°C < y°C.

I can get the temperature values e.g. through the sensors command:

$ sensors | grep °C
temp1:        +68.0°C  (crit = +95.0°C)
Core 0:       +68.0°C  (high = +80.0°C, crit = +90.0°C)
Core 2:       +67.0°C  (high = +80.0°C, crit = +90.0°C)

The script should be terminable all the time, e.g. through killall scriptname. If that won't work with your solution (e.g. because it will register any scheduled events to poll the temperature), I need a separate command to exit it.

How can I write this script?

2
  • The command, is it called with arguments or straightforward process call? Jan 19, 2016 at 17:12
  • @JacobVlijm It has keyword arguments in the form command -a 1.
    – Byte Commander
    Jan 20, 2016 at 7:52

3 Answers 3

4

This might work:

#!/bin/bash

targettemp=90
started=1

COMMAND &

trap "kill COMMAND; exit" SIGINT SIGTERM

while true
do
  currenttemp=$(sensors -u | awk '/temp1_input/ {print $2; exit}' )
  compare=$(echo $currenttemp'>'$targettemp | bc -l)
  if [ "$compare" -eq 1 ] && [ "$started" -eq 1 ] 
  then
    started=0
    kill -STOP COMMAND
  fi
  if [ "$compare" -eq 0 ] && [ "$started" -eq 0 ]
  then
    started=1
    kill -CONT COMMAND
  fi
  sleep 1 & wait $!
done

This will get the current "temp1" result from sensors, trim out any other characters it doesn't need so that bash can see it as a number, then compare it to whatever target temperature you set.
My reasoning behind the whole appending of "NR+1000" and then grep 1001 is because you might have two "temp1" results in sensors, like I did. It's kind of kludge, but it works.

Then, when you want to kill it, just killall script.sh.

The sleep 1 line is to avoid excess CPU consumption from busy waiting. You can change that to any sleep duration you like if you want to only poll the temperatures every so often.

2

This script will affect the whole process tree rooted at the process run in it.

You can easily test it by running it in a terminal and checking the temperatures in another terminal using watch -n 1 'sensors -u | grep -Po "^ temp[0-9]+_input: \K.*"'.

In this example the thresholds are set to 50 and 75 and the process run is stress -c 3: these are hard coded into the script, but it's easy to modify the script to read them from the arguments.

As soon as all the temperatures will be below 50°C stress will start; as long as all the temperatures will stay below 75°C stress will continue; as soon as one temperature will be above 75°C stress will stop; as soon as all the temperatures will be below 50°C again stress will continue again:

#!/bin/bash
function n_t_exceeding {
    sensors -u | awk -v x=0 -v temp=$1 '$1~/^temp[0-9]+_input:/{if($2 > temp){x++}}END{print x}'
}
set -m # Enables job control
mintemp=50 # First threshold
maxtemp=75 # Second threshold
while true; do
    if [ $(n_t_exceeding $mintemp) -eq 0 ]; then
        stress -c 3 & pid=$! # Starts the process, backgrounds it and stores the process' PID
        printf 'Started\n'
        break
    fi
    sleep 1 & wait $!
done
trap 'pkill -g $pid; exit' 2 # Upon SIGINT, sends SIGTERM to the process group and exits
while true; do
    if [ $(n_t_exceeding $maxtemp) -gt 0 ]; then
        pkill -19 -g $pid # Sends SIGSTOP to the process group
        printf 'Stopped\n'
        while true; do
            if [ $(n_t_exceeding $mintemp) -eq 0 ]; then
                pkill -18 -g $pid # Sends SIGCONT to the process group
                printf 'Resumed\n'
                break
            fi
            sleep 1 & wait $!
        done
    fi
    sleep 1 & wait $!
done
  • function n_t_exceeding { sensors -u | awk -v x=0 -v temp=$1 '$1~/^temp[0-9]+_input:/{if($2 > temp){x++}}END{print x}'; } parses the output of sensors -u and prints the number of temperatures above $1 (which is the first argument passed to the function):

    % sensors -u
    acpitz-virtual-0
    Adapter: Virtual device
    temp1:
      temp1_input: 45.000
      temp1_crit: 108.000
    
    asus-isa-0000
    Adapter: ISA adapter
    cpu_fan:
      fan1_input: 2200.000
    temp1:
      temp1_input: 45.000
    
    coretemp-isa-0000
    Adapter: ISA adapter
    Physical id 0:
      temp1_input: 47.000
      temp1_max: 87.000
      temp1_crit: 105.000
      temp1_crit_alarm: 0.000
    Core 0:
      temp2_input: 46.000
      temp2_max: 87.000
      temp2_crit: 105.000
      temp2_crit_alarm: 0.000
    Core 1:
      temp3_input: 47.000
      temp3_max: 87.000
      temp3_crit: 105.000
      temp3_crit_alarm: 0.000
    
    % sensors -u | awk -v x=0 -v temp=46 '$1~/^temp[0-9]+_input:/{if($2 > temp){x++}}END{print x}'
    2
    
  • set -m enables job control;

  • mintemp=50; maxtemp=75 sets $mintemp and $maxtemp to 50 and 75 respectively; these are the thresholds 1) below which the process should start the first time and continue again after having exceeded $maxtemp 2) above which the process should stop;

  • trap 'pkill -g $pid; exit' 2 makes sure that the script will terminate all the processes in the process group and exit upon CTRL+C;

  • The first while loop idles indefinetly until the number of temperatures over $mintemp is 0; when the number of temperatures over $mintemp is 0 starts the process, puts it into the background and breaks out of the loop;

  • The second while loop idles indefinetly until the number of temperatures over $maxtemp is more than 0; when the number of temperatures over $maxtemp is more than 0 sends a SIGSTOP to the process group and starts a third while loop; the third while loop idles indefinetly until the number of temperatures over $mintemp is 0; when the number of temperatures over $mintemp is 0 sends a SIGCONT to the process group and breaks out of the loop.

8
  • 1
    You have a LOT less plumbing than I do :P
    – user323419
    Jan 19, 2016 at 17:31
  • 1
    @HeatherBrown That's where knowing AWK comes in handy :) You could parse sensors -u instead of sensors in yours as well, it's way easier to parse.
    – kos
    Jan 19, 2016 at 17:39
  • It doesn't work because my command spawns 3 other process instances of itself with different PIDs that don't get paused. :-/ What would be ok is to stop all processes with that name using killall -s STOP commandname (and resume them with killall -s CONT commandname respectively).
    – Byte Commander
    Jan 20, 2016 at 8:30
  • @ByteCommander Updated, it should work for your specific case now. However I'm curious, which command are you running specifically?
    – kos
    Jan 20, 2016 at 10:21
  • @kos It's stress -c 3 for example, which produces 3 threads that keep the CPU busy. It's for some testing where I want to observe how my laptop reacts when running hot (but not too hot).
    – Byte Commander
    Jan 20, 2016 at 12:29
2

The script

#!/usr/bin/env python3
import subprocess
import time
import sys

low = int(sys.argv[1]); high = int(sys.argv[2]); command = sys.argv[3:]; proc = command[0]

def get_temps():
    data = subprocess.check_output("sensors").decode("utf-8").splitlines()
    return sum([[float(l.split(":")[1].split()[0].replace("+", "").replace("°C", "")) \
        for l in data if l.startswith(mark)]for mark in ["temp1", "Core"]], [])

def manage_start():
    try:
        pid = subprocess.check_output(["pgrep", proc]).decode("utf-8").strip()
        subprocess.Popen(["killall", "-s", "CONT", proc])
    except subprocess.CalledProcessError:
        subprocess.Popen(["/bin/bash", "-c", (" ").join(command)])

run = False

while True:
    time.sleep(1)
    if run == False:
        if all([n < low for n in get_temps()]):
            manage_start(); run = True  
    elif run == True:
        if not all([n < high for n in get_temps()]):
            subprocess.Popen(["killall", "-s", "STOP", proc]); run = False

How to use

  1. Copy the script into an empty file, save it as temp_run.py
  2. Run it, best before the process runs (the script will start the process), with (subsequently) the low_temp, the high_temp, the process_name and possible arguments, as arguments. I tested it for example with:

    python3 /path/to/temp_run.py 60 80 gedit /path/to/file.txt
    

    (using another plain text editor to change the numbers)

How I tested it

Since I do not have the variety in real temperature, in the script, I replaced the function get_temps(), by a function, reading numbers from a text file.

Thus "feeding" the script with virtual temperatures, it did the job without an error: pausing above "high" when running, resuming below "low" when paused.

How it works

When the script starts

  • it checks if all temperatures are below the lowest threshold.
  • If so, it either starts the process, or resumes it if it runs already, and sets the variable: run = True
  • As a result, the next test then is if all temps are below the highest threshold, if not, the process is paused, the script sets: run = False, making the next test if all temps are below the lowest and so on...

How to kill it

The script can be killed by:

kill "$(pgrep -f temp_run.py)"
2
  • As I said, I need a version which takes arguments. Yours throws exceptions in the pgrep line. And you probably must use killall commandname instead of kill pid, because my command spawns 3 other instances of itself with different PIDs. See my comment below kos' answer please.
    – Byte Commander
    Jan 20, 2016 at 8:43
  • @ByteCommander As far as I can see (and as far as I could test) the new version above should work. The script now takes arguments and pauses/resumes all instances of the process name. Jan 20, 2016 at 9:24

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .