17

I am interested in knowing the total time I spend working on my laptop in 2016.

last reboot --since 2016-01-01 --until 2016-12-31 | grep -o '(.*)' | grep  -v '-'

gives me the total uptime of the laptop in this format:

(01:33) 
(04:40) 
(01:31) 
(1+06:41) 
(02:47) 
(00:30)

Now how do I add it up?

  • can you tell me the meaning of (1+06:41) ? – kashish Dec 22 '16 at 7:48
  • @kashish 1 day, 6 hours, 41 minutes – muru Dec 22 '16 at 8:21
  • The thing is that wtmp logs (which is what you see in output of last command) are set to rotate monthly (in /etc/logrotate.conf), meaning that after certain time period , the oldest log gets deleted. In other words, whatever you're trying to do with last , it wont be accurate – Sergiy Kolodyazhnyy Dec 22 '16 at 14:00
  • @Serg I've disabled logrotate for wtmp. It's been more than an year since I did that, and the file size is currently 3.7M. So I don't see any reason why it should be rotated monthly. ;) – daltonfury42 Dec 22 '16 at 14:18
  • @daltonfury42 If it is disabled it won't rotate. Most users don't do that. – Sergiy Kolodyazhnyy Dec 22 '16 at 14:20
5

Here's an awk + awk version:

last ... | awk '/reboot/{print $NF}' |
    awk -F '[(+:)]' '
        {
            d += $(NF - 3); h += $(NF - 2); m += $(NF - 1)
        }
        END {
            carry = m / 60; h += carry;
            carry = h / 24; d += carry;
            printf "%d days, %d hours, %d minutes\n", d, h % 24, m % 60
        }'

last's last column is in the format (<days>+hours:minutes), where days+ is dropped if the period is less than 1 day.

Here, the first awk command outputs the last column, the duration of interest, for reboot entries.

For the second awk command:

  1. FS is [(+:)], i.e., parentheses or + or :. So, (h:m) is split to , h, m and (first and last fields empty), and (d+h:m) is split to , d, h, m and (again, first and last fields empty).
  2. Then we take the second-last field for minutes, third-last for hours and fourth-last for days. The fourth-last field will be the first, empty, field if days is not present. So, we'll be simply adding 0 in this case.
  3. Then we bump up the hours and days by if minutes and hours are above 60 and 24 respectively. Note that awk does floating point division, so h and d may now have fractional parts.
  4. Then we print the numbers as integers (%d), so any fractional part is ignored.
8

Trying with bash script and extending your command. I use dateutils to add up the time duration.

Hence to use this script one needs the dateutils package available through apt. ( sudo apt install dateutils )

This script takes into account the current uptime (present session) also, hence more accurate. Seconds are not counted. Lowest unit reported is minute.

#!/bin/bash
# Total uptime reported.
temp=$(last reboot --since 2016-01-01 --until 2016-12-31 | grep -o '(.*)' | grep  -v '-' | sed 's/(\([0-9]\{1,\}\)+\([0-9]\{1,\}\):\([0-9]\{1,\}\))/\1d\2h\3m/g ;s/(\([0-9]\{1,\}\):\([0-9]\{1,\}\))/\1h\2m/g'  )
curr=$( cat /proc/uptime |  perl -ne '/(\d*)/ ; printf "%02d:%02d:%02d\n",int($1/86400),int(($1%86400)/3600),int(($1%3600)/60)'  )
echo "Total run time (days:hours:minutes)"
curr="2015-01-01T"$curr
org="2015-01-01T00:00:00"
new=$(dateutils.dadd $curr $temp )
dateutils.ddiff $org $new -f "%dd %Hh %Mm"
  • First the uptimes from last reboot is listed and formatted to extract the day, hours, minutes and second information. This is then saved in temp.
  • A reference fake date is set called org=2015-01-01 to which current uptime is added.
  • Then all cumulative uptimes are added to the variable new
  • The duration between org and the net uptime new is found by difference.

Output:

vayu@helix:$ ./uptime_record.sh
Total run time (days:hours:minutes)
57d 20h 36m

The following script is for the uptime in exactly one year from the day the script is run.

#!/bin/bash
# Total uptime reported since exactly 1 year (from the time script is run).
now="$(date +'%Y-%m-%d')" ;
last_y=$(dateutils.dadd $now -1y)
temp=$(last reboot --since "$last_y" --until "$now" | grep -o '(.*)' | grep  -v '-' | sed 's/(\([0-9]\{1,\}\)+\([0-9]\{1,\}\):\([0-9]\{1,\}\))/\1d\2h\3m/g ;s/(\([0-9]\{1,\}\):\([0-9]\{1,\}\))/\1h\2m/g'  )
curr=$( cat /proc/uptime |  perl -ne '/(\d*)/ ; printf "%02d:%02d:%02d\n",int($1/86400),int(($1%86400)/3600),int(($1%3600)/60)'  )
echo "Total run time in one year (days:hours:minutes)"
curr="1980-01-01T"$curr
org="1980-01-01T00:00:00"
new=$(dateutils.dadd $curr $temp )
dateutils.ddiff $org $new -f "%dd %Hh %Mm"
  • 1
    Somehow I like bash scripts, but I know here that more elegant answers are possible. – ankit7540 Dec 22 '16 at 8:01
  • According to your script, I spend 2258days, 01hours, 15minutes online last year. – daltonfury42 Dec 22 '16 at 9:09
  • Error fixed. Code compacted to 8 lines. – ankit7540 Dec 22 '16 at 13:26
5

Here is a python implementation of what I want, but I am sure there is an elegant way to do it with bash:

import subprocess
output = subprocess.run("last reboot --since 2016-01-01 --until 2016-12-31 | grep -o '(.*)' | grep  -v '-'| sed -e 's/(//g; s/)//g; s/+/:/g'", shell=True, stdout=subprocess.PIPE, universal_newlines=True)
mins_total = 0

for line in output.stdout.split('\n')[:-1]:
    try:
        (hrs, mins) = [int(k) for k in line.split(':')]
        days = 0
    except:
        (days, hrs, mins) = [int(k) for k in line.split(':')]
    mins_total += (days*24 + hrs)*60 + mins

print("Hours: " + str(mins_total/60))
print("Days: " + str(mins_total/60/24))
  • 3
    “I am sure there is an elegant way to do it with bash” IMHO Python is a better choice. Bash is good for file manipulations, not for calculations. – Melebius Dec 22 '16 at 11:45

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

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