Ubuntu: How to go automatically from Suspend into Hibernate?



Question:

Is it possible to make Ubuntu go into Hibernate state from Suspend, aka "Suspend Sedation"?

For example, my laptop is set up to go into a Suspend once I close the lid. If then I don't use it for entire day, the battery goes flat, because even in suspend mode the hardware still consumes a small amount of power, and the battery eventually discharges. What I want is to be able to tell Ubuntu that even if it is suspended, it still needs to go into Hibernate after some hours of inactivity.

Windows can do that. Ubuntu can be programmed to go into Standby or Hibernate on timer, but not both.

Update:

I guess I need to be more specific. What I am looking for is this:
When I close the lid, the laptop is put into Suspend. Then, after a pre-determined time (even if the battery is going strong) if I still don't use it, it should put itself into a Hibernate to save battery power.


Solution:1

The solution to this is simple. First, upon suspend and resume, the pm-suspend program executes a series of scripts in /etc/pm/sleep.d and /usr/lib/pm-utils/sleep.d. So my solution is to add a script that does the following:

  1. Upon suspend, record the current time and register a wakeup event using rtcwake.
  2. Upon resume,check the current time against the recorded time from above. If enough time has elapsed, then we probably woke up due to the rtc timer event. Otherwise we woke up early due to a user event (such as opening the laptop screen).
  3. If we woke up due to the rtc timer, then immediately issue a "pm-hibernate" command to go into hibernation.

Here is a script that does this. Name it 0000rtchibernate and place it in the /etc/pm/sleep.d directory (the 0000 is important, so that the script executes first on suspend, and last on resume).

    #!/bin/bash  # Script name: /etc/pm/sleep.d/0000rtchibernate  # Purpose: Auto hibernates after a period of sleep  # Edit the "autohibernate" variable below to set the number of seconds to sleep.  curtime=$(date +%s)  autohibernate=7200  echo "$curtime $1" >>/tmp/autohibernate.log  if [ "$1" = "suspend" ]  then      # Suspending.  Record current time, and set a wake up timer.      echo "$curtime" >/var/run/pm-utils/locks/rtchibernate.lock      rtcwake -m no -s $autohibernate  fi    if [ "$1" = "resume" ]  then      # Coming out of sleep      sustime=$(cat /var/run/pm-utils/locks/rtchibernate.lock)      rm /var/run/pm-utils/locks/rtchibernate.lock      # Did we wake up due to the rtc timer above?      if [ $(($curtime - $sustime)) -ge $autohibernate ]      then          # Then hibernate          rm /var/run/pm-utils/locks/pm-suspend.lock          /usr/sbin/pm-hibernate      else          # Otherwise cancel the rtc timer and wake up normally.          rtcwake -m no -s 1      fi  fi  

Hopefully this code comes through on this message board (this is my first post here).

Edit the timeout value "autohibernate=7200" at the top, to however many seconds you which to sleep before going into hibernation. The current value above is 2 hours. Note, that you laptop WILL wake up at that time for a few seconds, while it is executing the hibernate function.

So if you plan on putting your laptop in a case, don't suspend, but hibernate instead. Otherwise your laptop could overheat in esp. if it is in a tight fitting slip case (although it will only be on for a few seconds to a minute).

I've been using this method for the past couple of days, so far it has been successful (and saved me from a dead battery this afternoon). Enjoy.

** Update ** For other Linux distributions that use systemd, this should still work if you place the script in /usr/lib/systemd/system-sleep instead of /etc/pm/sleep.d. Also, replace the "/usr/sbin/pm-hibernate" command with "systemctl hibernate".


Solution:2

To explain how this works (this is similar to Windows) in simple words: the machine doesn't wake up from standby when battery gets low to be able to save the machine state to the swap partition, it saves everything to the swap partition immediately on standby, and when the battery runs out, it will recover from that by loading the state from the swap partition (as it would do in case you hibernated).

