178

I am trying to understand the dmesg timestamp and find it hard to convert that to change it to java date/custom date format.

Sample dmesg log:

[14614.647880] airo(eth1): link lost (missed beacons)

So how do I convert 14614.647880 to a standard date?

Alexis Wilke
  • 19,179
  • 10
  • 84
  • 156
ukanth
  • 2,718
  • 5
  • 28
  • 38

12 Answers12

298

Understanding dmesg timestamp is pretty simple: it is time in seconds since the kernel started. So, having time of startup (uptime), you can add up the seconds and show them in whatever format you like.

Or better, you could use the -T command line option of dmesg and parse the human readable format.

From the man page:

-T, --ctime
    Print human readable timestamps. The timestamp could be inaccurate!

    The time source used for the logs is not updated after system SUSPEND/RESUME.
drescherjm
  • 10,365
  • 5
  • 44
  • 64
  • 12
    What command accepts -T ? My dmesg doesn't, neither manpage tells it. (Linux Mint Debian Edition). – gyorgyabraham Jun 04 '13 at 08:34
  • 1
    Mine does (`dmesg` from `util-linux 2.20.1` under Ubuntu 13.04) –  Jun 04 '13 at 16:55
  • 2
    Not available in redhat and/or oracle linux 5.6, `rpm -qf /bin/dmesg => util-linux-2.13-0.56.0.2.el5` – michael Jul 23 '13 at 19:59
  • 9
    This option appeared in `util-linux 2.20`, according to Release Notes: ftp://ftp.kernel.org/pub/linux/utils/util-linux/v2.20/v2.20-ReleaseNotes – ks1322 Apr 02 '14 at 08:04
  • @RC. ok, let me explain: I think the answer doesn't answer the question exactly. The question is "I am trying to understand..." and the answer is "parse the output of `-T` flag". Indeed, downvoting was conterproductive on my side -- I'm sorry for that. I submitted a short edit and will upvote when it is applied (or similar information shows up). – xealits May 02 '16 at 10:44
  • 1
    @xealits thanks for the follow up, that's nice of you :) Regarding the question, I think the understanding part is minor and the "convert that to change it to java date/custom date format." was the core part, but your opinion might differ. Have a nice day ;) –  May 02 '16 at 11:16
  • what timezone are `dmesg -T` timestamps in? – mark Mar 30 '21 at 01:25
36

With the help of dr answer, I wrote a workaround that makes the conversion to put in your .bashrc. It won't break anything if you don't have any timestamp or already correct timestamps.

dmesg_with_human_timestamps () {
    $(type -P dmesg) "$@" | perl -w -e 'use strict;
        my ($uptime) = do { local @ARGV="/proc/uptime";<>}; ($uptime) = ($uptime =~ /^(\d+)\./);
        foreach my $line (<>) {
            printf( ($line=~/^\[\s*(\d+)\.\d+\](.+)/) ? ( "[%s]%s\n", scalar localtime(time - $uptime + $1), $2 ) : $line )
        }'
}
alias dmesg=dmesg_with_human_timestamps

Also, a good reading on the dmesg timestamp conversion logic & how to enable timestamps when there are none: https://supportcenter.checkpoint.com/supportcenter/portal?eventSubmit_doGoviewsolutiondetails=&solutionid=sk92677

Community
  • 1
  • 1
