5

Is there any easy way to calculate uptime excluding period system is suspended? I would like to count time I've spent in front of computer.

  • 3
    var/log/pm-suspend.log, will contain the last awake time ,using this ,uptime and current time you can manually find real uptime with subtraction.Guess this is not the easy way!! – Stormvirux Jul 19 '13 at 12:38
  • @Stormvirux I can write script but it is the last resort – teodozjan Jul 19 '13 at 13:22
  • 1
    @Stormvirux I had the same idea, but wish I had found your suggestion first instead of figuring that out six years later! – WinEunuuchs2Unix Nov 7 '19 at 2:01
  • The next time you sign on, let us know how you eventually ended up solving this question. Thanks. – WinEunuuchs2Unix May 3 at 22:33
6

On one hand this answer is six years late, on the other hand it's a blink of the eye if the internet is eternal!

You can get the real uptime with this little bash script:

$ suspendtime

Apr 07 05:53:34 to Apr 07 17:07:17 suspended 11 hours, 13 minutes, 43 seconds
Apr 07 21:56:34 to Apr 08 04:20:57 suspended 6 hours, 24 minutes, 23 seconds
      (... SNIP ...)
May 08 05:55:20 to May 08 16:32:37 suspended 10 hours, 37 minutes, 17 seconds
May 08 23:21:00 to May 09 07:02:05 suspended 7 hours, 41 minutes, 5 seconds

Linux uptime 2,813,939 seconds (4 weeks, 4 days, 13 hours, 38 minutes, 59 seconds)
64 Suspends 1,715,196 seconds (2 weeks, 5 days, 20 hours, 26 minutes, 36 seconds)
Real uptime 1,098,743 seconds (1 week, 5 days, 17 hours, 12 minutes, 23 seconds)

Linux will report uptime as 8 days and 40 minutes. The real uptime (after subtracting suspend time) is about 2 days and 18 hours.


suspendtime bash script

Here's the code you can copy to your system:

#!/bin/bash

# NAME: suspendtime
# PATH: $HOME/askubuntu/
# DESC: For: https://askubuntu.com/questions/321855/how-to-get-real-uptime
# DATE: November 6, 2019.

# NOTE: Calculate suspend time from systemd's journalctl

# UPDT: 2019-11-07 Fine-tune removing 0 Units in DaysMinutesStr
#       2020-05-09 Add "weeks" unit measure to DaysMinutes() function

# Duplicate DaysMinutes from ~/.bashrc for Ask Ubuntu
DaysMinutes () {

    local w d h m s
    (( w = ${1} / 604800 ))
    (( d = ${1}%604800 / 86400 ))
    (( h = (${1}%86400) / 3600 ))
    (( m = (${1}%3600) / 60 ))
    (( s = ${1}%60 ))
    DaysMinutesStr="$w weeks, $d days, $h hours, $m minutes, $s seconds"

    # Convert 1's to singular
    [[ ${DaysMinutesStr:0:2} = "1 " ]] && \
        DaysMinutesStr="${DaysMinutesStr/weeks/week}"
    DaysMinutesStr="${DaysMinutesStr/ 1 days/ 1 day}"
    DaysMinutesStr="${DaysMinutesStr/ 1 hours/ 1 hour}"
    DaysMinutesStr="${DaysMinutesStr/ 1 minutes/ 1 minute}"
    DaysMinutesStr="${DaysMinutesStr/ 1 seconds/ 1 second}"

    # Suppress zero strings
    [[ ${DaysMinutesStr:0:1} = "0" ]] &&
        DaysMinutesStr="${DaysMinutesStr/0 weeks, / }"
    DaysMinutesStr="${DaysMinutesStr/ 0 days, / }"
    DaysMinutesStr="${DaysMinutesStr/ 0 hours, / }"
    DaysMinutesStr="${DaysMinutesStr/ 0 minutes, / }"
    DaysMinutesStr="${DaysMinutesStr/, 0 seconds/}"
} # DaysMinutes

# Build array of suspend cycles from Systemd
IFS=$'\n' Arr=( $(journalctl -b-0 | \
                grep -E 'systemd\[1]: Start.*Suspend' | cut -c1-15) )