AFAIK linux will/should use hybrid standby/hibernate instead of "normal" standby if it knows that it works for your hardware. It's also possible that this is disabled currently because of too many bugs or something... ;)

If you like experimenting, maybe you can see if you can get any good results with pm-suspend-hybrid.

If the following says you're lucky, then in theory hybrid suspend is supported on your system:

pm-is-supported --suspend-hybrid && echo "you're lucky"  


Solution:3

You may be interested in s2both. It is provided by the package uswsusp in Ubuntu 10.10. It suspends to disk, but instead of shutting down the system instead puts it in S3, which is the power mode usually associated with the "Suspend" option in Ubuntu. pm-suspend-hybrid is another tool that purports to do the same thing.

To make this automated on lid close, take a look at the following guide which allows you to run an arbitrary script when a lid event is caught:

http://ubuntuforums.org/showthread.php?t=1076486

If you happen to have a ThinkPad, the manpage for tpctl makes reference to an argument, --pm-sedation-hibernate-from-suspend-timer, which seems to provide the feature you're looking for. I would caution you against trying this on non-ThinkPad hardware.

For reference, I looked through the manpage for hibernate.conf; it didn't seem to have any relevant options but might be worth a second reading.


Solution:4

Just in case something goes wrong during pm-hibernate i'd rather put the computer to suspend than let it run. So you can use:

   ...  /usr/sbin/pm-hibernate || /usr/sbin/pm-suspend     ...  


Solution:5

Here's an updated version of Derek Pressnall's answer that works with systemd and includes Eliah Kagan's suggestion, just drop it in /usr/lib/systemd/system-sleep/delayed_hibernation.sh and make it executable:

#!/bin/bash    hibernation_timeout=1800  #30 minutes    if [ "$2" = "suspend" ]; then      curtime=$(date +%s)      if [ "$1" = "pre" ]; then          echo -e "[($curtime) $@]\nExecuting pre-suspend hook..." >> /tmp/delayed_hibernation.log          echo "$curtime" > /var/run/delayed_hibernation.lock          rtcwake -m no -s $hibernation_timeout      elif [ "$1" = "post" ]; then          echo -e "[($curtime) $@]\nExecuting post-suspend hook..." >> /tmp/delayed_hibernation.log          sustime=$(cat /var/run/delayed_hibernation.lock)          if [ $(($curtime - $sustime)) -ge $hibernation_timeout ]; then              echo -e "Automatic resume detected, hibernating.\n" >> /tmp/delayed_hibernation.log              systemctl hibernate || systemctl suspend          else              echo -e "Manual resume detected, clearing RTC alarm.\n" >> /tmp/delayed_hibernation.log              rtcwake -m no -s 1          fi          rm /var/run/delayed_hibernation.lock      fi  fi  


Solution:6

Ubuntu 16.04 - from suspend/sleep into hibernate after a pre-determined time

