Tue, 06 Jan 2015 21:39:09 +0100
Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.
michael@0 | 1 | #!/usr/bin/perl |
michael@0 | 2 | # |
michael@0 | 3 | # This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. |
michael@0 | 6 | use DBI; |
michael@0 | 7 | use CGI::Carp qw(fatalsToBrowser); |
michael@0 | 8 | use CGI::Request; |
michael@0 | 9 | use URLTimingDataSet; |
michael@0 | 10 | use File::Copy (); |
michael@0 | 11 | use strict; |
michael@0 | 12 | |
michael@0 | 13 | use vars qw($dbh $arc $dbroot); # current db, and db/archive |
michael@0 | 14 | |
michael@0 | 15 | use constant STALE_AGE => 5 * 60; # seconds |
michael@0 | 16 | |
michael@0 | 17 | # show a chart of this run; turned off in automated tests, and where |
michael@0 | 18 | # an installation hasn't set up the required modules and libraries |
michael@0 | 19 | use constant SHOW_CHART => 0; |
michael@0 | 20 | |
michael@0 | 21 | sub createArchiveMetaTable { |
michael@0 | 22 | my $table = "tMetaTable"; |
michael@0 | 23 | return if -e "$dbroot/archive/$table"; # don't create it if it exists |
michael@0 | 24 | warn "createMetaTable:\t$dbroot/archive/$table"; |
michael@0 | 25 | mkdir "$dbroot/archive" unless -d "$dbroot/archive"; |
michael@0 | 26 | my ($sth, $sql); |
michael@0 | 27 | $sql = qq{ |
michael@0 | 28 | CREATE TABLE tMetaTable |
michael@0 | 29 | (DATETIME CHAR(14), LASTPING CHAR(14), |
michael@0 | 30 | ID CHAR(8), INDEX INTEGER, |
michael@0 | 31 | CUR_IDX INTEGER, CUR_CYC INTEGER, |
michael@0 | 32 | CUR_CONTENT CHAR(128), STATE INTEGER, |
michael@0 | 33 | BLESSED INTEGER, MAXCYC INTEGER, |
michael@0 | 34 | MAXIDX INTEGER, REPLACE INTEGER, |
michael@0 | 35 | NOCACHE INTEGER, DELAY INTEGER, |
michael@0 | 36 | REMOTE_USER CHAR(16), HTTP_USER_AGENT CHAR(128), |
michael@0 | 37 | REMOTE_ADDR CHAR(15), USER_EMAIL CHAR(32), |
michael@0 | 38 | USER_COMMENT CHAR(256) |
michael@0 | 39 | ) |
michael@0 | 40 | }; |
michael@0 | 41 | $sth = $arc->prepare($sql); |
michael@0 | 42 | $sth->execute(); |
michael@0 | 43 | $sth->finish(); |
michael@0 | 44 | warn 'created archive meta table'; |
michael@0 | 45 | return 1; |
michael@0 | 46 | } |
michael@0 | 47 | |
michael@0 | 48 | |
michael@0 | 49 | sub purgeStaleEntries { |
michael@0 | 50 | my $id = shift; |
michael@0 | 51 | my $metatable = "tMetaTable"; |
michael@0 | 52 | |
michael@0 | 53 | # first, remove dead stuff |
michael@0 | 54 | my $sql = qq{SELECT * FROM $metatable |
michael@0 | 55 | WHERE STATE = "INIT" OR STATE = "OPEN"}; |
michael@0 | 56 | my $sth = $dbh->prepare($sql); |
michael@0 | 57 | $sth->execute(); |
michael@0 | 58 | my $now = time(); |
michael@0 | 59 | my $status; |
michael@0 | 60 | while (my @data = $sth->fetchrow_array()) { |
michael@0 | 61 | my $age = $now - timestamp2Time($data[1]); |
michael@0 | 62 | # if OPEN or INIT, and not heard from in 10 minutes, then it's never coming |
michael@0 | 63 | # back here again. Delete the entry. Whine in the error_log. |
michael@0 | 64 | if ($age > STALE_AGE) { |
michael@0 | 65 | warn "deleting stale record+table, id = $data[2], last = $data[1], @data"; |
michael@0 | 66 | $dbh->do( qq(DELETE FROM $metatable WHERE ID = "$data[2]") ); |
michael@0 | 67 | $dbh->do("DROP TABLE t" . $data[2]); |
michael@0 | 68 | } |
michael@0 | 69 | $status .= "$age @data\n"; |
michael@0 | 70 | } |
michael@0 | 71 | $sth->finish(); |
michael@0 | 72 | |
michael@0 | 73 | # now move any COMPLETE records to archive |
michael@0 | 74 | $sql = qq{SELECT * FROM $metatable}; |
michael@0 | 75 | $sth = $dbh->prepare($sql); |
michael@0 | 76 | $sth->execute(); |
michael@0 | 77 | $now = time(); |
michael@0 | 78 | while (my @data = $sth->fetchrow_array()) { |
michael@0 | 79 | my $age = $now - timestamp2Time($data[1]); |
michael@0 | 80 | # This keeps the "live" entries from growing too slow. |
michael@0 | 81 | # If COMPLETE and older than 10 minutes, move to archive. |
michael@0 | 82 | if ($age > STALE_AGE) { |
michael@0 | 83 | warn "moving COMPLETE record+table, id = $data[2], last = $data[1], @data"; |
michael@0 | 84 | moveRecordToArchive($data[2], \@data); |
michael@0 | 85 | $dbh->do( qq(DELETE FROM $metatable WHERE ID = "$data[2]") ); |
michael@0 | 86 | } |
michael@0 | 87 | } |
michael@0 | 88 | $sth->finish(); |
michael@0 | 89 | |
michael@0 | 90 | |
michael@0 | 91 | if (!SHOW_CHART) { |
michael@0 | 92 | # Don't move it if showing a chart. (Otherwise, if showing a |
michael@0 | 93 | # a chart, I'd have to do a little extra work to make sure I |
michael@0 | 94 | # didn't yank the record away from the IMG request) |
michael@0 | 95 | $sql = qq{SELECT * FROM $metatable WHERE ID = "$id"}; |
michael@0 | 96 | $sth = $dbh->prepare($sql); |
michael@0 | 97 | $sth->execute(); |
michael@0 | 98 | while (my @data = $sth->fetchrow_array()) { |
michael@0 | 99 | warn "moving COMPLETE record+table, id = $id, @data\n"; |
michael@0 | 100 | moveRecordToArchive($data[2], \@data); |
michael@0 | 101 | $dbh->do( qq(DELETE FROM $metatable WHERE ID = "$data[2]") ); |
michael@0 | 102 | } |
michael@0 | 103 | } |
michael@0 | 104 | $sth->finish(); |
michael@0 | 105 | } |
michael@0 | 106 | |
michael@0 | 107 | |
michael@0 | 108 | sub moveRecordToArchive { |
michael@0 | 109 | my $id = shift || die "no id"; |
michael@0 | 110 | my $dataref = shift || die "no dataref"; |
michael@0 | 111 | createArchiveMetaTable(); # if it doesn't exist |
michael@0 | 112 | insertIntoMetaTable($dataref); |
michael@0 | 113 | File::Copy::move("$dbroot/t$id", "$dbroot/archive/t$id"); |
michael@0 | 114 | } |
michael@0 | 115 | |
michael@0 | 116 | |
michael@0 | 117 | sub insertIntoMetaTable { |
michael@0 | 118 | my $dataref = shift || die "no dataref"; |
michael@0 | 119 | my $table = "tMetaTable"; |
michael@0 | 120 | my ($sth, $sql); |
michael@0 | 121 | $sql = qq{ |
michael@0 | 122 | INSERT INTO $table |
michael@0 | 123 | (DATETIME, LASTPING, ID, |
michael@0 | 124 | INDEX, CUR_IDX, CUR_CYC, |
michael@0 | 125 | CUR_CONTENT, STATE, BLESSED, |
michael@0 | 126 | MAXCYC, MAXIDX, REPLACE, |
michael@0 | 127 | NOCACHE, DELAY, REMOTE_USER, |
michael@0 | 128 | HTTP_USER_AGENT, REMOTE_ADDR, USER_EMAIL, |
michael@0 | 129 | USER_COMMENT |
michael@0 | 130 | ) |
michael@0 | 131 | VALUES (?,?,?,?, |
michael@0 | 132 | ?,?,?,?, |
michael@0 | 133 | ?,?,?,?, |
michael@0 | 134 | ?,?,?,?, |
michael@0 | 135 | ?,?,?) |
michael@0 | 136 | }; |
michael@0 | 137 | $sth = $arc->prepare($sql); |
michael@0 | 138 | $sth->execute(@$dataref); |
michael@0 | 139 | $sth->finish(); |
michael@0 | 140 | } |
michael@0 | 141 | |
michael@0 | 142 | |
michael@0 | 143 | sub timestamp2Time ($) { |
michael@0 | 144 | my $str = shift; |
michael@0 | 145 | use Time::Local (); |
michael@0 | 146 | my @datetime = reverse unpack 'A4A2A2A2A2A2', $str; |
michael@0 | 147 | --$datetime[4]; # month: 0-11 |
michael@0 | 148 | return Time::Local::timelocal(@datetime); |
michael@0 | 149 | } |
michael@0 | 150 | |
michael@0 | 151 | |
michael@0 | 152 | sub serializeDataSet { |
michael@0 | 153 | # package up this data for storage elsewhere |
michael@0 | 154 | my $rs = shift; |
michael@0 | 155 | my $data = "avgmedian|" . $rs->{avgmedian}; |
michael@0 | 156 | $data .= "|average|" . $rs->{average}; |
michael@0 | 157 | $data .= "|minimum|" . $rs->{minimum}; |
michael@0 | 158 | $data .= "|maximum|" . $rs->{maximum}; |
michael@0 | 159 | $_ = $rs->as_string; |
michael@0 | 160 | s/^\s+//gs; |
michael@0 | 161 | s/\s+\n$//gs; |
michael@0 | 162 | s/\s*\n/\|/gs; # fold newlines |
michael@0 | 163 | s/\|\s+/\|/gs; |
michael@0 | 164 | s/\s+/;/gs; |
michael@0 | 165 | return $data . ":" . $_; |
michael@0 | 166 | } |
michael@0 | 167 | |
michael@0 | 168 | # |
michael@0 | 169 | # handle the request |
michael@0 | 170 | # |
michael@0 | 171 | my $request = new CGI::Request; |
michael@0 | 172 | my $id = $request->param('id'); #XXX need to check for valid parameter id |
michael@0 | 173 | my $rs = URLTimingDataSet->new($id); |
michael@0 | 174 | |
michael@0 | 175 | print "Content-type: text/html\n\n"; |
michael@0 | 176 | |
michael@0 | 177 | # This sucks: we'll let the test time out to avoid crash-on-shutdown bugs |
michael@0 | 178 | print "<html><body onload='window.close();'>"; |
michael@0 | 179 | # |
michael@0 | 180 | # dump some stats for tinderbox to snarf |
michael@0 | 181 | # |
michael@0 | 182 | print "<script>\n"; |
michael@0 | 183 | print "if (window.dump) dump('"; |
michael@0 | 184 | print "Starting Page Load Test\\n\\\n"; |
michael@0 | 185 | print "Test id: $id\\n\\\n"; |
michael@0 | 186 | print "Avg. Median : ", $rs->{avgmedian}, " msec\\n\\\n"; |
michael@0 | 187 | print "Average : ", $rs->{average}, " msec\\n\\\n"; |
michael@0 | 188 | print "Minimum : ", $rs->{minimum}, " msec\\n\\\n"; |
michael@0 | 189 | print "Maximum : ", $rs->{maximum}, " msec\\n\\\n"; |
michael@0 | 190 | print "IDX PATH AVG MED MAX MIN TIMES ...\\n\\\n"; |
michael@0 | 191 | if ($request->param('sort')) { |
michael@0 | 192 | $_ = $rs->as_string_sorted(); |
michael@0 | 193 | } else { |
michael@0 | 194 | $_ = $rs->as_string(); |
michael@0 | 195 | } |
michael@0 | 196 | # |
michael@0 | 197 | # Terminate raw newlines with '\n\' so we don't have an unterminated string literal. |
michael@0 | 198 | # |
michael@0 | 199 | s/\n/\\n\\\n/g; |
michael@0 | 200 | print $_; |
michael@0 | 201 | print "(tinderbox dropping follows)\\n\\\n"; |
michael@0 | 202 | print "_x_x_mozilla_page_load," , $rs->{avgmedian}, ",", $rs->{maximum}, ",", $rs->{minimum}, "\\n\\\n"; |
michael@0 | 203 | # |
michael@0 | 204 | # package up this data for storage elsewhere |
michael@0 | 205 | # |
michael@0 | 206 | my $data = serializeDataSet($rs); |
michael@0 | 207 | print "_x_x_mozilla_page_load_details,", $data, "\\n\\\n"; |
michael@0 | 208 | # |
michael@0 | 209 | # average median |
michael@0 | 210 | # |
michael@0 | 211 | #print "TinderboxPrint:<a title=\"Avg. of the median per url pageload time.\" href=\"http://tegu.mozilla.org/graph/query.cgi?tbox=spider&testname=pageload&autoscale=1&days=7&avg=1\">Tp:", $rs->{avgmedian}, "ms</a>", "\\n\\\n"; |
michael@0 | 212 | print "');"; |
michael@0 | 213 | print "</script></body></html>\n"; |
michael@0 | 214 | |
michael@0 | 215 | |
michael@0 | 216 | # |
michael@0 | 217 | # If this is SurfingSafari, then catch a wave and you're sitting on top of the world!! |
michael@0 | 218 | # (and also blat this out to tegu, cause we got no 'dump' statement. |
michael@0 | 219 | # |
michael@0 | 220 | if ($request->cgi->var("HTTP_USER_AGENT") =~ /Safari/) { |
michael@0 | 221 | my %machineMap = |
michael@0 | 222 | ( |
michael@0 | 223 | "10.169.105.26" => "boxset", |
michael@0 | 224 | "10.169.105.21" => "pawn" |
michael@0 | 225 | ); |
michael@0 | 226 | my $ip = $request->cgi->var('REMOTE_ADDR'); |
michael@0 | 227 | my $machine = $machineMap{$ip}; |
michael@0 | 228 | my $res = eval q{ |
michael@0 | 229 | use LWP::UserAgent; |
michael@0 | 230 | use HTTP::Request::Common qw(POST); |
michael@0 | 231 | my $ua = LWP::UserAgent->new; |
michael@0 | 232 | $ua->timeout(10); # seconds |
michael@0 | 233 | my $req = POST('http://tegu.mozilla.org/graph/collect.cgi', |
michael@0 | 234 | [testname => 'pageload', |
michael@0 | 235 | tbox => "$machine" . "-aux", |
michael@0 | 236 | value => $rs->{avgmedian}, |
michael@0 | 237 | data => $data]); |
michael@0 | 238 | my $res = $ua->request($req); |
michael@0 | 239 | return $res; |
michael@0 | 240 | }; |
michael@0 | 241 | if ($@) { |
michael@0 | 242 | warn "Failed to submit startup results: $@"; |
michael@0 | 243 | } else { |
michael@0 | 244 | warn "Startup results submitted to server: \n", |
michael@0 | 245 | $res->status_line, "\n", $res->content, "\n"; |
michael@0 | 246 | } |
michael@0 | 247 | } |
michael@0 | 248 | |
michael@0 | 249 | |
michael@0 | 250 | if ($request->param('purge')) { |
michael@0 | 251 | # now move any old stuff into archive and clean stale entries |
michael@0 | 252 | # just going with the simple approach of "whoever sees old entries |
michael@0 | 253 | # first, cleans em up, whether they 'own' them or not". Hopefully, |
michael@0 | 254 | # the default locking will be sufficient to prevent a race. |
michael@0 | 255 | close(STDOUT); |
michael@0 | 256 | sleep(1); |
michael@0 | 257 | $dbroot = "db"; |
michael@0 | 258 | $dbh = DBI->connect("DBI:CSV:f_dir=./$dbroot", |
michael@0 | 259 | {RaiseError => 1, AutoCommit => 1}) |
michael@0 | 260 | || die "Cannot connect: " . $DBI::errstr; |
michael@0 | 261 | $arc = DBI->connect("DBI:CSV:f_dir=./$dbroot/archive", |
michael@0 | 262 | {RaiseError => 1, AutoCommit => 1}) |
michael@0 | 263 | || die "Cannot connect: " . $DBI::errstr; |
michael@0 | 264 | purgeStaleEntries($id); |
michael@0 | 265 | $dbh->disconnect(); |
michael@0 | 266 | $arc->disconnect(); |
michael@0 | 267 | } |
michael@0 | 268 | |
michael@0 | 269 | exit 0; |