[[ ${#Arr[@]} -gt 0 ]] && upper=$(( ${#Arr[@]} - 1 ))
[[ $upper -gt 0 ]] && for (( i=0; i<upper; i=i+2 )) ; do
    (( SuspendCount++ ))
    Time=$(( $(date +%s -d "${Arr[i+1]}") - $(date +%s -d "${Arr[i]}") ))
    SuspendTime=$(( SuspendTime + Time ))
    DaysMinutes "$Time"
    printf "%s to %s suspended%s\n" "${Arr[i]}" "${Arr[i+1]}" \
          "$DaysMinutesStr"
done

echo
LinuxTime=$(( $(date +%s -d "Now") - $(date +%s -d "$(uptime -s)") ))
DaysMinutes "$LinuxTime"
printf "Linux uptime %'d seconds (%s)\n" "$LinuxTime" "$DaysMinutesStr"
DaysMinutes "$SuspendTime"
printf "%s Suspends %'d seconds (%s)\n" \
        "$SuspendCount" "$SuspendTime" "$DaysMinutesStr"
RealTime=$(( LinuxTime - SuspendTime ))
DaysMinutes "$RealTime"
printf "Real uptime %'d seconds (%s)\n" "$RealTime" "$DaysMinutesStr"

About half the program is converting seconds to human readable format in weeks, days, hours, minutes and seconds. The function DaysMinutes does this and was copied from my ~/.bashrc file where you may want to put that function yourself.


How it works

The key component is getting suspend start and end times from journalctl:

$ journalctl -b-0 | grep -E 'systemd\[1]: Start.*Suspend'
Oct 31 05:55:19 alien systemd[1]: Starting Suspend...
Oct 31 16:54:26 alien systemd[1]: Started Suspend.
 (... SNIP ...)
Nov 07 21:07:28 alien systemd[1]: Starting Suspend...
Nov 08 05:08:52 alien systemd[1]: Started Suspend.
  • The journalctl -b-0 command reads all the system messages for the current boot. You could enhance the function to look at the previous boot using -b-1 the boot before that with -b-2, etc.
  • The grep command with regex does the heavy lifting returning 32 system messages pertaining to suspend from the 19,330 system messages recorded (on my system)
| improve this answer | |
  • It seems to work not anymore in 20.04 ?!? Build array of suspend cycles from Systemd seems not to work. echo $Arr Mai 03 09:06:53 Mai 03 09:19:28 Mai 03 10:10:09 but next command - what this should be result with '\n' ahead? – Arno May 3 at 8:32
  • @Arno Can we first confirm what systemd returns when you use the example above of: journalctl -b-0 | grep -E 'systemd\[1]: Start.*Suspend'? It should show the times going to sleep and waking up. – WinEunuuchs2Unix May 3 at 14:37
  • Journal has been changed: Better is: "IFS=$'\n' Arr=( $(journalctl -b-0 | grep -E 'PM: suspend' | cut -c1-15) )", but date command gives me "wrong date"... – Arno May 3 at 16:13
  • date +%s -d "Mai 03 18:00:00" date: ungültiges Datum »Mai 03 18:00:00“ (or translated invalid date) – Arno May 3 at 16:37
  • @Arno I can only test my own code not your code I can't see. I will have to upgrade my test partition to 20.04 and test how systemd has changed in that version. Life is busy with SARS-CoV-2 research and I still haven't installed the 32GB Ram upgrade I bought just before the March 28, 2020 lock down. If I don't reply with an updated answer in 1 week please send me a reminder. Thanks. – WinEunuuchs2Unix May 3 at 17:08
0

After years the above script requires an update. systemd messages and date command are not compatible anymore - as explained in the comments to the upper script. So I take that one as a base and modified it. Journal returns date as "Mai 03 ..." which can't be handled by date. To work around I faked the year and month - so it will not count properly if month is changed during standby .... Maybe someone will change that.

#!/bin/bash

# NAME: suspendtime
# PATH: $HOME/askubuntu/
# DESC: For: https://askubuntu.com/questions/321855/how-to-get-real-uptime
# DATE: November 6, 2019.

# NOTE: Calculate suspend time from systemd's journalctl

# UPDT: 2019-11-07 Fine-tune removing 0 Units in DaysMinutesStr
# UPDT: 2020-05-05 works with Ubuntu 20.04 but has problem with changing monthes

# Duplicate DaysMinutes from ~/.bashrc for Ask Ubuntu
DaysMinutes () {

    local d h m s
    (( d = ${1} / 86400 ))
    (( h = (${1}%86400) / 3600 ))
    (( m = (${1}%3600) / 60 ))
    (( s = ${1}%60 ))
    DaysMinutesStr="$d days, $h hours, $m minutes, $s seconds"

    # Convert 1's to singular
    [[ ${DaysMinutesStr:0:2} = "1 " ]] && \
        DaysMinutesStr="${DaysMinutesStr/days/day}"
    DaysMinutesStr="${DaysMinutesStr/ 1 hours/ 1 hour}"
    DaysMinutesStr="${DaysMinutesStr/ 1 minutes/ 1 minute}"
    DaysMinutesStr="${DaysMinutesStr/ 1 seconds/ 1 second}"

    # Suppress zero strings
    [[ ${DaysMinutesStr:0:1} = "0" ]] &&
        DaysMinutesStr="${DaysMinutesStr/0 days, / }"
    DaysMinutesStr="${DaysMinutesStr/ 0 hours, / }"
    DaysMinutesStr="${DaysMinutesStr/ 0 minutes, / }"
    DaysMinutesStr="${DaysMinutesStr/, 0 seconds/}"
} # DaysMinutes

# Build array of suspend cycles from Systemd
IFS=$'\n' Arr=( $(journalctl -b-0 | grep -E 'PM: suspend' | cut -c5-15) )

[[ ${#Arr[@]} -gt 0 ]] && upper=$(( ${#Arr[@]} - 1 ))
SuspentCount=0
[[ $upper -gt 0 ]] && for (( i=0; i<upper; i=i+2 )) ; do
    (( SuspendCount++ ))
    Time=$(( $(date +%s -d "2020-01-${Arr[i+1]}") - $(date +%s -d "2020-01-${Arr[i]}") ))
    SuspendTime=$(( SuspendTime + Time ))
    printf "%s to %s lasting %'d seconds\n" "${Arr[i]}" "${Arr[i+1]}" "$Time"
done

echo
LinuxTime=$(( $(date +%s -d "Now") - $(date +%s -d "$(uptime -s)") ))
DaysMinutes "$LinuxTime"
printf "Linux uptime \t%'d seconds (%s)\n" "$LinuxTime" "$DaysMinutesStr"
if [ $SuspendCount >  0 ]; then 
    DaysMinutes "$SuspendTime"
    printf "%sx Suspend \t%'d seconds (%s)\n" "$SuspendCount" "$SuspendTime" "$DaysMinutesStr"
fi
RealTime=$(( LinuxTime - SuspendTime ))
DaysMinutes "$RealTime"
printf "Real uptime \t%'d seconds (%s)\n" "$RealTime" "$DaysMinutesStr"   
| improve this answer | |
  • The problem appears to not be with systemd in Ubuntu 20.04 but rather with the date command in Ubuntu 20.04. I see you've changed this line: Time=$(( $(date +%s -d "${Arr[i+1]}") - $(date +%s -d "${Arr[i]}") )) because the date command isn't working. – WinEunuuchs2Unix May 3 at 22:30
  • True. 3 things I have changed: I go for "PM: suspend" in the journal. Without that change it will only find Starting of suspend, not start and stop. Then I wiped out the month in the log (cut -c4-15) and finally replaced the months by a fixed year and month in different format (Mai 03 -> 2020-05-03) to make the conversion to seconds happen. To improve it it needs some more effort in converting the date. – Arno May 4 at 5:54
  • May I ask what is this IFS=$'\n' used for ? – Arno May 4 at 6:10
  • It basically means you read one line at a time. See: unix.stackexchange.com/questions/184863/… – WinEunuuchs2Unix May 4 at 11:24
  • Updated the script - as the previous version show error if no suspend has happened. If someone will take effort to improve month/year transition : welcome. I am atm to lazy to do that. – Arno May 5 at 9:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.