--- a/netwerk/cache/nsDiskCacheStreams.cpp
+++ b/netwerk/cache/nsDiskCacheStreams.cpp
@@ -10,26 +10,24 @@
#include "nsDiskCacheStreams.h"
#include "nsCacheService.h"
#include "mozilla/FileUtils.h"
#include "nsIDiskCacheStreamInternal.h"
#include "nsThreadUtils.h"
#include "mozilla/Telemetry.h"
#include "mozilla/TimeStamp.h"
+
+
// Assumptions:
// - cache descriptors live for life of streams
// - streams will only be used by FileTransport,
// they will not be directly accessible to clients
// - overlapped I/O is NOT supported
-// we pick 16k as the max buffer size because that is the threshold above which
-// we are unable to store the data in the cache block files
-// see nsDiskCacheMap.[cpp,h]
-#define kMaxBufferSize (16 * 1024)
/******************************************************************************
* nsDiskCacheInputStream
*****************************************************************************/
class nsDiskCacheInputStream : public nsIInputStream {
public:
@@ -175,109 +173,218 @@ nsDiskCacheInputStream::ReadSegments(nsW
NS_IMETHODIMP
nsDiskCacheInputStream::IsNonBlocking(bool * nonBlocking)
{
*nonBlocking = false;
return NS_OK;
}
+
+/******************************************************************************
+ * nsDiskCacheOutputStream
+ *****************************************************************************/
+class nsDiskCacheOutputStream : public nsIOutputStream
+ , public nsIDiskCacheStreamInternal
+{
+public:
+ nsDiskCacheOutputStream( nsDiskCacheStreamIO * parent);
+ virtual ~nsDiskCacheOutputStream();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOUTPUTSTREAM
+ NS_DECL_NSIDISKCACHESTREAMINTERNAL
+
+ void ReleaseStreamIO() { NS_IF_RELEASE(mStreamIO); }
+
+private:
+ nsDiskCacheStreamIO * mStreamIO; // backpointer to parent
+ bool mClosed;
+};
+
+
+NS_IMPL_THREADSAFE_ISUPPORTS2(nsDiskCacheOutputStream,
+ nsIOutputStream,
+ nsIDiskCacheStreamInternal)
+
+nsDiskCacheOutputStream::nsDiskCacheOutputStream( nsDiskCacheStreamIO * parent)
+ : mStreamIO(parent)
+ , mClosed(false)
+{
+ NS_ADDREF(mStreamIO);
+}
+
+
+nsDiskCacheOutputStream::~nsDiskCacheOutputStream()
+{
+ Close();
+ ReleaseStreamIO();
+}
+
+
+NS_IMETHODIMP
+nsDiskCacheOutputStream::Close()
+{
+ nsresult rv = NS_OK;
+ mozilla::TimeStamp start = mozilla::TimeStamp::Now();
+
+ if (!mClosed) {
+ mClosed = true;
+ // tell parent streamIO we are closing
+ rv = mStreamIO->CloseOutputStream(this);
+ }
+
+ mozilla::Telemetry::ID id;
+ if (NS_IsMainThread())
+ id = mozilla::Telemetry::NETWORK_DISK_CACHE_OUTPUT_STREAM_CLOSE_MAIN_THREAD;
+ else
+ id = mozilla::Telemetry::NETWORK_DISK_CACHE_OUTPUT_STREAM_CLOSE;
+
+ mozilla::Telemetry::AccumulateTimeDelta(id, start);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsDiskCacheOutputStream::CloseInternal()
+{
+ nsresult rv = NS_OK;
+ mozilla::TimeStamp start = mozilla::TimeStamp::Now();
+
+ if (!mClosed) {
+ mClosed = true;
+ // tell parent streamIO we are closing
+ rv = mStreamIO->CloseOutputStreamInternal(this);
+ }
+
+ mozilla::Telemetry::ID id;
+ if (NS_IsMainThread())
+ id = mozilla::Telemetry::NETWORK_DISK_CACHE_OUTPUT_STREAM_CLOSE_INTERNAL_MAIN_THREAD;
+ else
+ id = mozilla::Telemetry::NETWORK_DISK_CACHE_OUTPUT_STREAM_CLOSE_INTERNAL;
+
+ mozilla::Telemetry::AccumulateTimeDelta(id, start);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsDiskCacheOutputStream::Flush()
+{
+ if (mClosed) return NS_BASE_STREAM_CLOSED;
+ // yeah, yeah, well get to it...eventually...
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsDiskCacheOutputStream::Write(const char *buf, uint32_t count, uint32_t *bytesWritten)
+{
+ if (mClosed) return NS_BASE_STREAM_CLOSED;
+ return mStreamIO->Write(buf, count, bytesWritten);
+}
+
+
+NS_IMETHODIMP
+nsDiskCacheOutputStream::WriteFrom(nsIInputStream *inStream, uint32_t count, uint32_t *bytesWritten)
+{
+ NS_NOTREACHED("WriteFrom");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+
+NS_IMETHODIMP
+nsDiskCacheOutputStream::WriteSegments( nsReadSegmentFun reader,
+ void * closure,
+ uint32_t count,
+ uint32_t * bytesWritten)
+{
+ NS_NOTREACHED("WriteSegments");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+
+NS_IMETHODIMP
+nsDiskCacheOutputStream::IsNonBlocking(bool * nonBlocking)
+{
+ *nonBlocking = false;
+ return NS_OK;
+}
+
+
+
/******************************************************************************
* nsDiskCacheStreamIO
*****************************************************************************/
-NS_IMPL_THREADSAFE_ISUPPORTS2(nsDiskCacheStreamIO, nsIOutputStream, nsIDiskCacheStreamInternal)
+NS_IMPL_THREADSAFE_ISUPPORTS0(nsDiskCacheStreamIO)
+
+// we pick 16k as the max buffer size because that is the threshold above which
+// we are unable to store the data in the cache block files
+// see nsDiskCacheMap.[cpp,h]
+#define kMaxBufferSize (16 * 1024)
nsDiskCacheStreamIO::nsDiskCacheStreamIO(nsDiskCacheBinding * binding)
: mBinding(binding)
+ , mOutStream(nullptr)
, mInStreamCount(0)
, mFD(nullptr)
, mStreamPos(0)
, mStreamEnd(0)
, mBufPos(0)
, mBufEnd(0)
, mBufSize(0)
, mBufDirty(false)
- , mOutputStreamIsOpen(false)
, mBuffer(nullptr)
{
mDevice = (nsDiskCacheDevice *)mBinding->mCacheEntry->CacheDevice();
// acquire "death grip" on cache service
nsCacheService *service = nsCacheService::GlobalInstance();
NS_ADDREF(service);
}
nsDiskCacheStreamIO::~nsDiskCacheStreamIO()
{
- if (mOutputStreamIsOpen) {
- nsCacheService::AssertOwnsLock();
- CloseInternal();
- }
-
- NS_ASSERTION(!mOutputStreamIsOpen, "output stream still open");
- NS_ASSERTION(mInStreamCount == 0, "input stream still open");
- NS_ASSERTION(!mFD, "file descriptor not closed");
-
- DeleteBuffer();
+ Close();
// release "death grip" on cache service
nsCacheService *service = nsCacheService::GlobalInstance();
NS_RELEASE(service);
}
-NS_IMETHODIMP
-nsDiskCacheStreamIO::WriteFrom(nsIInputStream *inStream, uint32_t count, uint32_t *bytesWritten)
-{
- NS_NOTREACHED("WriteFrom");
- return NS_ERROR_NOT_IMPLEMENTED;
-}
-
-
-NS_IMETHODIMP
-nsDiskCacheStreamIO::WriteSegments(nsReadSegmentFun reader,
- void * closure,
- uint32_t count,
- uint32_t * bytesWritten)
-{
- NS_NOTREACHED("WriteSegments");
- return NS_ERROR_NOT_IMPLEMENTED;
-}
-
-
-NS_IMETHODIMP
-nsDiskCacheStreamIO::IsNonBlocking(bool * nonBlocking)
-{
- *nonBlocking = false;
- return NS_OK;
-}
-
-
-NS_IMETHODIMP
+void
nsDiskCacheStreamIO::Close()
{
- nsCacheServiceAutoLock lock(LOCK_TELEM(NSDISKCACHESTREAMIO_CLOSEOUTPUTSTREAM));
- return CloseInternal();
+ // this should only be called from our destructor
+ // no one is interested in us anymore, so we don't need to grab any locks
+
+ // assert streams closed
+ NS_ASSERTION(!mOutStream, "output stream still open");
+ NS_ASSERTION(mInStreamCount == 0, "input stream still open");
+ NS_ASSERTION(!mFD, "file descriptor not closed");
+
+ DeleteBuffer();
}
// NOTE: called with service lock held
nsresult
nsDiskCacheStreamIO::GetInputStream(uint32_t offset, nsIInputStream ** inputStream)
{
NS_ENSURE_ARG_POINTER(inputStream);
NS_ENSURE_TRUE(offset == 0, NS_ERROR_NOT_IMPLEMENTED);
*inputStream = nullptr;
if (!mBinding) return NS_ERROR_NOT_AVAILABLE;
- if (mOutputStreamIsOpen) {
- NS_WARNING("already have the output stream open");
+ if (mOutStream) {
+ NS_WARNING("already have an output stream open");
return NS_ERROR_NOT_AVAILABLE;
}
nsresult rv;
PRFileDesc * fd = nullptr;
mStreamEnd = mBinding->mCacheEntry->DataSize();
if (mStreamEnd == 0) {
@@ -311,118 +418,90 @@ nsDiskCacheStreamIO::GetInputStream(uint
// NOTE: called with service lock held
nsresult
nsDiskCacheStreamIO::GetOutputStream(uint32_t offset, nsIOutputStream ** outputStream)
{
NS_ENSURE_ARG_POINTER(outputStream);
*outputStream = nullptr;
if (!mBinding) return NS_ERROR_NOT_AVAILABLE;
-
- NS_ASSERTION(!mOutputStreamIsOpen, "already have the output stream open");
+
+ NS_ASSERTION(!mOutStream, "already have an output stream open");
NS_ASSERTION(mInStreamCount == 0, "we already have input streams open");
- if (mOutputStreamIsOpen || mInStreamCount) return NS_ERROR_NOT_AVAILABLE;
+ if (mOutStream || mInStreamCount) return NS_ERROR_NOT_AVAILABLE;
// mBuffer lazily allocated, but might exist if a previous stream already
// created one.
mBufPos = 0;
mStreamPos = 0;
mStreamEnd = mBinding->mCacheEntry->DataSize();
- if (offset > mStreamEnd) {
- NS_WARNING("seek offset out of range");
- return NS_ERROR_INVALID_ARG;
+ nsresult rv;
+ if (offset) {
+ rv = Seek(PR_SEEK_SET, offset);
+ if (NS_FAILED(rv)) return rv;
}
-
- nsresult rv;
- // Seek and truncate at the desired offset
- if (mBinding->mRecord.DataLocationInitialized() &&
- (mBinding->mRecord.DataFile() == 0)) {
- // File storage, seek in file
- rv = OpenCacheFile(PR_WRONLY | PR_CREATE_FILE, &mFD);
- NS_ENSURE_SUCCESS(rv, rv);
- if (offset) {
- int32_t newPos = PR_Seek(mFD, offset, PR_SEEK_SET);
- if (newPos == -1) {
- return NS_ErrorAccordingToNSPR();
- }
- }
+ rv = SetEOF();
+ if (NS_FAILED(rv)) return rv;
- // Truncate at start position (offset)
- rv = nsDiskCache::Truncate(mFD, offset);
- NS_ENSURE_SUCCESS(rv, rv);
-
- mStreamPos = mStreamEnd = offset;
- UpdateFileSize();
- } else if (offset) {
- // else, read and seek in mBuffer
- rv = ReadCacheBlocks();
- NS_ENSURE_SUCCESS(rv, rv);
-
- // Start writing at the provided offset
- mBufEnd = mBufPos = offset;
- mStreamPos = mStreamEnd = offset;
- }
-
- mOutputStreamIsOpen = true;
- // return myself as the output stream
- NS_ADDREF(*outputStream = this);
+ // create a new output stream
+ mOutStream = new nsDiskCacheOutputStream(this);
+ if (!mOutStream) return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(*outputStream = mOutStream);
return NS_OK;
}
-
nsresult
nsDiskCacheStreamIO::ClearBinding()
{
nsresult rv = NS_OK;
- if (mBinding && mOutputStreamIsOpen)
+ if (mBinding && mOutStream)
rv = Flush();
mBinding = nullptr;
return rv;
}
-
-NS_IMETHODIMP
-nsDiskCacheStreamIO::CloseInternal()
+nsresult
+nsDiskCacheStreamIO::CloseOutputStream(nsDiskCacheOutputStream * outputStream)
{
- mozilla::TimeStamp start = mozilla::TimeStamp::Now();
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSDISKCACHESTREAMIO_CLOSEOUTPUTSTREAM)); // grab service lock
+ return CloseOutputStreamInternal(outputStream);
+}
- if (mOutputStreamIsOpen) {
- if (!mBinding) { // if we're severed, just clear member variables
- NS_ASSERTION(!mBufDirty, "oops");
- } else {
- nsresult rv = Flush();
- NS_ENSURE_SUCCESS(rv, rv);
- }
- mOutputStreamIsOpen = PR_FALSE;
+nsresult
+nsDiskCacheStreamIO::CloseOutputStreamInternal(
+ nsDiskCacheOutputStream * outputStream)
+{
+ nsresult rv;
+
+ if (outputStream != mOutStream) {
+ NS_WARNING("mismatched output streams");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // output stream is closing
+ if (!mBinding) { // if we're severed, just clear member variables
+ NS_ASSERTION(!mBufDirty, "oops");
+ mOutStream = nullptr;
+ outputStream->ReleaseStreamIO();
+ return NS_ERROR_NOT_AVAILABLE;
}
- // Make sure to always close the FileDescriptor
- if (mFD) {
- (void) PR_Close(mFD);
- mFD = nullptr;
- }
+ rv = Flush();
+ if (NS_FAILED(rv))
+ NS_WARNING("Flush() failed");
- mozilla::Telemetry::ID id;
- if (NS_IsMainThread())
- id = mozilla::Telemetry::NETWORK_DISK_CACHE_OUTPUT_STREAM_CLOSE_INTERNAL_MAIN_THREAD;
- else
- id = mozilla::Telemetry::NETWORK_DISK_CACHE_OUTPUT_STREAM_CLOSE_INTERNAL;
-
- mozilla::Telemetry::AccumulateTimeDelta(id, start);
-
- return NS_OK;
+ mOutStream = nullptr;
+ return rv;
}
-
-NS_IMETHODIMP
+nsresult
nsDiskCacheStreamIO::Flush()
{
- if (!mOutputStreamIsOpen) return NS_BASE_STREAM_CLOSED;
-
NS_ASSERTION(mBinding, "oops");
CACHE_LOG_DEBUG(("CACHE: Flush [%x doomed=%u]\n",
mBinding->mRecord.HashNumber(), mBinding->mDoomed));
if (!mBufDirty) {
if (mFD) {
(void) PR_Close(mFD);
@@ -509,25 +588,22 @@ nsDiskCacheStreamIO::Flush()
}
// assumptions:
// only one thread writing at a time
// never have both output and input streams open
// OnDataSizeChanged() will have already been called to update entry->DataSize()
-NS_IMETHODIMP
+nsresult
nsDiskCacheStreamIO::Write( const char * buffer,
uint32_t count,
uint32_t * bytesWritten)
{
- if (!mOutputStreamIsOpen) {
- return NS_BASE_STREAM_CLOSED;
- }
-
+ nsresult rv = NS_OK;
nsCacheServiceAutoLock lock(LOCK_TELEM(NSDISKCACHESTREAMIO_WRITE)); // grab service lock
if (!mBinding) return NS_ERROR_NOT_AVAILABLE;
if (mInStreamCount) {
// we have open input streams already
// this is an error until we support overlapped I/O
NS_WARNING("Attempting to write to cache entry with open input streams.\n");
return NS_ERROR_NOT_AVAILABLE;
@@ -584,17 +660,17 @@ nsDiskCacheStreamIO::Write( const char *
NS_ASSERTION(mBinding->mCacheEntry->DataSize() == mStreamEnd, "bad stream");
// If we have flushed to a file, update the file size
if (flushed && mFD) {
UpdateFileSize();
}
}
- return NS_OK;
+ return rv;
}
void
nsDiskCacheStreamIO::UpdateFileSize()
{
NS_ASSERTION(mFD, "nsDiskCacheStreamIO::UpdateFileSize should not have been called");
@@ -693,17 +769,17 @@ nsDiskCacheStreamIO::FlushBufferToFile()
// remove cache block storage
nsDiskCacheMap * cacheMap = mDevice->CacheMap();
rv = cacheMap->DeleteStorage(record, nsDiskCache::kData);
if (NS_FAILED(rv)) return rv;
}
record->SetDataFileGeneration(mBinding->mGeneration);
// allocate file
- rv = OpenCacheFile(PR_WRONLY | PR_CREATE_FILE, &mFD);
+ rv = OpenCacheFile(PR_RDWR | PR_CREATE_FILE, &mFD);
if (NS_FAILED(rv)) return rv;
int64_t dataSize = mBinding->mCacheEntry->PredictedDataSize();
if (dataSize != -1)
mozilla::fallocate(mFD, NS_MIN<int64_t>(dataSize, kPreallocateLimit));
}
// write buffer
@@ -741,8 +817,163 @@ nsDiskCacheStreamIO::SizeOfIncludingThis
size_t usage = aMallocSizeOf(this);
usage += aMallocSizeOf(mLocalFile);
usage += aMallocSizeOf(mFD);
usage += aMallocSizeOf(mBuffer);
return usage;
}
+
+// NOTE: called with service lock held
+nsresult
+nsDiskCacheStreamIO::Seek(int32_t whence, int32_t offset)
+{
+ int32_t newPos;
+ if (!mBinding) return NS_ERROR_NOT_AVAILABLE;
+
+ if (uint32_t(offset) > mStreamEnd) return NS_ERROR_FAILURE;
+
+ if (mBinding->mRecord.DataLocationInitialized()) {
+ if (mBinding->mRecord.DataFile() == 0) {
+ if (!mFD) {
+ // we need an mFD, we better open it now
+ nsresult rv = OpenCacheFile(PR_RDWR | PR_CREATE_FILE, &mFD);
+ if (NS_FAILED(rv)) return rv;
+ }
+ }
+ }
+
+ if (mFD) {
+ // do we have data in the buffer that needs to be flushed?
+ if (mBufDirty) {
+ // XXX optimization: are we just moving within the current buffer?
+ nsresult rv = FlushBufferToFile();
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ newPos = PR_Seek(mFD, offset, (PRSeekWhence)whence);
+ if (newPos == -1)
+ return NS_ErrorAccordingToNSPR();
+
+ mStreamPos = (uint32_t) newPos;
+ mBufPos = 0;
+ mBufEnd = 0;
+ return NS_OK;
+ }
+
+ // else, seek in mBuffer
+
+ switch(whence) {
+ case PR_SEEK_SET:
+ newPos = offset;
+ break;
+
+ case PR_SEEK_CUR: // relative from current posistion
+ newPos = offset + (uint32_t)mStreamPos;
+ break;
+
+ case PR_SEEK_END: // relative from end
+ newPos = offset + (uint32_t)mBufEnd;
+ break;
+
+ default:
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // read data into mBuffer if not read yet.
+ if (mStreamEnd && !mBufEnd) {
+ if (newPos > 0) {
+ nsresult rv = ReadCacheBlocks();
+ if (NS_FAILED(rv)) return rv;
+ }
+ }
+
+ // stream buffer sanity checks
+ NS_ASSERTION(mBufEnd <= kMaxBufferSize, "bad stream");
+ NS_ASSERTION(mBufPos <= mBufEnd, "bad stream");
+ NS_ASSERTION(mStreamPos == mBufPos, "bad stream");
+ NS_ASSERTION(mStreamEnd == mBufEnd, "bad stream");
+
+ if ((newPos < 0) || (uint32_t(newPos) > mBufEnd)) {
+ NS_WARNING("seek offset out of range");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ mStreamPos = newPos;
+ mBufPos = newPos;
+ return NS_OK;
+}
+
+
+// called only from nsDiskCacheOutputStream::Tell
+nsresult
+nsDiskCacheStreamIO::Tell(uint32_t * result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ *result = mStreamPos;
+ return NS_OK;
+}
+
+
+// NOTE: called with service lock held
+nsresult
+nsDiskCacheStreamIO::SetEOF()
+{
+ nsresult rv;
+ bool needToCloseFD = false;
+
+ NS_ASSERTION(mStreamPos <= mStreamEnd, "bad stream");
+ if (!mBinding) return NS_ERROR_NOT_AVAILABLE;
+
+ if (mBinding->mRecord.DataLocationInitialized()) {
+ if (mBinding->mRecord.DataFile() == 0) {
+ if (!mFD) {
+ // we need an mFD, we better open it now
+ rv = OpenCacheFile(PR_RDWR | PR_CREATE_FILE, &mFD);
+ if (NS_FAILED(rv)) return rv;
+ needToCloseFD = true;
+ }
+ } else {
+ // data in cache block files
+ if ((mStreamPos != 0) && (mStreamPos != mBufPos)) {
+ // only read data if there will be some left after truncation
+ rv = ReadCacheBlocks();
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // We need to make sure we reflect this change in Flush().
+ // In particular, if mStreamPos is 0 and we never write to
+ // the buffer, we want the storage to be deleted.
+ mBufDirty = true;
+ }
+ }
+
+ if (mFD) {
+ rv = nsDiskCache::Truncate(mFD, mStreamPos);
+#ifdef DEBUG
+ uint32_t oldSizeK = (mStreamEnd + 0x03FF) >> 10;
+ NS_ASSERTION(mBinding->mRecord.DataFileSize() == oldSizeK, "bad disk cache entry size");
+ } else {
+ // data stored in buffer.
+ NS_ASSERTION(mStreamEnd <= kMaxBufferSize, "buffer truncation inadequate");
+ NS_ASSERTION(mBufPos == mStreamPos, "bad stream");
+ NS_ASSERTION(mBuffer ? mBufEnd == mStreamEnd : true, "bad stream");
+#endif
+ }
+
+ NS_ASSERTION(mStreamEnd == mBinding->mCacheEntry->DataSize(), "cache entry not updated");
+ // we expect nsCacheEntryDescriptor::TransportWrapper::OpenOutputStream()
+ // to eventually update the cache entry
+
+ mStreamEnd = mStreamPos;
+ mBufEnd = mBufPos;
+
+ if (mFD) {
+ UpdateFileSize();
+ if (needToCloseFD) {
+ (void) PR_Close(mFD);
+ mFD = nullptr;
+ }
+ }
+
+ return NS_OK;
+}
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -1630,16 +1630,28 @@
"description": "Time spent (ms) during showdown deleting disk cache for 'clear private data' option"
},
"NETWORK_DISK_CACHE_REVALIDATION": {
"kind": "exponential",
"high": "10000",
"n_buckets": 10,
"description": "Total Time spent (ms) during disk cache revalidation"
},
+ "NETWORK_DISK_CACHE_OUTPUT_STREAM_CLOSE": {
+ "kind": "exponential",
+ "high": "10000",
+ "n_buckets": 10,
+ "description": "Time spent in nsDiskCacheOutputStream::Close() on non-main thread (ms)"
+ },
+ "NETWORK_DISK_CACHE_OUTPUT_STREAM_CLOSE_MAIN_THREAD": {
+ "kind": "exponential",
+ "high": "10000",
+ "n_buckets": 10,
+ "description": "Time spent in nsDiskCacheOutputStream::Close() on the main thread (ms)"
+ },
"NETWORK_DISK_CACHE_OUTPUT_STREAM_CLOSE_INTERNAL": {
"kind": "exponential",
"high": "10000",
"n_buckets": 10,
"description": "Time spent in nsDiskCacheOutputStream::CloseInternal() on non-main thread (ms)"
},
"NETWORK_DISK_CACHE_OUTPUT_STREAM_CLOSE_INTERNAL_MAIN_THREAD": {
"kind": "exponential",