mogfiledebug: show Last-Modified header if available
[MogileFS-Utils.git] / mogfiledebug
blob63c0ddd77f3068feb31fbd3be40e25709ce63128
1 #!/usr/bin/perl
3 =head1 NAME
5 mogfiledebug -- Dump gobs of information about a FID
7 =head1 SYNOPSIS
9 $ mogfiledebug --trackers=host --domain=foo --key=bar
10 $ mogfiledebug --trackers=host --fid=1234
12 =head1 DESCRIPTION
14 Utility for troubleshooting problemic files in a mogilefs cluster. Also useful
15 for verification or testing new setups.
17 Finds as much information about a file as it can. All of the paths, any queues
18 it might be sitting in, etc. Will then test all of the paths, MD5 hash their
19 contents, and check the file lengths. If you see errors about a FID in
20 mogilefsd's logs plugging it through mogfiledebug should illuminate most of
21 the potential issues.
23 This is also useful information for posting to the mailing list, along with
24 the error you had.
26 =head1 OPTIONS
28 =over
30 =item --trackers=host1:7001,host2:7001
32 Use these MogileFS trackers to negotiate with.
34 =item --domain=<domain>
36 Set the MogileFS domain to use.
38 =item --key="<key>"
40 The key to inspect. Can be an arbitrary string.
42 =item --fid=<fid>
44 A numeric fid to inspect. Provide this as an alternative to a domain/key
45 combination.
47 =back
49 =head1 AUTHOR
51 Dormando E<lt>L<dormando@rydia.net>E<gt>
53 =head1 BUGS
55 None known. Could use more helpful prints, or a longer troubleshooting manual.
57 =head1 LICENSE
59 Licensed for use and redistribution under the same terms as Perl itself.
61 =cut
63 use strict;
64 use warnings;
66 use lib './lib';
67 use MogileFS::Utils;
68 use Digest::MD5;
69 use LWP::UserAgent;
71 my $util = MogileFS::Utils->new;
72 my $usage = qq{--trackers=host --domain=foo --key='/hello.jpg'
73 If FID is known, but domain/key are not known:
74 --trackers=host --fid=123456};
75 # FIXME: add "nofetch" mode that just prints paths?
76 my $c = $util->getopts($usage, qw/key=s fid=i/);
78 my $arg;
79 if ($c->{fid}) {
80 $c->{domain} ||= 'mogfiledebug-unset';
81 $arg = 'fid';
82 } else {
83 $arg = 'key';
86 my $mogc = $util->client;
87 my $details = $mogc->file_debug($arg => $c->{$arg});
88 if ($mogc->errcode) {
89 die "Error fetching fid info: " . $mogc->errstr;
92 my %parts = ();
93 my @paths = grep { $_ =~ m/^devpath_/ } keys %$details;
94 while (my ($k, $v) = each %$details) {
95 next if $k =~ m/^devpath_/;
96 if ($k =~ s/^(\w+)_//) {
97 $parts{$1}->{$k} = $v;
101 # If no paths, print something about that.
102 if (@paths) {
103 my @results;
104 # For each actual path, fetch and calculate the MD5SUM.
105 print "Fetching and summing paths...\n";
106 for my $key (@paths) {
107 my $path = $details->{$key};
108 push(@results, fetch_path($path));
110 my $hash; # detect if hashes don't match
111 my $len = $parts{fid}->{length};
112 print "No length, cannot verify content length" unless defined $len;
113 # No I don't have a good excuse for why this isn't one loop.
114 for my $res (@results) {
115 print "\nResults for path: ", $res->{path}, "\n";
116 if ($res->{res} =~ /404/) {
117 print " - ERROR: File copy is missing: ", $res->{res}, "\n";
118 next;
120 $hash = $res->{hash} unless $hash;
121 if ($hash ne $res->{hash}) {
122 print " - ERROR: Hash does not match first path!\n";
124 if (defined $len && defined $res->{length} && $len != $res->{length}) {
125 print " - ERROR: Length does not match file row!\n";
127 print " - MD5 Hash: ", $res->{hash}, "\n";
128 print " - Length: ", $res->{length}, "\n" if defined $res->{length};
129 print " - Last-Modified: ", $res->{mtime}, "\n" if defined $res->{mtime};
130 print " - HTTP result: ", $res->{res}, "\n";
132 } else {
133 print "No valid-ish paths found\n";
136 # print info from all of the queues. Raw is fine? failcount/etc.
137 print "\nTempfile and/or queue rows...\n";
138 my $found = 0;
139 for my $type (qw/tempfile replqueue delqueue rebqueue fsckqueue/) {
140 my $part = $parts{$type};
141 next unless (defined $part);
142 $found++;
143 printf("- %12s\n", $type);
144 while (my ($k, $v) = each %$part) {
145 printf(" %20s: %20s\n", $k, $v);
148 print "none.\n" unless $found;
150 # Print rest of file info like file_info
151 if (my $fid = $parts{fid}) {
152 print "\n- File Row:\n";
153 for my $item (sort keys %$fid) {
154 printf(" %8s: %20s\n", $item, $fid->{$item});
156 } else {
157 print qq{- ERROR: No file row was found!
158 File may have been deleted or never closed.
159 See above for any matching rows from tempfile or delqueue.
163 if (my $devids = $details->{devids}) {
164 print "\n- Raw devids: ", $devids, "\n";
167 if (my $hash = $details->{checksum}) {
168 print "\n- Stored checksum: ", $hash, "\n";
171 sub fetch_path {
172 my $path = shift;
173 my $ua = LWP::UserAgent->new;
174 my $ctx = Digest::MD5->new;
175 $ua->timeout(10);
176 my %toret = (length => 0);
178 my $sum_up = sub {
179 $toret{length} += length($_[0]);
180 $ctx->add($_[0]);
182 my $res = $ua->get($path, ':content_cb' => $sum_up,
183 ':read_size_hint' => 32768);
185 $toret{hash} = $ctx->hexdigest;
186 $toret{res} = $res->status_line;
187 $toret{mtime} = $res->header("Last-Modified");
188 $toret{path} = $path;
189 return \%toret;