From f37f04f61b8bdaa6b3a43ff72a359281b741f11f Mon Sep 17 00:00:00 2001 From: "brandonsalmon@chromium.org" Date: Thu, 10 Jul 2014 21:18:00 +0000 Subject: [PATCH] Adding DiskBasedCertCache to HttpCache (+UMA). Review URL: https://codereview.chromium.org/356953003 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@282429 0039d316-1c4b-4281-b951-d872f2087c98 --- net/http/http_cache.cc | 12 +- net/http/http_cache.h | 5 + net/http/http_cache_transaction.cc | 170 ++++++++++++++++++++++++ net/http/http_cache_transaction.h | 5 + tools/metrics/histograms/histograms.xml | 61 +++++++++ 5 files changed, 252 insertions(+), 1 deletion(-) diff --git a/net/http/http_cache.cc b/net/http/http_cache.cc index ef2e3fdd231acc..c45ab4118ca0ce 100644 --- a/net/http/http_cache.cc +++ b/net/http/http_cache.cc @@ -33,6 +33,7 @@ #include "net/base/net_errors.h" #include "net/base/upload_data_stream.h" #include "net/disk_cache/disk_cache.h" +#include "net/http/disk_based_cert_cache.h" #include "net/http/disk_cache_based_quic_server_info.h" #include "net/http/http_cache_transaction.h" #include "net/http/http_network_layer.h" @@ -45,6 +46,11 @@ namespace { +bool UseCertCache() { + return base::FieldTrialList::FindFullName("CertCacheTrial") == + "ExperimentGroup"; +} + // Adaptor to delete a file on a worker thread. void DeletePath(base::FilePath path) { base::DeleteFile(path, false); @@ -346,6 +352,7 @@ HttpCache::~HttpCache() { // Before deleting pending_ops_, we have to make sure that the disk cache is // done with said operations, or it will attempt to use deleted data. + cert_cache_.reset(); disk_cache_.reset(); PendingOpsMap::iterator pending_it = pending_ops_.begin(); @@ -1168,8 +1175,11 @@ void HttpCache::OnBackendCreated(int result, PendingOp* pending_op) { // work items. The first call saves the backend and releases the factory, // and the last call clears building_backend_. backend_factory_.reset(); // Reclaim memory. - if (result == OK) + if (result == OK) { disk_cache_ = pending_op->backend.Pass(); + if (UseCertCache()) + cert_cache_.reset(new DiskBasedCertCache(disk_cache_.get())); + } } if (!pending_op->pending_queue.empty()) { diff --git a/net/http/http_cache.h b/net/http/http_cache.h index 752b98a6715b0f..5bdda8a1f9c89a 100644 --- a/net/http/http_cache.h +++ b/net/http/http_cache.h @@ -44,6 +44,7 @@ class Entry; namespace net { class CertVerifier; +class DiskBasedCertCache; class HostResolver; class HttpAuthHandlerFactory; class HttpNetworkSession; @@ -142,6 +143,8 @@ class NET_EXPORT HttpCache : public HttpTransactionFactory, HttpTransactionFactory* network_layer() { return network_layer_.get(); } + DiskBasedCertCache* cert_cache() const { return cert_cache_.get(); } + // Retrieves the cache backend for this HttpCache instance. If the backend // is not initialized yet, this method will initialize it. The return value is // a network error code, and it could be ERR_IO_PENDING, in which case the @@ -405,6 +408,8 @@ class NET_EXPORT HttpCache : public HttpTransactionFactory, scoped_ptr disk_cache_; + scoped_ptr cert_cache_; + // The set of active entries indexed by cache key. ActiveEntriesMap active_entries_; diff --git a/net/http/http_cache_transaction.cc b/net/http/http_cache_transaction.cc index bf791118be88b7..aba792330bf4dc 100644 --- a/net/http/http_cache_transaction.cc +++ b/net/http/http_cache_transaction.cc @@ -32,6 +32,7 @@ #include "net/base/upload_data_stream.h" #include "net/cert/cert_status_flags.h" #include "net/disk_cache/disk_cache.h" +#include "net/http/disk_based_cert_cache.h" #include "net/http/http_network_session.h" #include "net/http/http_request_info.h" #include "net/http/http_response_headers.h" @@ -47,6 +48,111 @@ using base::TimeTicks; namespace { +// Stores data relevant to the statistics of writing and reading entire +// certificate chains using DiskBasedCertCache. |num_pending_ops| is the number +// of certificates in the chain that have pending operations in the +// DiskBasedCertCache. |start_time| is the time that the read and write +// commands began being issued to the DiskBasedCertCache. +// TODO(brandonsalmon): Remove this when it is no longer necessary to +// collect data. +class SharedChainData : public base::RefCounted { + public: + SharedChainData(int num_ops, TimeTicks start) + : num_pending_ops(num_ops), start_time(start) {} + + int num_pending_ops; + TimeTicks start_time; + + private: + friend class base::RefCounted; + ~SharedChainData() {} + DISALLOW_COPY_AND_ASSIGN(SharedChainData); +}; + +// Used to obtain a cache entry key for an OSCertHandle. +// TODO(brandonsalmon): Remove this when cache keys are stored +// and no longer have to be recomputed to retrieve the OSCertHandle +// from the disk. +std::string GetCacheKeyForCert(net::X509Certificate::OSCertHandle cert_handle) { + net::SHA1HashValue fingerprint = + net::X509Certificate::CalculateFingerprint(cert_handle); + + return "cert:" + + base::HexEncode(fingerprint.data, arraysize(fingerprint.data)); +} + +// |dist_from_root| indicates the position of the read certificate in the +// certificate chain, 0 indicating it is the root. |is_leaf| indicates +// whether or not the read certificate was the leaf of the chain. +// |shared_chain_data| contains data shared by each certificate in +// the chain. +void OnCertReadIOComplete( + int dist_from_root, + bool is_leaf, + const scoped_refptr& shared_chain_data, + net::X509Certificate::OSCertHandle cert_handle) { + // If |num_pending_ops| is one, this was the last pending read operation + // for this chain of certificates. The total time used to read the chain + // can be calculated by subtracting the starting time from Now(). + shared_chain_data->num_pending_ops--; + if (!shared_chain_data->num_pending_ops) { + const TimeDelta read_chain_wait = + TimeTicks::Now() - shared_chain_data->start_time; + UMA_HISTOGRAM_CUSTOM_TIMES("DiskBasedCertCache.ChainReadTime", + read_chain_wait, + base::TimeDelta::FromMilliseconds(1), + base::TimeDelta::FromMinutes(10), + 50); + } + + bool success = (cert_handle != NULL); + if (is_leaf) + UMA_HISTOGRAM_BOOLEAN("DiskBasedCertCache.CertIoReadSuccessLeaf", success); + + if (success) + UMA_HISTOGRAM_CUSTOM_COUNTS( + "DiskBasedCertCache.CertIoReadSuccess", dist_from_root, 0, 10, 7); + else + UMA_HISTOGRAM_CUSTOM_COUNTS( + "DiskBasedCertCache.CertIoReadFailure", dist_from_root, 0, 10, 7); +} + +// |dist_from_root| indicates the position of the written certificate in the +// certificate chain, 0 indicating it is the root. |is_leaf| indicates +// whether or not the written certificate was the leaf of the chain. +// |shared_chain_data| contains data shared by each certificate in +// the chain. +void OnCertWriteIOComplete( + int dist_from_root, + bool is_leaf, + const scoped_refptr& shared_chain_data, + const std::string& key) { + // If |num_pending_ops| is one, this was the last pending write operation + // for this chain of certificates. The total time used to write the chain + // can be calculated by subtracting the starting time from Now(). + shared_chain_data->num_pending_ops--; + if (!shared_chain_data->num_pending_ops) { + const TimeDelta write_chain_wait = + TimeTicks::Now() - shared_chain_data->start_time; + UMA_HISTOGRAM_CUSTOM_TIMES("DiskBasedCertCache.ChainWriteTime", + write_chain_wait, + base::TimeDelta::FromMilliseconds(1), + base::TimeDelta::FromMinutes(10), + 50); + } + + bool success = !key.empty(); + if (is_leaf) + UMA_HISTOGRAM_BOOLEAN("DiskBasedCertCache.CertIoWriteSuccessLeaf", success); + + if (success) + UMA_HISTOGRAM_CUSTOM_COUNTS( + "DiskBasedCertCache.CertIoWriteSuccess", dist_from_root, 0, 10, 7); + else + UMA_HISTOGRAM_CUSTOM_COUNTS( + "DiskBasedCertCache.CertIoWriteFailure", dist_from_root, 0, 10, 7); +} + // From http://tools.ietf.org/html/draft-ietf-httpbis-p6-cache-21#section-6 // a "non-error response" is one with a 2xx (Successful) or 3xx // (Redirection) status code. @@ -1495,6 +1601,10 @@ int HttpCache::Transaction::DoCacheReadResponseComplete(int result) { return OnCacheReadError(result, true); } + // cert_cache() will be null if the CertCacheTrial field trial is disabled. + if (cache_->cert_cache() && response_.ssl_info.is_valid()) + ReadCertChain(); + // Some resources may have slipped in as truncated when they're not. int current_size = entry_->disk_entry->GetDataSize(kResponseContentIndex); if (response_.headers->GetContentLength() == current_size) @@ -1707,6 +1817,62 @@ int HttpCache::Transaction::DoCacheWriteDataComplete(int result) { //----------------------------------------------------------------------------- +void HttpCache::Transaction::ReadCertChain() { + std::string key = + GetCacheKeyForCert(response_.ssl_info.cert->os_cert_handle()); + const X509Certificate::OSCertHandles& intermediates = + response_.ssl_info.cert->GetIntermediateCertificates(); + int dist_from_root = intermediates.size(); + + scoped_refptr shared_chain_data( + new SharedChainData(intermediates.size() + 1, TimeTicks::Now())); + cache_->cert_cache()->Get(key, + base::Bind(&OnCertReadIOComplete, + dist_from_root, + true /* is leaf */, + shared_chain_data)); + + for (X509Certificate::OSCertHandles::const_iterator it = + intermediates.begin(); + it != intermediates.end(); + ++it) { + --dist_from_root; + key = GetCacheKeyForCert(*it); + cache_->cert_cache()->Get(key, + base::Bind(&OnCertReadIOComplete, + dist_from_root, + false /* is not leaf */, + shared_chain_data)); + } + DCHECK_EQ(0, dist_from_root); +} + +void HttpCache::Transaction::WriteCertChain() { + const X509Certificate::OSCertHandles& intermediates = + response_.ssl_info.cert->GetIntermediateCertificates(); + int dist_from_root = intermediates.size(); + + scoped_refptr shared_chain_data( + new SharedChainData(intermediates.size() + 1, TimeTicks::Now())); + cache_->cert_cache()->Set(response_.ssl_info.cert->os_cert_handle(), + base::Bind(&OnCertWriteIOComplete, + dist_from_root, + true /* is leaf */, + shared_chain_data)); + for (X509Certificate::OSCertHandles::const_iterator it = + intermediates.begin(); + it != intermediates.end(); + ++it) { + --dist_from_root; + cache_->cert_cache()->Set(*it, + base::Bind(&OnCertWriteIOComplete, + dist_from_root, + false /* is not leaf */, + shared_chain_data)); + } + DCHECK_EQ(0, dist_from_root); +} + void HttpCache::Transaction::SetRequest(const BoundNetLog& net_log, const HttpRequestInfo* request) { net_log_ = net_log; @@ -2327,6 +2493,10 @@ int HttpCache::Transaction::WriteResponseInfoToEntry(bool truncated) { return OK; } + // cert_cache() will be null if the CertCacheTrial field trial is disabled. + if (cache_->cert_cache() && response_.ssl_info.is_valid()) + WriteCertChain(); + // When writing headers, we normally only write the non-transient // headers; when in record mode, record everything. bool skip_transient_headers = (cache_->mode() != RECORD); diff --git a/net/http/http_cache_transaction.h b/net/http/http_cache_transaction.h index fd1018bb500687..8159edc983bd4d 100644 --- a/net/http/http_cache_transaction.h +++ b/net/http/http_cache_transaction.h @@ -267,6 +267,11 @@ class HttpCache::Transaction : public HttpTransaction { int DoCacheWriteData(int num_bytes); int DoCacheWriteDataComplete(int result); + // These functions are involved in a field trial testing storing certificates + // in seperate entries from the HttpResponseInfo. + void ReadCertChain(); + void WriteCertChain(); + // Sets request_ and fields derived from it. void SetRequest(const BoundNetLog& net_log, const HttpRequestInfo* request); diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml index b460be5edc9779..24273456cfaf2d 100644 --- a/tools/metrics/histograms/histograms.xml +++ b/tools/metrics/histograms/histograms.xml @@ -3622,6 +3622,51 @@ Therefore, the affected-histogram name has to have at least one dot in it. + + brandonsalmon@chromium.org + + Records information about DiskBasedCertCache operations with respect to + certificate chain positions. Zero indicates that a certificate is root, one + indicates that it is the first intermediate certificate, etc. + + + + + brandonsalmon@chromium.org + + Whether or not the leaf certificate of a certificate chain was successfuly + read from the disk cache. + + + + + brandonsalmon@chromium.org + + Whether or not the leaf certificate of a certificate chain was successfully + written to the disk cache. + + + + + brandonsalmon@chromium.org + + Measures the wall clock time spent reading a certificate chain. The starting + time is when the read command is issued, and the ending time is when all of + the certificates in the chain have been read into memory. + + + + + brandonsalmon@chromium.org + + Measures the wall clock time spent writing a certificate chain to disk. The + starting time is when the write command is issued, and the ending time is + when all the certificates in the chain have been written to disk. + + + rvargas@chromium.org The age of the cache's files (wall time). @@ -35789,6 +35834,10 @@ Therefore, the affected-histogram name has to have at least one dot in it. + + + + @@ -48172,6 +48221,18 @@ Therefore, the affected-histogram name has to have at least one dot in it. + + + + + + + +