blob: 71d8310febb2b5c1dd2374f9088ce4e4d1a6fa2c [file] [log] [blame]
#!/usr/bin/python
# Copyright 2016 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Inspection of the prefetch predictor database.
On Android, the database can be extracted using:
adb pull \
'/data/user/0/$package_name/app_chrome/Default/Network Action Predictor'
predictor_db
"""
import argparse
import datetime
import sqlite3
import os
import sys
class Entry(object):
"""Represents an entry in the predictor database."""
def __init__(
self, primary_key, proto_buffer):
from resource_prefetch_predictor_pb2 import (PrefetchData, ResourceData)
self.primary_key = primary_key
self.prefetch_data = PrefetchData()
self.prefetch_data.ParseFromString(proto_buffer)
@classmethod
def _ComputeResourceScore(cls, resource):
"""Mirrors ResourcePrefetchPredictorTables::ComputeResourceScore.
Args:
resource: ResourceData.
Return:
The resource score (int).
"""
from resource_prefetch_predictor_pb2 import (PrefetchData, ResourceData)
priority_multiplier = 1
type_multiplier = 1
if resource.priority == ResourceData.REQUEST_PRIORITY_HIGHEST:
priority_multiplier = 3
elif resource.priority == ResourceData.REQUEST_PRIORITY_MEDIUM:
priority_multiplier = 2
if resource.resource_type in (ResourceData.RESOURCE_TYPE_STYLESHEET,
ResourceData.RESOURCE_TYPE_SCRIPT):
type_multiplier = 3
elif resource.resource_type == ResourceData.RESOURCE_TYPE_FONT_RESOURCE:
type_multiplier = 2
return (100 * (priority_multiplier * 100 + type_multiplier * 10)
- resource.average_position)
@classmethod
def FromRow(cls, row):
"""Builds an entry from a database row."""
return Entry(*row)
@classmethod
def _PrettyPrintResource(cls, resource):
"""Pretty-prints a resource to stdout.
Args:
resource: ResourceData.
"""
print 'score: %d' % cls._ComputeResourceScore(resource)
print resource
def PrettyPrintCandidates(self):
"""Prints the candidates for prefetch."""
print 'primary_key: %s' % self.prefetch_data.primary_key
for resource in self.prefetch_data.resources:
confidence = float(resource.number_of_hits) / (
resource.number_of_hits + resource.number_of_misses)
if resource.number_of_hits < 2 or confidence < .7:
continue
self._PrettyPrintResource(resource)
def DumpOriginDatabaseRow(domain, primary_key, proto):
from resource_prefetch_predictor_pb2 import OriginData
entry = OriginData()
entry.ParseFromString(proto)
# For the offset, see kTimeTToMicrosecondsOffset in base/time/time.h.
last_visit_timestamp = int(entry.last_visit_time / 1e6 - 11644473600)
formatted_last_visit_time = datetime.datetime.utcfromtimestamp(
last_visit_timestamp).strftime('%Y-%m-%d %H:%M:%S')
print '''host: %s
last_visit_time: %s
origins:''' % (entry.host, formatted_last_visit_time)
for origin_stat in entry.origins:
print ''' origin: %s
number_of_hits: %d
number_of_misses: %d
consecutive_misses: %d
average_position: %f
always_access_network: %s
accessed_network: %s
''' % (origin_stat.origin, origin_stat.number_of_hits,
origin_stat.number_of_misses, origin_stat.consecutive_misses,
origin_stat.average_position, origin_stat.always_access_network,
origin_stat.accessed_network)
# The version of python sqlite3 library we have in Ubuntu 14.04 LTS doesn't
# support views but command line util does.
# TODO(alexilin): get rid of this when python sqlite3 adds view support.
def CreateCompatibleDatabaseCopy(filename):
import tempfile, shutil, subprocess
_, tmpfile = tempfile.mkstemp()
shutil.copy2(filename, tmpfile)
subprocess.call(['sqlite3', tmpfile, 'DROP VIEW MmapStatus'])
return tmpfile
def DatabaseStats(filename, host):
query_template = 'SELECT key, proto from %s'
connection = sqlite3.connect(filename)
c = connection.cursor()
print 'HOST DATABASE'
query = query_template % 'resource_prefetch_predictor_host'
entries = [Entry.FromRow(row) for row in c.execute(query)]
for x in entries:
if host is None or x.primary_key == host:
x.PrettyPrintCandidates()
print '\n\nORIGIN DATABASE'
query = query_template % 'resource_prefetch_predictor_origin'
rows = [row for row in c.execute(query)
if host is None or row.primary_key == host]
for row in rows:
DumpOriginDatabaseRow(host, *row)
def _AddProtocolBuffersPath(build_dir):
assert os.path.isdir(build_dir)
proto_dir = os.path.join(
build_dir, os.path.join('pyproto', 'chrome', 'browser', 'predictors'))
sys.path.append(proto_dir)
def main():
parser = argparse.ArgumentParser()
parser.add_argument('-f', dest='database_filename', required=True,
help='Path to the database')
parser.add_argument('-d', dest='domain', default=None, help='Domain')
parser.add_argument('--build-dir', dest='build_dir', required=True,
help='Path to the build directory.')
args = parser.parse_args()
_AddProtocolBuffersPath(args.build_dir)
try:
database_copy = CreateCompatibleDatabaseCopy(args.database_filename)
DatabaseStats(database_copy, args.domain)
finally:
if os.path.exists(database_copy):
os.remove(database_copy)
if __name__ == '__main__':
main()