#!/usr/bin/perl # Copywrite 2009 - Steven Rostedt # Licensed under the terms of the GNU GPL License version 2 # # usage: # rb-read.pl [cpu] # # cpu is optional. It is used to print out the cpu in the output field. # without it, it will default to zero. # # This file parses the /debugfs/tracing/events directory to find all # the registered events, and then it reads the input file and parses # out the data to produce the event trace. # # The input file is a file that has been read via the # /debugfs/tracing/binary_buffers/* files. # use strict; my $timestamp; my $pagesize; my $type; my $len; my $time_delta; my $read = 0; my $PADDING = 0; my $TIME_EXTEND = 1; my $TIME_STAMP = 2; my $DATA = 3; my $event; my $cpu; my %event_types; my %event_ids; sub debug_print { return; printf @_; } sub do_read { my ($l) = @_; my $buffer; my $r; $r = read IN, $buffer, $l; if ($r != $l) { print "only read $r but expected $l\n"; exit(0); } $read += $l; return $buffer; } sub read_page_header { my $header; $header = do_read(16); my($time_low, $time_high, $commit_low, $commit_hi) = unpack "I4", $header; $timestamp = $time_low + ($time_high << 32); $pagesize = $commit_low + ($commit_hi << 32); } sub read_header { my $header; $header = do_read(4); $header = unpack "I", $header; debug_print("header = %08x\n", $header); $type = $header & 3; $len = ($header & 0x1c) >> 2; $time_delta = $header >> 5; debug_print("type=%d len=%d timedelta=%x\n", $type, $len, $time_delta); if ($type == $PADDING) { debug_print("PADDING\n"); $len = -1; } elsif ($type == $TIME_EXTEND) { debug_print("TIME_EXTEND\n"); $header = do_read(4); my $time_ext = unpack "I", $header; $time_delta |= ($time_ext << 28); debug_print(" time delta = %x\n", $time_delta); $len = 0; } elsif ($type == $TIME_STAMP) { debug_print("TIME_STAMP\n"); do_read(12); $len = 0; } elsif ($type == $DATA) { debug_print("DATA\n"); if (!$len) { $header = do_read(4); $len = unpack "I", $header; # Len is the size of the array, which includes the # 4 bytes we just read. $len -= 4; debug_print("len = $len\n"); $len += 3; $len &= ~3; } else { $len *= 4; } } else { print("ERROR!!!!\n"); exit(-1); } $timestamp += $time_delta; debug_print("record len=%d\n", $len); } sub read_event { my ($l) = @_; return if (!$l); $event = do_read($l); debug_print "read $l\n"; } sub print_event { my ($type, $flags, $pc, $dummy, $pid, $tgid, $rest) = unpack "CCCcIIa*", $event; my $index = 12; # 4 bytes and 2 words debug_print("type=%d flags=%x pc=%d pid=%d tgid=%s\n", $type, $flags, $pc, $pid, $tgid); if (!defined($event_ids{$type})) { print "UNKNOWN TYPE $type\n"; return; } if (!defined(${$event_ids{$type}}{"print"})) { return; } my @fields; # use the format string to help determine type my $idx = 1; my @delims; my $fmt; $fmt = ${$event_ids{$type}}{"print"}; $fmt =~ s/%%//; @delims = split /%/, $fmt; foreach my $f (@{${$event_ids{$type}}{"fields"}}) { my $field = $f->{"field"}; my $offset = $f->{"offset"}; my $size = $f->{"size"}; my $delim; $delim = $delims[$idx++]; debug_print(" delim: $delim\n"); # check for holes if ($index < $offset) { my $pad = $offset - $index; $index += $pad; debug_print("pad for index: $index and offset: $offset\n"); ($pad, $rest) = unpack "a$pad a*", $rest; } if ($field =~ /\bint\b/ || $field =~ /\blong\b/ || $field =~ /\bpid_t\b/ ) { my $e; if ($size == 4) { ($e, $rest) = unpack "Ia*", $rest; $index += 4; } elsif ($size == 8) { my $e2; ($e, $e2, $rest) = unpack "IIa*", $rest; $e += $e2 << 32; $index += 8; } else { print "Funny size for integer: $size\n"; return; } $fields[$#fields+1] = $e; } else { my $rec; ($rec, $rest) = unpack "A$size a*", $rest; $index += $size; debug_print("non-int field: '%s'\n", $rec); # Perl sucks with this. If we have a string and it # has NULL characters, perl will still print those # NULL characters. This hack removes them. if ($delim =~ /^[\d\.]*s/) { my @arr = split /\000/, $rec; $rec = $arr[0]; } $fields[$#fields+1] = $rec; } } my $secs; $secs = $timestamp / 1000000000; printf ("%10d [%03d] %.6f: (%s) ", $pid, $cpu, $secs, $event_ids{$type}{"name"}); if (defined(${$event_ids{$type}}{"print"})) { printf ${$event_ids{$type}}{"print"}, @fields; } print "\n"; } sub read_page { read_page_header; debug_print("timestamp = %llx\n", $timestamp); debug_print("pagesize = %llx (%d)\n", $pagesize, $pagesize); my $end = $read + $pagesize; while ($read < $end) { read_header; if ($len) { read_event($len); print_event($event); } } # skip the rest of the page my $rem = $read & 0xfff; debug_print ("rem=%d read=%d\n", $rem, $read); my $rest = do_read(0x1000 - $rem); $rest = unpack "I", $rest; debug_print "rest = %x read=%d\n", $rest, $read; } sub find_mount { open (M, "/proc/mounts") || die "can't open mounts file\n"; my $debugfs; while () { if (/^debugfs\s+(\S+)\s/) { $debugfs = $1; last; } } defined($debugfs) || die "can't find debugfs mount\n"; return $debugfs; } sub read_format { my ($event_format, $file) = @_; if (defined($event_types{$event_format})) { die "$event_format already defined!"; } $event_types{$event_format} = 1; my %hash; my @hdr; my @fields; debug_print "reading $file\n"; debug_print " for event $event_format\n"; my ($name, $id, $print_fmt); my $format = 0; open (F, $file) || die "can't read $file\n"; while () { chomp; if (/^\s*name:\s*(.*)$/) { $name = $1; debug_print "name = $name\n"; $hash{"name"} = $name; } elsif (/^\s*ID:\s*(\d+)/) { $id = $1; debug_print "id = $id\n"; } elsif (/^\s*print fmt:\s"*(.*)"\s*$/) { $print_fmt = $1; debug_print ("print_fmt = %s\n", $print_fmt); $hash{"print"} = $print_fmt; } elsif (/^\s*format:/) { $format = 1; } elsif ($format) { if (/^\s*$/) { $format++; next; } if (/^\s*field( special)?:([^;]*);\s*offset:(\d+);\s*size:(\d+);/) { my ($special, $field, $offset, $size) = ($1, $2, $3, $4); my %fhash; $fhash{"field"} = $field; $fhash{"offset"} = $offset; $fhash{"size"} = $size; if (defined($special)) { $fhash{"special"} = 1; } else { $fhash{"special"} = 0; } if ($format == 1) { debug_print "hdr "; $hdr[$#hdr+1] = \%fhash; } else { $fields[$#fields+1] = \%fhash; } debug_print "field: $field, offset: $offset, size: $size\n"; } } } if (defined($event_ids{$id})) { print "WARNING ID $id IS DUPLICATED\n"; return; } $hash{"name"} = $name; $hash{"hdr"} = \@hdr; $hash{"fields"} = \@fields; $event_ids{$id} = \%hash; } sub check_subsys { my ($subsys, $dir) = @_; debug_print ("read sys $subsys $dir\n"); opendir DIR, $dir; my @files = readdir DIR; closedir DIR; foreach my $file (@files) { my $f = "$dir/$file"; if (-d $f) { if (-f "$f/format") { read_format("$subsys:$file", "$f/format"); } } } } sub read_formats() { my $mount = find_mount; debug_print "$mount\n"; my $eventdir = $mount . "/tracing/events"; if (! -d $eventdir) { die "$eventdir does not exist\n"; } opendir DIR, $eventdir || die "can't open $eventdir"; my @files = readdir DIR; closedir DIR; foreach my $dir (@files) { next if ($dir =~ /^\./); my $d = "$eventdir/$dir"; if (-d "$d") { if (-f "$d/format") { read_format($dir, "$d/format"); } check_subsys($dir, $d); } } } $#ARGV >= 0 || die "usage: rb-read.pl [cpu]\n"; my $file; ($file, $cpu) = @ARGV; $cpu = 0 if (!defined($cpu)); read_formats; open(IN, $file) || die "can't read $file\n"; for (;;) { read_page; };