It seems that on Ubuntu 16.04 things are a little different, so steps I took to make it work were:

  1. Make sure hibernate is working as expected when running

    systemctl hibernate  
  2. Copy the original suspend.target file:

    sudo cp /lib/systemd/system/suspend.target /etc/systemd/system/suspend.target  

    Then edit the file /etc/systemd/system/suspend.target and add the line:

    Requires=delayed-hibernation.service  

    to the [Unit] section of that file.

  3. Create the file /etc/systemd/system/delayed-hibernation.service with the following content:

  [Unit]  Description=Delayed hibernation trigger  Before=suspend.target  Conflicts=hibernate.target hybrid-suspend.target  StopWhenUnneeded=true    [Service]  Type=oneshot  RemainAfterExit=yes  ExecStart=/usr/local/bin/delayed-hibernation.sh pre suspend  ExecStop=/usr/local/bin/delayed-hibernation.sh post suspend    [Install]  WantedBy=sleep.target  
  1. Create the configuration file /etc/delayed-hibernation.conf for the script with the following content:
  # Configuration file for 'delayed-hibernation.sh' script    # Specify the time in seconds to spend in sleep mode before the computer hibernates  TIMEOUT=1200  #in seconds, gives 20 minutes  
  1. Create the script which will actually does the hard work.

    Create file /usr/local/bin/delayed-hibernation.sh with the content:

  #!/bin/bash  # Script name: delayed-hibernation.sh  # Purpose: Auto hibernates after a period of sleep  # Edit the `TIMEOUT` variable in the `$hibernation_conf` file to set the number of seconds to sleep.    hibernation_lock='/var/run/delayed-hibernation.lock'  hibernation_fail='/var/run/delayed-hibernation.fail'  hibernation_conf='/etc/delayed-hibernation.conf'    # Checking the configuration file  if [ ! -f $hibernation_conf ]; then      echo "Missing configuration file ('$hibernation_conf'), aborting."      exit 1  fi  hibernation_timeout=$(grep "^[^#]" $hibernation_conf | grep "TIMEOUT=" | awk -F'=' '{ print $2 }' | awk -F'#' '{print $1}' | tr -d '[[ \t]]')  if [ "$hibernation_timeout" = "" ]; then      echo "Missing 'TIMEOUT' parameter from configuration file ('$hibernation_conf'), aborting."      exit 1  elif [[ ! "$hibernation_timeout" =~ ^[0-9]+$ ]]; then      echo "Bad 'TIMEOUT' parameter ('$hibernation_timeout') in configuration file ('$hibernation_conf'), expected number of seconds, aborting."      exit 1  fi    # Processing given parameters  if [ "$2" = "suspend" ]; then      curtime=$(date +%s)      if [ "$1" = "pre" ]; then          if [ -f $hibernation_fail ]; then              echo "Failed hibernation detected, skipping setting RTC wakeup timer."          else              echo "Suspend detected. Recording time, set RTC timer"              echo "$curtime" > $hibernation_lock              rtcwake -m no -s $hibernation_timeout          fi      elif [ "$1" = "post" ]; then          if [ -f $hibernation_fail ]; then              rm $hibernation_fail          fi          if [ -f $hibernation_lock ]; then              sustime=$(cat $hibernation_lock)              rm $hibernation_lock              if [ $(($curtime - $sustime)) -ge $hibernation_timeout ]; then                  echo "Automatic resume from suspend detected. Hibernating..."                  systemctl hibernate                  if [ $? -ne 0 ]; then                      echo "Automatic hibernation failed. Trying to suspend instead."                      touch $hibernation_fail                      systemctl suspend                      if [ $? -ne 0 ]; then                          echo "Automatic hibernation and suspend failover failed. Nothing else to try."                      fi                  fi              else                  echo "Manual resume from suspend detected. Clearing RTC timer"                  rtcwake -m disable              fi          else              echo "File '$hibernation_lock' was not found, nothing to do"          fi      else          echo "Unrecognised first parameter: '$1', expected 'pre' or 'post'"      fi  else      echo "This script is intended to be run by systemctl delayed-hibernation.service (expected second parameter: 'suspend')"  fi  
  1. Make the script executable:
  chmod 755 /usr/local/bin/delayed-hibernation.sh  

It took me quite a lot until writing this script based on other replies in this thread, things I found on the internet like https://bbs.archlinux.org/viewtopic.php?pid=1554259

My version of the script tries to deal with many problems like go into suspend again if hibernate was not successful but do not wake again after the pre-determined time over and over.

  1. Final step I assume would be to just execute

    sudo systemctl daemon-reload  sudo systemctl enable delayed-hibernation.service   

    to make sure new service/configurations are being used.

To check the service log, you can use:

sudo systemctl status delayed-hibernation.service

or for a complete log of the service use:

sudo journalctl -u delayed-hibernation.service

