From 91ea7ebcdfac18137a1f3d1e89d99917ad66d87f Mon Sep 17 00:00:00 2001 From: Sonali Srivastava Date: Wed, 20 Jul 2022 14:47:04 +0530 Subject: [PATCH] sleep: store battery discharge rate/hour with hash Estimated battery discharge rate per hour is stored in : /var/lib/systemd/sleep/battery_discharge_percentage_rate_per_hour This value is used to determine the initial suspend interval. In case this file is not available or value is invalid, HibernateDelaySec interval is used. After wakeup from initial suspend, this value is again estimated and written to file if value is in range of 1-199. Logs for reference : HibernateDelaySec=15min - Updated in /etc/systemd/sleep.conf Jul 14 19:17:58 localhost systemd-sleep[567]: Current battery charge percentage: 100% Jul 14 19:17:58 localhost systemd-sleep[567]: Failed to read discharge rate from /var/lib/systemd/sleep/batt ery_discharge_percentage_rate_per_hour: No such file or directory Jul 14 19:17:58 localhost systemd-sleep[567]: Set timerfd wake alarm for 15min Jul 14 19:33:00 localhost systemd-sleep[567]: Current battery charge percentage after wakeup: 90% Jul 14 19:33:00 localhost systemd-sleep[567]: Attempting to estimate battery discharge rate after wakeup from 15min sleep Jul 14 19:33:00 localhost systemd-sleep[567]: product_id does not exist: No such file or directory Jul 14 19:33:00 localhost systemd-sleep[567]: Estimated discharge rate 39 successfully updated to /var/lib/systemd/sleep/battery_discharge_percentage_rate_per_hour Jul 14 19:33:00 localhost systemd-sleep[567]: Current battery charge percentage: 90% Jul 14 19:33:00 localhost systemd-sleep[567]: product_id does not exist: No such file or directory Jul 14 19:33:00 localhost systemd-sleep[567]: Set timerfd wake alarm for 1h 48min 27s Jul 14 21:21:30 localhost systemd-sleep[567]: Current battery charge percentage after wakeup: 90% Jul 14 21:21:30 localhost systemd-sleep[567]: Battery was not discharged during suspension --- man/systemd-sleep.conf.xml | 23 +++++-- src/shared/sleep-config.c | 131 +++++++++++++++++++++++++++++++++++++ src/shared/sleep-config.h | 2 + src/sleep/sleep.c | 21 +++++- 4 files changed, 170 insertions(+), 7 deletions(-) diff --git a/man/systemd-sleep.conf.xml b/man/systemd-sleep.conf.xml index 0c01ecf3ffc..2f04ce84a15 100644 --- a/man/systemd-sleep.conf.xml +++ b/man/systemd-sleep.conf.xml @@ -78,9 +78,20 @@ suspend-then-hibernate A low power state where the system is initially suspended - (the state is stored in RAM). If not interrupted within the delay specified by - HibernateDelaySec=, the system will be woken using an RTC - alarm and hibernated (the state is then stored on disk). + (the state is stored in RAM) and then hibernated based on battery percentage + capacity. If the current battery capacity is higher than 5%, the system goes + back to suspend for interval calculated using battery disharge rate per hour. + Battery discharge rate per hour is stored in a file which is created after + initial suspend-resume cycle. The value is calculated using battery decreasing + charge level a timespan. In case of manual wakeup before RTC alarm, the timespan + is the duration for which system was suspended. If the file does not exist or + has invalid value, initial suspend duration is set to + HibernateDelaySec=. + After wakeup via an RTC alarm, the battery discharge rate per hour is again + estimated. If the current battery charge level is equal to or less than 5%, + the system will be hibernated (the state is then stored on disk) else the + system goes back to suspend for the amount of time it would take to fully + discharge the battery minus 30 minutes. @@ -173,9 +184,9 @@ HibernateDelaySec= - - The amount of time the system spends in suspend mode before the system is - automatically put into hibernate mode, when using + The amount of time the system spends in suspend mode + before the RTC alarm wakes the system, before the battery discharge rate + can be estimated and used instead to calculate the suspension interval. systemd-suspend-then-hibernate.service8. Defaults to 2h. diff --git a/src/shared/sleep-config.c b/src/shared/sleep-config.c index 980cd5f7a05..b18f6bf3f48 100644 --- a/src/shared/sleep-config.c +++ b/src/shared/sleep-config.c @@ -25,10 +25,13 @@ #include "errno-util.h" #include "fd-util.h" #include "fileio.h" +#include "hexdecoct.h" +#include "id128-util.h" #include "log.h" #include "macro.h" #include "path-util.h" #include "sleep-config.h" +#include "siphash24.h" #include "stat-util.h" #include "stdio-util.h" #include "string-table.h" @@ -36,6 +39,9 @@ #include "strv.h" #include "time-util.h" +#define DISCHARGE_RATE_FILEPATH "/var/lib/systemd/sleep/battery_discharge_percentage_rate_per_hour" +#define BATTERY_DISCHARGE_RATE_HASH_KEY SD_ID128_MAKE(5f,9a,20,18,38,76,46,07,8d,36,58,0b,bb,c4,e0,63) + int parse_sleep_config(SleepConfig **ret_sleep_config) { _cleanup_(free_sleep_configp) SleepConfig *sc = NULL; int allow_suspend = -1, allow_hibernate = -1, @@ -140,6 +146,131 @@ int read_battery_capacity_percentage(void) { return battery_capacity; } +/* Read file path and return hash of value in that file */ +static int get_battery_identifier(const char *filepath, struct siphash *ret) { + _cleanup_free_ char *value = NULL; + int r; + + assert(filepath); + assert(ret); + + r = read_one_line_file(filepath, &value); + if (r == -ENOENT) + log_debug_errno(r, "%s is unavailable: %m", filepath); + else if (r < 0) + return log_debug_errno(r, "Failed to read %s: %m", filepath); + else if (isempty(value)) + log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "%s is empty: %m", filepath); + else + siphash24_compress_string(value, ret); + + return 0; +} + +/* Read system and battery identifier from specific location and generate hash of it */ +static int get_system_battery_identifier_hash(uint64_t *ret_hash) { + struct siphash state; + sd_id128_t machine_id, product_id; + int r; + + assert(ret_hash); + + siphash24_init(&state, BATTERY_DISCHARGE_RATE_HASH_KEY.bytes); + get_battery_identifier("/sys/class/power_supply/BAT0/manufacturer", &state); + get_battery_identifier("/sys/class/power_supply/BAT0/model_name", &state); + get_battery_identifier("/sys/class/power_supply/BAT0/serial_number", &state); + + r = sd_id128_get_machine(&machine_id); + if (r == -ENOENT) + log_debug_errno(r, "machine ID is unavailable: %m"); + else if (r < 0) + return log_debug_errno(r, "Failed to get machine ID: %m"); + else + siphash24_compress(&machine_id, sizeof(sd_id128_t), &state); + + r = id128_get_product(&product_id); + if (r == -ENOENT) + log_debug_errno(r, "product_id does not exist: %m"); + else if (r < 0) + return log_debug_errno(r, "Failed to get product ID: %m"); + else + siphash24_compress(&product_id, sizeof(sd_id128_t), &state); + + *ret_hash = siphash24_finalize(&state); + + return 0; +} + +/* battery percentage discharge rate per hour is in range 1-199 then return success */ +static bool battery_discharge_rate_is_valid(int battery_discharge_rate) { + return battery_discharge_rate > 0 && battery_discharge_rate < 200; +} + +/* Battery percentage discharge rate per hour is read from specific file. It is stored along with system + * and battery identifier hash to maintain the integrity of discharge rate value */ +int get_battery_discharge_rate(void) { + _cleanup_free_ char *hash_id_discharge_rate = NULL, *stored_hash_id = NULL, *stored_discharge_rate = NULL; + const char *p; + uint64_t current_hash_id, hash_id; + int discharge_rate, r; + + r = read_one_line_file(DISCHARGE_RATE_FILEPATH, &hash_id_discharge_rate); + if (r < 0) + return log_debug_errno(r, "Failed to read discharge rate from %s: %m", DISCHARGE_RATE_FILEPATH); + + p = hash_id_discharge_rate; + r = extract_many_words(&p, " ", 0, &stored_hash_id, &stored_discharge_rate, NULL); + if (r < 0) + return log_debug_errno(r, "Failed to parse hash_id and discharge_rate read from %s location: %m", DISCHARGE_RATE_FILEPATH); + if (r != 2) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid number of items fetched from %s", DISCHARGE_RATE_FILEPATH); + + r = safe_atou64(stored_hash_id, &hash_id); + if (r < 0) + return log_debug_errno(r, "Failed to parse discharge rate read from %s location: %m", DISCHARGE_RATE_FILEPATH); + + r = get_system_battery_identifier_hash(¤t_hash_id); + if (r < 0) + return log_debug_errno(r, "Failed to generate system battery identifier hash: %m"); + + if(current_hash_id != hash_id) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Current identifier does not match stored identifier: %m"); + + r = safe_atoi(stored_discharge_rate, &discharge_rate); + if (r < 0) + return log_debug_errno(r, "Failed to parse discharge rate read from %s location: %m", DISCHARGE_RATE_FILEPATH); + + if (!battery_discharge_rate_is_valid(discharge_rate)) + return log_debug_errno(SYNTHETIC_ERRNO(ERANGE), "Invalid battery discharge percentage rate per hour: %m"); + + return discharge_rate; +} + +/* Write battery percentage discharge rate per hour along with system and battery identifier hash to file */ +int put_battery_discharge_rate(int estimated_battery_discharge_rate) { + uint64_t system_hash_id; + int r; + + if (!battery_discharge_rate_is_valid(estimated_battery_discharge_rate)) + return log_debug_errno(SYNTHETIC_ERRNO(ERANGE), "Invalid battery discharge percentage rate per hour: %m"); + + r = get_system_battery_identifier_hash(&system_hash_id); + if (r < 0) + return log_debug_errno(r, "Failed to generate system battery identifier hash: %m"); + + r = write_string_filef( + DISCHARGE_RATE_FILEPATH, + WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_MKDIR_0755, + "%"PRIu64" %d", + system_hash_id, + estimated_battery_discharge_rate); + if (r < 0) + return log_debug_errno(r, "Failed to create %s: %m", DISCHARGE_RATE_FILEPATH); + + log_debug("Estimated discharge rate %d successfully updated to %s", estimated_battery_discharge_rate, DISCHARGE_RATE_FILEPATH); + return 0; +} + int can_sleep_state(char **types) { _cleanup_free_ char *text = NULL; int r; diff --git a/src/shared/sleep-config.h b/src/shared/sleep-config.h index 39d952b9549..a48cf17a54e 100644 --- a/src/shared/sleep-config.h +++ b/src/shared/sleep-config.h @@ -57,6 +57,8 @@ int can_sleep_disk(char **types); int can_sleep_state(char **types); int read_battery_capacity_percentage(void); int battery_is_low(void); +int get_battery_discharge_rate(void); +int put_battery_discharge_rate(int estimated_battery_discharge_rate); const char* sleep_operation_to_string(SleepOperation s) _const_; SleepOperation sleep_operation_from_string(const char *s) _pure_; diff --git a/src/sleep/sleep.c b/src/sleep/sleep.c index 8e36933de23..a1905dbc275 100644 --- a/src/sleep/sleep.c +++ b/src/sleep/sleep.c @@ -272,7 +272,7 @@ static int execute_s2h(const SleepConfig *sleep_config) { struct itimerspec ts = {}; usec_t suspend_interval = sleep_config->hibernate_delay_sec, before_timestamp = 0, after_timestamp = 0; bool woken_by_timer; - int last_capacity = 0, current_capacity = 0, estimated_discharge_rate = 0; + int last_capacity = 0, current_capacity = 0, previous_discharge_rate, estimated_discharge_rate = 0; tfd = timerfd_create(CLOCK_BOOTTIME_ALARM, TFD_NONBLOCK|TFD_CLOEXEC); if (tfd < 0) @@ -290,6 +290,22 @@ static int execute_s2h(const SleepConfig *sleep_config) { else return log_error_errno(r, "Error fetching battery capacity percentage: %m"); + r = get_battery_discharge_rate(); + if (r < 0) + log_full_errno(r == -ENOENT ? LOG_DEBUG : LOG_WARNING, r, "Failed to get discharge rate, ignoring: %m"); + else if (last_capacity * 2 <= r) { + /* System should hibernate in case discharge rate is higher than double of battery current capacity + * why double : Because while calculating suspend interval, we have taken a buffer of 30 minute and + * discharge_rate is calculated on per 60 minute basis which is double. Also suspend_interval > 0 */ + log_debug("Current battery percentage capacity too low to suspend, so invoking hibernation"); + break; + } else { + previous_discharge_rate = r; + assert(previous_discharge_rate != 0); + suspend_interval = usec_sub_unsigned(last_capacity * USEC_PER_HOUR / previous_discharge_rate, 30 * USEC_PER_MINUTE); + /* The previous discharge rate is stored in per hour basis so converted to minutes. + * Substracted 30 minutes from the result to keep a buffer of 30 minutes before battery gets critical */ + } log_debug("Set timerfd wake alarm for %s", FORMAT_TIMESPAN(suspend_interval, USEC_PER_SEC)); /* Wake alarm for system with or without battery to hibernate or estimate discharge rate whichever is applicable */ timespec_store(&ts.it_value, suspend_interval); @@ -325,6 +341,9 @@ static int execute_s2h(const SleepConfig *sleep_config) { log_debug("Attempting to estimate battery discharge rate after wakeup from %s sleep", FORMAT_TIMESPAN(after_timestamp - before_timestamp, USEC_PER_HOUR)); estimated_discharge_rate = (last_capacity - current_capacity) * USEC_PER_HOUR / (after_timestamp - before_timestamp); + r = put_battery_discharge_rate(estimated_discharge_rate); + if (r < 0) + log_warning_errno(r, "Failed to update battery discharge rate, ignoring: %m"); } if (!woken_by_timer) -- 2.39.2