Lucas Cimon
  • 1,859
  • 2
  • 24
  • 33
  • minor improvement: you can remove 'tail -1 ' in the pipeline and just let awk eat lines and print from the final line in its buffer. `local dmesg_bin=$(type -a dmesg | awk 'END { print $NF }')` – Brian Onn Dec 03 '13 at 10:33
  • @Lucas: can you explain using 'type -a dmesg|...' instead of $(which dmesg)? Is there an advantage to a 3-stage pipe to get that path? – Stabledog Dec 31 '13 at 15:28
  • @Stabledog: good question. For an explanation on why using `type` over `which`, see [this question](http://unix.stackexchange.com/q/10525). I edited my answer to avoid the useless triple pipe though. – Lucas Cimon Jan 10 '14 at 19:38
  • this bash/perl snippet worked for me, I have an old RHEL5.7 machine I have to look after and dmesg has no option to print timestamp in human time. – Paul M Sep 04 '18 at 09:39
23

For systems without "dmesg -T" such as RHEL/CentOS 6, I liked the "dmesg_with_human_timestamps" function provided by lucas-cimon earlier. It has a bit of trouble with some of our boxes with large uptime though. Turns out that kernel timestamps in dmesg are derived from an uptime value kept by individual CPUs. Over time this gets out of sync with the real time clock. As a result, the most accurate conversion for recent dmesg entries will be based on the CPU clock rather than /proc/uptime. For example, on a particular CentOS 6.6 box here:

# grep "\.clock" /proc/sched_debug  | head -1
  .clock                         : 32103895072.444568
# uptime
 15:54:05 up 371 days, 19:09,  4 users,  load average: 3.41, 3.62, 3.57
# cat /proc/uptime
32123362.57 638648955.00

Accounting for the CPU uptime being in milliseconds, there's an offset of nearly 5 1/2 hours here. So I revised the script and converted it to native bash in the process:

dmesg_with_human_timestamps () {
    FORMAT="%a %b %d %H:%M:%S %Y"

    now=$(date +%s)
    cputime_line=$(grep -m1 "\.clock" /proc/sched_debug)

    if [[ $cputime_line =~ [^0-9]*([0-9]*).* ]]; then
        cputime=$((BASH_REMATCH[1] / 1000))
    fi

    dmesg | while IFS= read -r line; do
        if [[ $line =~ ^\[\ *([0-9]+)\.[0-9]+\]\ (.*) ]]; then
            stamp=$((now-cputime+BASH_REMATCH[1]))
            echo "[$(date +"${FORMAT}" --date=@${stamp})] ${BASH_REMATCH[2]}"
        else
            echo "$line"
        fi
    done
}

alias dmesgt=dmesg_with_human_timestamps
Community
  • 1
  • 1
Allen Belletti
  • 331
  • 2
  • 4
  • Function didn't work in zsh. Had to do it in bash proper. That said, on a box with 221 days of uptime, this solution had the time stamp down to the minute of actual. Other solutions showed the root cause event happening 2+ hours earlier in the day. Thanks, Allen. You saved my afternoon. – Trenton Sep 27 '16 at 19:47
  • RHEL5.x machines don't appear to have /proc/sched_debug :-( – Paul M Sep 04 '18 at 09:42
20

So KevZero requested a less kludgy solution, so I came up with the following:

sed -r 's#^\[([0-9]+\.[0-9]+)\](.*)#echo -n "[";echo -n $(date --date="@$(echo "$(grep btime /proc/stat|cut -d " " -f 2)+\1" | bc)" +"%c");echo -n "]";echo -n "\2"#e'

Here's an example:

$ dmesg|tail | sed -r 's#^\[([0-9]+\.[0-9]+)\](.*)#echo -n "[";echo -n $(date --date="@$(echo "$(grep btime /proc/stat|cut -d " " -f 2)+\1" | bc)" +"%c");echo -n "]";echo -n "\2"#e'
[2015-12-09T04:29:20 COT] cfg80211:   (57240000 KHz - 63720000 KHz @ 2160000 KHz), (N/A, 0 mBm), (N/A)
[2015-12-09T04:29:23 COT] wlp3s0: authenticate with dc:9f:db:92:d3:07
[2015-12-09T04:29:23 COT] wlp3s0: send auth to dc:9f:db:92:d3:07 (try 1/3)
[2015-12-09T04:29:23 COT] wlp3s0: authenticated
[2015-12-09T04:29:23 COT] wlp3s0: associate with dc:9f:db:92:d3:07 (try 1/3)
[2015-12-09T04:29:23 COT] wlp3s0: RX AssocResp from dc:9f:db:92:d3:07 (capab=0x431 status=0 aid=6)
[2015-12-09T04:29:23 COT] wlp3s0: associated
[2015-12-09T04:29:56 COT] thinkpad_acpi: EC reports that Thermal Table has changed
[2015-12-09T04:29:59 COT] i915 0000:00:02.0: BAR 6: [??? 0x00000000 flags 0x2] has bogus alignment
[2015-12-09T05:00:52 COT] thinkpad_acpi: EC reports that Thermal Table has changed

If you want it to perform a bit better, put the timestamp from proc into a variable instead :)

Android Control
  • 496
  • 1
  • 5
  • 14
runejuhl
  • 2,147
  • 1
  • 17
  • 17
9

dmesg -T may show wrong time, different from date command output.

Workaround is journalctl with -k, --dmesg. I am using -k as it is shorter:

journalctl -k

It will show only kernel messages and correct time.

To show only kernel lines matching phrase:

journalctl -kg phrase
16851556
  • 255
  • 3
  • 11
6

In recent versions of dmesg, you can just call dmesg -T.

Steffen Heil
  • 4,286
  • 3
  • 32
  • 35
6

If you don't have the -T option for dmesg as for example on Andoid, you can use the busybox version. The following solves also some other issues:

  1. The [0.0000] format is preceded by something that looks like misplaced color information, prefixes like <6>.
  2. Make integers from floats.

It is inspired by this blog post.

#!/bin/sh                                                                                                               
# Translate dmesg timestamps to human readable format                                                                   

# uptime in seconds                                                                                                     
uptime=$(cut -d " " -f 1 /proc/uptime)                                                                                  

# remove fraction                                                                                                       
uptime=$(echo $uptime | cut -d "." -f1)                                                                                 