A normal log I get from the running service is:

  mile@mile-ThinkPad:~$ sudo systemctl status delayed-hibernation.service   ● delayed-hibernation.service - Delayed hibernation trigger     Loaded: loaded (/etc/systemd/system/delayed-hibernation.service; enabled; vendor preset: enabled)     Active: inactive (dead)    Jun 09 20:35:42 mile-ThinkPad systemd[1]: Starting Delayed hibernation trigger...  Jun 09 20:35:42 mile-ThinkPad delayed-hibernation.sh[2933]: Suspend detected. Recording time, set RTC timer  Jun 09 20:35:42 mile-ThinkPad delayed-hibernation.sh[2933]: rtcwake: assuming RTC uses UTC ...  Jun 09 20:35:42 mile-ThinkPad delayed-hibernation.sh[2933]: rtcwake: wakeup using /dev/rtc0 at Thu Jun  9 18:55:43 2016  Jun 09 20:55:44 mile-ThinkPad systemd[1]: Started Delayed hibernation trigger.  Jun 09 20:55:44 mile-ThinkPad systemd[1]: delayed-hibernation.service: Unit not needed anymore. Stopping.  Jun 09 20:55:44 mile-ThinkPad systemd[1]: Stopping Delayed hibernation trigger...  Jun 09 20:55:44 mile-ThinkPad delayed-hibernation.sh[3093]: Automatic resume from suspend detected. Hibernating...  Jun 09 20:55:44 mile-ThinkPad systemd[1]: Stopped Delayed hibernation trigger.  mile@mile-ThinkPad:~$   

So This would be it, I hope it really helps someone since I spent days trying to figure out the right combination of configurations and script versions to make this handy feature work.


Solution:7

Here is my recipe (tested it on two notebooks Ubuntu 16.04):

Put this script whereever you like (I put it to root, /syspend.sh) and make it executable (chmod +x /suspend.sh)

TIMELOG=/tmp/autohibernate.log  ALARM=$(tail -n 1 $TIMELOG)  SLEEPTIME=5000 #edit this line to change timer, e.g. 2 hours "$((2*60*60))"  if [[ $1 == "resume" ]]  then      if [[ $(date +%s) -ge $(( $ALARM + $SLEEPTIME )) ]]      then          echo "hibernate triggered $(date +%H:%M:%S)">>$TIMELOG          systemctl hibernate 2>> $TIMELOG      else          echo "normal wakeup $(date +%H:%M:%S)">>$TIMELOG      fi  elif [[ $1 == "suspend" ]]  then      echo "$(date +%s)" >> $TIMELOG      rtcwake -m no -s $SLEEPTIME  fi  

Then create systemd target: # touch /etc/systemd/system/suspend-to-sleep.target Paste this content:

#/etc/systemd/system/suspend-to-hibernate.service  [Unit]  Description=Delayed hibernation trigger  Before=suspend.target  Conflicts=hibernate.target hybrid-suspend.target  StopWhenUnneeded=true    [Service]  Type=oneshot  RemainAfterExit=yes  ExecStart=/bin/bash /suspend.sh suspend  ExecStop=/bin/bash /suspend.sh wakeup    [Install]  WantedBy=sleep.target  RequiredBy=suspend.target  

Then enable it # systemctl enable suspend-to-sleep.target.

I've faced an issue on the one of notebooks: closing lid didn't trigger this target. This was due to xfce4-power-manager. There are two ways to workaround this problem. The first one is to edit /etc/systemd/logind.conf file and replace HandleLidSwitch=ignore with HandleLidSwitch=suspend. But it will be systemwide, so I just added symlink to my script # ln -s /suspend.sh /etc/pm/sleep.d/0000rtchibernate


Solution:8

Another more common workaround you can use hybrid-sleep (like the Mac OS does). If your computer supports hibernation, you can use this feature:

systemctl hybrid-sleep  

That command should suspend and send to disk (hibernate) the computer. After some time the computer will turn off (when turning on, it will use the hibernation files to wake up).

p.s.: I know it's not exactly what the OP posted, but it's fairly close


Solution:9

Don't forget to chmod +x that file, make it executable.

