summaryrefslogtreecommitdiff
path: root/ts
blob: af23cf71667f519fbde51d0d32eea4222998a33a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
#!/usr/bin/perl

=head1 NAME

ts - timestamp input

=head1 SYNOPSIS

ts [-r] [-i | -s] [-m] [format]

=head1 DESCRIPTION

ts adds a timestamp to the beginning of each line of input.

The optional format parameter controls how the timestamp is formatted,
as used by L<strftime(3)>. The default format is "%b %d %H:%M:%S". In
addition to the regular strftime conversion specifications, 
"%.S" and "%.s" and "%.T"
are like "%S" and "%s" and "%T", but provide subsecond resolution
(ie, "30.00001" and "1301682593.00001" and "1:15:30.00001").

If the -r switch is passed, it instead converts existing timestamps in
the input to relative times, such as "15m5s ago". Many common timestamp
formats are supported. Note that the Time::Duration and Date::Parse perl
modules are required for this mode to work. Currently, converting localized
dates is not supported.

If both -r and a format is passed, the existing timestamps are
converted to the specified format.

If the -i or -s switch is passed, ts reports incremental timestamps instead of
absolute ones. The default format changes to "%H:%M:%S", and "%.S" and "%.s" can
be used as well. In case of -i, every timestamp will be the time elapsed since
the last timestamp. In case of -s, the time elapsed since start of the program
is used.

The -m switch makes the system's monotonic clock be used.

=head1 ENVIRONMENT

The standard TZ environment variable controls what time zone dates
are assumed to be in, if a timezone is not specified as part of the date.

=head1 AUTHOR

Copyright 2006 by Joey Hess <id@joeyh.name>

Licensed under the GNU GPL.

=cut

use warnings;
use strict;
use POSIX q{strftime};
no warnings 'utf8';

$|=1;

my $rel=0;
my $inc=0;
my $sincestart=0;
my $mono=0;
use Getopt::Long;
GetOptions(
	"r" => \$rel,
	"i" => \$inc,
	"s" => \$sincestart,
	"m" => \$mono
) || die "usage: ts [-r] [-i | -s] [-m] [format]\n";

if ($rel) {
	eval q{
		use Date::Parse;
		use Time::Duration;
	};
	die $@ if $@;
}

my $use_format=@ARGV;
my $format="%b %d %H:%M:%S";
if ($inc || $sincestart) {
	$format="%H:%M:%S";
	$ENV{TZ}='GMT';
}
$format=shift if @ARGV;

# For subsecond resolution, Time::HiRes is needed.
my $hires=0;
if ($format=~/\%\.[SsT]/ || $mono) {
	require Time::HiRes;
	use Time::HiRes qw(CLOCK_MONOTONIC);
	$hires=1;
}

my $lastseconds = 0;
my $lastmicroseconds = 0;
my $monodelta;

if ($mono) {
	my $raw_time = Time::HiRes::clock_gettime(CLOCK_MONOTONIC);
	$lastseconds = time;
	$lastmicroseconds = int(1000000 * ($raw_time - int($raw_time)));
	$monodelta = $lastseconds - int($raw_time);
}
elsif ($hires) {
	($lastseconds, $lastmicroseconds) = Time::HiRes::gettimeofday();
}
else {
	$lastseconds = time;
}


while (<>) {
	if (! $rel) {
		if ($hires) {
			my $f=$format;
            my $seconds;
			my $microseconds;
			if ($mono) {
				my $raw_time =
					Time::HiRes::clock_gettime(CLOCK_MONOTONIC) +
					$monodelta;
				$seconds = int($raw_time);
				$microseconds = int(1000000 * ($raw_time - $seconds));
			}
			else {
				($seconds, $microseconds) = Time::HiRes::gettimeofday();
			}

			if ($inc || $sincestart) {
				my $deltaseconds = $seconds - $lastseconds;
				my $deltamicroseconds = $microseconds - $lastmicroseconds;
				if ($deltamicroseconds < 0) {
					$deltaseconds -= 1;
					$deltamicroseconds += 1000000;
				}
				if ($inc) {
					$lastseconds = $seconds;
					$lastmicroseconds = $microseconds;
				}
				$seconds = $deltaseconds;
				$microseconds = $deltamicroseconds;
			}
			my $s=sprintf("%06i", $microseconds);
			$f=~s/\%\.([SsT])/%$1.$s/g;
			print strftime($f, localtime($seconds));
		}
		else {
			if ($inc || $sincestart) {
				my $seconds = time;
				my $deltaseconds = $seconds - $lastseconds;
				if ($inc) {
					$lastseconds = $seconds;
				}
				print strftime($format, localtime($deltaseconds));
			}
			else {
				print strftime($format, localtime);
			}
		}
		print " ".$_;
	}
	else {
		s{\b(
			\d\d[-\s\/]\w\w\w	# 21 dec 17:05
				(?:\/\d\d+)?	# 21 dec/93 17:05
				[\s:]\d\d:\d\d	#       (time part of above)
				(?::\d\d)?	#       (optional seconds)
				(?:\s+[+-]\d\d\d\d)? #  (optional timezone)
			|
			\w{3}\s+\d{1,2}\s+\d\d:\d\d:\d\d # syslog form
			|
			\d\d\d\d[-:]\d\d[-:]\d\dT\d\d:\d\d:\d\d.\d+Z? # ISO-8601
			|
			(?:\w\w\w,?\s+)?	#       (optional Day)
			\d+\s+\w\w\w\s+\d\d+\s+\d\d:\d\d:\d\d
						# 16 Jun 94 07:29:35
				(?:\s+\w\w\w|\s[+-]\d\d\d\d)?
						#	(optional timezone)
			|
			\w\w\w\s+\w\w\w\s+\d\d\s+\d\d:\d\d
						# lastlog format
		  )\b
		}{
			$use_format
				? strftime($format, localtime(str2time($1)))
				: concise(ago(time - str2time($1), 2))
		}exg;

		print $_;
	}
}