# run only if timestamps are enabled                                                                                    
if [ "Y" = "$(cat /sys/module/printk/parameters/time)" ]; then                                                          
  dmesg | sed "s/[^\[]*\[/\[/" | sed "s/^\[[ ]*\?\([0-9.]*\)\] \(.*\)/\\1 \\2/" | while read timestamp message; do      
    timestamp=$(echo $timestamp | cut -d "." -f1)                                                                       
    ts1=$(( $(busybox date +%s) - $uptime + $timestamp ))                                                               
    ts2=$(busybox date -d "@${ts1}")                                                                                    
    printf "[%s] %s\n" "$ts2" "$message"                                                                                
  done                                                                                                                  
else                                                                                                                    
  echo "Timestamps are disabled (/sys/module/printk/parameters/time)"                                                   
fi                                                                                                                      

Note, however, that this implementation is quite slow.

Anne van Rossum
  • 3,091
  • 1
  • 35
  • 39
3

you will need to reference the "btime" in /proc/stat, which is the Unix epoch time when the system was latest booted. Then you could base on that system boot time and then add on the elapsed seconds given in dmesg to calculate timestamp for each events.

imcom
  • 61
  • 1
  • 6
3

With older Linux distros yet another option is to use wrapping script, e.g. in Perl or Python.

See solutions here:

http://linuxaria.com/article/how-to-make-dmesg-timestamp-human-readable?lang=en http://jmorano.moretrix.com/2012/03/dmesg-human-readable-timestamps/

Aliaksei Ramanau
  • 909
  • 1
  • 9
  • 16
1

A caveat which the other answers don't seem to mention is that the time which is shown by dmesg doesn't take into account any sleep/suspend time. So there are cases where the usual answer of using dmesg -T doesn't work, and shows a completely wrong time.

A workaround for such situations is to write something to the kernel log at a known time and then use that entry as a reference to calculate the other times. Obviously, it will only work for times after the last suspend.

So to display the correct time for recent entries on machines which may have been suspended since their last boot, use something like this from my other answer here:

# write current time to kernel ring buffer so it appears in dmesg output
echo "timecheck: $(date +%s) = $(date +%F_%T)" | sudo tee /dev/kmsg

# use our "timecheck" entry to get the difference
# between the dmesg timestamp and real time
offset=$(dmesg | grep timecheck | tail -1 \
| perl -nle '($t1,$t2)=/^.(\d+)\S+ timecheck: (\d+)/; print $t2-$t1')

# pipe dmesg output through a Perl snippet to
# convert it's timestamp to correct readable times
dmesg | tail \
| perl -pe 'BEGIN{$offset=shift} s/^\[(\d+)\S+/localtime($1+$offset)/e' $offset
mivk
  • 13,452
  • 5
  • 76
  • 69
1

I needed to view the ring buffer log on an older embedded Linux device. The device didn't have the new -T option for dmesg, didn't have bc or even a version of sed or bash that worked with the examples above. I ended creating a script using AWK to format the log dates based on runejuhl's sed script. Here is the script in case it's useful to someone.

    #!/bin/sh
    
    # print kernel ring buffer (dmesg log) with readable times if (dmsg -T not available)
    
    # boot time in seconds
    boottime=$(echo $(grep -m1 "btime" /proc/stat) | grep -Eo "[0-9]*$")
    
    lines=$1
    if [ -z $lines ]; then
        lines=10
    fi
    
    #dislpay last x lines of kernel log using awk script instead of sed / bc
    dmesg | tail -n $lines | awk 'match($0, /^\[ *([0-9]+\.[0-9]+)\]/) \
    { print strftime("[%a %d/%m/%Y %H:%M:%S]",sprintf("%.0f", ('$boottime' + substr($0, RSTART+1, RLENGTH-2)))) substr($0, RLENGTH+1) }'

This reads the time the system booted and stores it in a variable, then parses the log extracting the timestamp of each line adding it on to the boot time, formatting it to a date time string and writing it out with the rest of the line.

The boot time line (btime) is read from /proc/stat and the time extracted
boottime=$(echo $(grep -m1 "btime" /proc/stat) | grep -Eo "[0-9]*$")

the AWK match command is used to find the timestamp with a format of [ 0.0]
match($0, /^\[ *([0-9]+\.[0-9]+)\]/)

After a regex match the RSTART and RLENGTH variables hold the start and length of matched chars. The time stamp is extracted using a substring command, ignoring the [ ] substr($0, RSTART+1, RLENGTH-2)

The timestamp is then added onto the boottime value and rounded back to an integer using sprintf("%.0f", timestamp + boottime)

Finally the time is formatted to a readable value using strftime("[%a %d/%m/%Y %H:%M:%S]", logdate) and the rest of the log line to printed using another substr command taking the rest of the line after the original timestamp substr($0, RLENGTH+1)

Brian
  • 11
  • 1
0

The below command gives you better output

dmesg | awk -F ] '{"cat /proc/uptime | cut -d \" \" -f 1" | getline st;a=substr( $1,2, length($1) - 1);print strftime("%F %H:%M:%S %Z",systime()-st+a)" -> "$0}'
Usitha Indeewara
  • 870
  • 3
  • 10
  • 21