There's another solution without rtcwake, using wakealarm in /sys/class/rtc/rtc0. Make use obsolete code in pm-functions (/usr/lib/pm-utils) after the comments #since the kernel does not directly support ... , ('cos the current kernel (after 3.6 something) does directly support). Revert that code and put in do_suspend() part instead of do_suspend_hybrid().

Obsolete code (suspend then hibernate when suspend_hybrid is called):

# since the kernel does not directly support hybrid sleep, we do  # something else -- suspend and schedule an alarm to go into  # hibernate if we have slept long enough.  # Only do this if we do not need to do any special video hackery on resume  # from hibernate, though.  if [ -z "$SUSPEND_HYBRID_MODULE" -a -w "$PM_RTC/wakealarm" ] && \      check_suspend && check_hibernate && ! is_set $HIBERNATE_RESUME_POST_VIDEO; \      then      SUSPEND_HYBRID_MODULE="kernel"      do_suspend_hybrid() {      WAKETIME=$(( $(cat "$PM_RTC/since_epoch") + PM_HIBERNATE_DELAY))      echo >"$PM_RTC/wakealarm"      echo $WAKETIME > "$PM_RTC/wakealarm"      if do_suspend; then          NOW=$(cat "$PM_RTC/since_epoch")          if [ "$NOW" -ge "$WAKETIME" -a "$NOW" -lt $((WAKETIME + 30)) ]; then          log "Woken by RTC alarm, hibernating."          # if hibernate fails for any reason, go back to suspend.          do_hibernate || do_suspend          else          echo > "$PM_RTC/wakealarm"          fi      else          # if we cannot suspend, just try to hibernate.          do_hibernate      fi      }  fi  

Recommended. Even easier to use uswsusp while the same time maximize the benefit of s2both i.e. s2both when suspend. Put the reverted code in do_suspend() part of uswsusp module (/usr/lib/pm-utils/module.d).

Reverted code (suspend_hybrid when suspend is called):

WAKETIME=$(( $(cat "$PM_RTC/since_epoch") + PM_HIBERNATE_DELAY))  echo >"$PM_RTC/wakealarm"  echo $WAKETIME > "$PM_RTC/wakealarm"  if do_suspend_hybrid; then      NOW=$(cat "$PM_RTC/since_epoch")      if [ "$NOW" -ge "$WAKETIME" -a "$NOW" -lt $((WAKETIME + 30)) ];             then      log "Woken by RTC alarm, hibernating."      # if hibernate fails for any reason, go back to suspend_hybrid.      do_hibernate || do_suspend_hybrid      else      echo > "$PM_RTC/wakealarm"      fi  else      # when do_suspend is being called, convert to suspend_hybrid.      do_suspend_hybrid  fi        

With uswsusp, we can see the progress of suspend/hibernate and the reverse process displayed in text, even we can abort it by pressing backspace. Without uswsusp, suspend/hibernate just appear-disappear annoyingly, especially when wakealarm is triggered and execute hibernate (s2disk in uswsusp). Set the period of sleep before hibernate in the usual place on pm-functions file.

# variables to handle hibernate after suspend support  PM_HIBERNATE_DELAY=900  # 15 minutes  PM_RTC=/sys/class/rtc/rtc0  

Here's the uswsusp mod: (remember, this module is called from pm-functions so the inserted variables are the same)

#!/bin/sh    # disable processing of 90chvt and 99video.  # s2ram and s2disk handle all this stuff internally.  uswsusp_hooks()  {      disablehook 99video "disabled by uswsusp"  }    # Since we disabled 99video, we need to take responsibility for proper  # quirk handling.  s2ram handles all common video quirks internally,  # so all we have to do is translate the HAL standard options to s2ram options.  uswsusp_get_quirks()  {      OPTS=""      ACPI_SLEEP=0      for opt in $PM_CMDLINE; do          case "${opt##--quirk-}" in # just quirks, please              dpms-on)       ;; # no-op              dpms-suspend)      ;; # no-op              radeon-off)        OPTS="$OPTS --radeontool" ;;              reset-brightness)  ;; # no-op              s3-bios)       ACPI_SLEEP=$(($ACPI_SLEEP + 1)) ;;              s3-mode)       ACPI_SLEEP=$(($ACPI_SLEEP + 2)) ;;              vbe-post)      OPTS="$OPTS --vbe_post" ;;              vbemode-restore)   OPTS="$OPTS --vbe_mode" ;;              vbestate-restore)  OPTS="$OPTS --vbe_save" ;;              vga-mode-3)        ;; # no-op              save-pci)          OPTS="$OPTS --pci_save" ;;              none)          QUIRK_NONE="true" ;;              *) continue ;;          esac      done      [ $ACPI_SLEEP -ne 0 ] && OPTS="$OPTS --acpi_sleep $ACPI_SLEEP"      # if we were told to ignore quirks, do so.      # This is arguably not the best way to do things, but...      [ "$QUIRK_NONE" = "true" ] && OPTS=""  }    # Since we disabled 99video, we also need to handle displaying  # help info for the quirks we handle.  uswsusp_help()  {      echo  # first echo makes it look nicer.      echo "s2ram video quirk handler options:"      echo      echo "  --quirk-radeon-off"      echo "  --quirk-s3-bios"      echo "  --quirk-s3-mode"      echo "  --quirk-vbe-post"      echo "  --quirk-vbemode-restore"      echo "  --quirk-vbestate-restore"      echo "  --quirk-save-pci"      echo "  --quirk-none"  }    # This idiom is used for all sleep methods.  Only declare the actual  # do_ method if:  # 1: some other sleep module has not already done so, and  # 2: this sleep method can actually work on this system.  #  # For suspend, if SUSPEND_MODULE is set then something else has already  # implemented do_suspend.  We could just check to see of do_suspend was  # already declared using command_exists, but using a dedicated environment  # variable makes it easier to debug when we have to know what sleep module  # ended up claiming ownership of a given sleep method.  if [ -z "$SUSPEND_MODULE" ] && command_exists s2ram && \      ( grep -q mem /sys/power/state || \          ( [ -c /dev/pmu ] && check_suspend_pmu; ); ); then      SUSPEND_MODULE="uswsusp"      do_suspend()      {          WAKETIME=$(( $(cat "$PM_RTC/since_epoch") + PM_HIBERNATE_DELAY))          echo >"$PM_RTC/wakealarm"          echo $WAKETIME > "$PM_RTC/wakealarm"          if do_suspend_hybrid; then              NOW=$(cat "$PM_RTC/since_epoch")              if [ "$NOW" -ge "$WAKETIME" -a "$NOW" -lt $((WAKETIME + 30)) ];             then              log "Woken by RTC alarm, hibernating."              # if hibernate fails for any reason, go back to suspend_hybrid.              do_hibernate || do_suspend_hybrid              else              echo > "$PM_RTC/wakealarm"              fi          else              # when do_suspend is being called, convert to suspend_hybrid.              do_suspend_hybrid          fi            }  fi    if [ -z "$HIBERNATE_MODULE" ] && \      [ -f /sys/power/disk ] && \      grep -q disk /sys/power/state && \      [ -c /dev/snapshot ] &&      command_exists s2disk; then      HIBERNATE_MODULE="uswsusp"      do_hibernate()      {          s2disk      }  fi    if [ -z "$SUSPEND_HYBRID_MODULE" ] &&       grep -q mem /sys/power/state && \      command_exists s2both && \      check_hibernate; then      SUSPEND_HYBRID_MODULE="uswsusp"      do_suspend_hybrid()      {             uswsusp_get_quirks          s2both --force $OPTS       }      if [ "$METHOD" = "suspend_hybrid" ]; then          add_before_hooks uswsusp_hooks          add_module_help uswsusp_help      fi  fi    

Note:If u also have question or solution just comment us below or mail us on toontricks1994@gmail.com
Previous
Next Post »