blob: 47403258732776b35e460a4c9eb8e72383fb731f [file] [log] [blame]
// Copyright 2017 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.
#ifndef CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_MANAGER_COMMON_H_
#define CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_MANAGER_COMMON_H_
#include <memory>
#include <string>
#include "base/files/file_path.h"
#include "base/optional.h"
#include "base/time/time.h"
#include "build/build_config.h"
namespace content {
class BrowserContext;
} // namespace content
namespace webrtc_event_logging {
// This file is intended for:
// 1. Code shared between WebRtcEventLogManager, WebRtcLocalEventLogManager
// and WebRtcRemoteEventLogManager.
// 2. Code specific to either of the above classes, but which also needs
// to be seen by unit tests (such as constants).
extern const size_t kWebRtcEventLogManagerUnlimitedFileSize;
extern const size_t kDefaultMaxLocalLogFileSizeBytes;
extern const size_t kMaxNumberLocalWebRtcEventLogFiles;
extern const size_t kMaxRemoteLogFileSizeBytes;
extern const int kMaxOutputPeriodMs;
// Maximum size for a response from Crash, which is the upload ID.
extern const size_t kWebRtcEventLogMaxUploadIdBytes;
// The number of digits required to encode a remote-bound log ID.
extern const size_t kWebRtcEventLogIdLength;
// Min/max legal web-app IDs.
extern const size_t kMinWebRtcEventLogWebAppId;
extern const size_t kMaxWebRtcEventLogWebAppId;
// Sentinel value, guaranteed not to fall inside the range of min-max valid IDs.
extern const size_t kInvalidWebRtcEventLogWebAppId;
// Limit over the number of concurrently active (currently being written to
// disk) remote-bound log files. This limits IO operations, and so it is
// applied globally (all browser contexts are limited together).
extern const size_t kMaxActiveRemoteBoundWebRtcEventLogs;
// Limit over the number of pending logs (logs stored on disk and awaiting to
// be uploaded to a remote server). This limit avoids excessive storage. If a
// user chooses to have multiple profiles (and hence browser contexts) on a
// system, it is assumed that the user has enough storage to accommodate
// the increased storage consumption that comes with it. Therefore, this
// limit is applied per browser context.
extern const size_t kMaxPendingRemoteBoundWebRtcEventLogs;
// Max number of history files that may be kept; after this number is exceeded,
// the oldest logs should be pruned.
extern const size_t kMaxWebRtcEventLogHistoryFiles;
// Overhead incurred by GZIP due to its header and footer.
extern const size_t kGzipOverheadBytes;
// Remote-bound log files' names will be of the format:
// [prefix]_[web_app_id]_[log_id].[ext]
// Where:
// * |prefix| is equal to kRemoteBoundWebRtcEventLogFileNamePrefix.
// * |web_app_id| is a number between kMinWebRtcEventLogWebAppId and
// kMaxWebRtcEventLogWebAppId, with zero padding.
// * |log_id| is composed of 32 random characters from '0'-'9' and 'A'-'F'.
// * |ext| is the extension determined by the used LogCompressor::Factory,
// which will be either kWebRtcEventLogUncompressedExtension or
// kWebRtcEventLogGzippedExtension.
extern const char kRemoteBoundWebRtcEventLogFileNamePrefix[];
extern const base::FilePath::CharType kWebRtcEventLogUncompressedExtension[];
extern const base::FilePath::CharType kWebRtcEventLogGzippedExtension[];
// Logs themselves are kept on disk for kRemoteBoundWebRtcEventLogsMaxRetention,
// or until uploaded. Smaller history files are kept for a longer time, allowing
// Chrome to display on chrome://webrtc-logs/ that these files were captured
// and later uploaded.
extern const base::FilePath::CharType kWebRtcEventLogHistoryExtension[];
// Remote-bound event logs will not be uploaded if the time since their last
// modification (meaning the time when they were completed) exceeds this value.
// Such expired files will be purged from disk when examined.
extern const base::TimeDelta kRemoteBoundWebRtcEventLogsMaxRetention;
// StartRemoteLogging could fail for several reasons, but we only report
// individually those failures that relate to either bad parameters, or calls
// at a time that makes no sense. Anything else would leak information to
// the JS application (too many pending logs, etc.), and is not actionable
// anyhow.
// These are made globally visible so that unit tests may check for them.
extern const char kStartRemoteLoggingFailureFeatureDisabled[];
extern const char kStartRemoteLoggingFailureUnlimitedSizeDisallowed[];
extern const char kStartRemoteLoggingFailureMaxSizeTooSmall[];
extern const char kStartRemoteLoggingFailureMaxSizeTooLarge[];
extern const char kStartRemoteLoggingFailureOutputPeriodMsTooLarge[];
extern const char kStartRemoteLoggingFailureIllegalWebAppId[];
extern const char kStartRemoteLoggingFailureUnknownOrInactivePeerConnection[];
extern const char kStartRemoteLoggingFailureAlreadyLogging[];
extern const char kStartRemoteLoggingFailureGeneric[];
// For a given Chrome session, this is a unique key for PeerConnections.
// It's not, however, unique between sessions (after Chrome is restarted).
struct WebRtcEventLogPeerConnectionKey {
using BrowserContextId = uintptr_t;
constexpr WebRtcEventLogPeerConnectionKey()
: WebRtcEventLogPeerConnectionKey(
/* render_process_id = */ 0,
/* lid = */ 0,
reinterpret_cast<BrowserContextId>(nullptr)) {}
constexpr WebRtcEventLogPeerConnectionKey(int render_process_id,
int lid,
BrowserContextId browser_context_id)
: render_process_id(render_process_id),
lid(lid),
browser_context_id(browser_context_id) {}
bool operator==(const WebRtcEventLogPeerConnectionKey& other) const {
// Each RPH is associated with exactly one BrowserContext.
DCHECK(render_process_id != other.render_process_id ||
browser_context_id == other.browser_context_id);
const bool equal = std::tie(render_process_id, lid) ==
std::tie(other.render_process_id, other.lid);
return equal;
}
bool operator<(const WebRtcEventLogPeerConnectionKey& other) const {
// Each RPH is associated with exactly one BrowserContext.
DCHECK(render_process_id != other.render_process_id ||
browser_context_id == other.browser_context_id);
return std::tie(render_process_id, lid) <
std::tie(other.render_process_id, other.lid);
}
// These two fields are the actual key; any peer connection is uniquely
// identifiable by the renderer process in which it lives, and its ID within
// that process.
int render_process_id;
int lid; // Renderer-local PeerConnection ID.
// The BrowserContext is not actually part of the key, but each PeerConnection
// is associated with a BrowserContext, and that BrowserContext is almost
// always necessary, so it makes sense to remember it along with the key.
BrowserContextId browser_context_id;
};
// Sentinel value for an unknown BrowserContext.
extern const WebRtcEventLogPeerConnectionKey::BrowserContextId
kNullBrowserContextId;
// Holds housekeeping information about log files.
struct WebRtcLogFileInfo {
WebRtcLogFileInfo(
WebRtcEventLogPeerConnectionKey::BrowserContextId browser_context_id,
const base::FilePath& path,
base::Time last_modified)
: browser_context_id(browser_context_id),
path(path),
last_modified(last_modified) {}
WebRtcLogFileInfo(const WebRtcLogFileInfo& other)
: browser_context_id(other.browser_context_id),
path(other.path),
last_modified(other.last_modified) {}
bool operator<(const WebRtcLogFileInfo& other) const {
if (last_modified != other.last_modified) {
return last_modified < other.last_modified;
}
return path < other.path; // Break ties arbitrarily, but consistently.
}
// The BrowserContext which produced this file.
const WebRtcEventLogPeerConnectionKey::BrowserContextId browser_context_id;
// The path to the log file itself.
const base::FilePath path;
// |last_modified| recorded at BrowserContext initialization. Chrome will
// not modify it afterwards, and neither should the user.
const base::Time last_modified;
};
// An observer for notifications of local log files being started/stopped, and
// the paths which will be used for these logs.
class WebRtcLocalEventLogsObserver {
public:
virtual void OnLocalLogStarted(WebRtcEventLogPeerConnectionKey key,
const base::FilePath& file_path) = 0;
virtual void OnLocalLogStopped(WebRtcEventLogPeerConnectionKey key) = 0;
protected:
virtual ~WebRtcLocalEventLogsObserver() = default;
};
// An observer for notifications of remote-bound log files being
// started/stopped. The start event would likely only interest unit tests
// (because it exposes the randomized filename to them). The stop event is of
// general interest, because it would often mean that WebRTC can stop sending
// us event logs for this peer connection.
// Some cases where OnRemoteLogStopped would be called include:
// 1. The PeerConnection has become inactive.
// 2. The file's maximum size has been reached.
// 3. Any type of error while writing to the file.
class WebRtcRemoteEventLogsObserver {
public:
virtual void OnRemoteLogStarted(WebRtcEventLogPeerConnectionKey key,
const base::FilePath& file_path,
int output_period_ms) = 0;
virtual void OnRemoteLogStopped(WebRtcEventLogPeerConnectionKey key) = 0;
protected:
virtual ~WebRtcRemoteEventLogsObserver() = default;
};
// Writes a log to a file while observing a maximum size.
class LogFileWriter {
public:
class Factory {
public:
virtual ~Factory() = default;
// The smallest size a log file of this type may assume.
virtual size_t MinFileSizeBytes() const = 0;
// The extension type associated with this type of log files.
virtual base::FilePath::StringPieceType Extension() const = 0;
// Instantiate and initialize a LogFileWriter.
// If creation or initialization fail, an empty unique_ptr will be returned,
// and it will be guaranteed that the file itself is not created. (If |path|
// had pointed to an existing file, that file will be deleted.)
// If !max_file_size_bytes.has_value(), the LogFileWriter is unlimited.
virtual std::unique_ptr<LogFileWriter> Create(
const base::FilePath& path,
base::Optional<size_t> max_file_size_bytes) const = 0;
};
virtual ~LogFileWriter() = default;
// Init() must be called on each LogFileWriter exactly once, before it's used.
// If initialization fails, no further actions may be performed on the object
// other than Close() and Delete().
virtual bool Init() = 0;
// Getter for the path of the file |this| wraps.
virtual const base::FilePath& path() const = 0;
// Whether the maximum file size was reached.
virtual bool MaxSizeReached() const = 0;
// Writes to the log file while respecting the file's size limit.
// True is returned if and only if the message was written to the file in
// it entirety. That is, |false| is returned either if a genuine error
// occurs, or when the budget does not allow the next write.
// If |false| is ever returned, only Close() and Delete() may subsequently
// be called.
// The function does *not* close the file.
// The function may not be called if MaxSizeReached().
virtual bool Write(const std::string& input) = 0;
// If the file was successfully closed, true is returned, and the file may
// now be used. Otherwise, the file is deleted, and false is returned.
virtual bool Close() = 0;
// Delete the file from disk.
virtual void Delete() = 0;
};
// Produces LogFileWriter instances that perform no compression.
class BaseLogFileWriterFactory : public LogFileWriter::Factory {
public:
~BaseLogFileWriterFactory() override = default;
size_t MinFileSizeBytes() const override;
base::FilePath::StringPieceType Extension() const override;
std::unique_ptr<LogFileWriter> Create(
const base::FilePath& path,
base::Optional<size_t> max_file_size_bytes) const override;
};
// Interface for a class that provides compression of a stream, while attempting
// to observe a limit on the size.
//
// One should note that:
// * For compressors that use a footer, to guarantee proper decompression,
// the footer must be written to the file.
// * In such a case, usually, nothing can be omitted from the file, or the
// footer's CRC (if used) would be wrong.
// * Determining a string's size pre-compression, without performing the actual
// compression, is heuristic in nature.
//
// Therefore, compression might terminate (FULL) earlier than it
// must, or even in theory (which we attempt to avoid in practice) exceed the
// size allowed it, in which case the file will be discarded (ERROR).
class LogCompressor {
public:
// By subclassing this factory, concrete implementations of LogCompressor can
// be produced by unit tests, while keeping their definition in the .cc file.
// (Only the factory needs to be declared in the header.)
class Factory {
public:
virtual ~Factory() = default;
// The smallest size a log file of this type may assume.
virtual size_t MinSizeBytes() const = 0;
// Returns a LogCompressor if the parameters are valid and all
// initializations are successful; en empty unique_ptr otherwise.
// If !max_size_bytes.has_value(), an unlimited compressor is created.
virtual std::unique_ptr<LogCompressor> Create(
base::Optional<size_t> max_size_bytes) const = 0;
};
// Result of a call to Compress().
// * OK and ERROR_ENCOUNTERED are self-explanatory.
// * DISALLOWED means that, due to budget constraints, the input could
// not be compressed. The stream is still in a legal state, but only
// a call to CreateFooter() is now allowed.
enum class Result { OK, DISALLOWED, ERROR_ENCOUNTERED };
virtual ~LogCompressor() = default;
// Produces a compression header and writes it to |output|.
// The size does not count towards the max size limit.
// Guaranteed not to fail (nothing can realistically go wrong).
virtual void CreateHeader(std::string* output) = 0;
// Compresses |input| into |output|.
// * If compression succeeded, and the budget was observed, OK is returned.
// * If the compressor thinks the string, once compressed, will exceed the
// maximum size (when combined with previously compressed strings),
// compression will not be done, and DISALLOWED will be returned.
// This allows producing a valid footer without exceeding the size limit.
// * Unexpected errors in the underlying compressor (e.g. zlib, etc.),
// or unexpectedly getting a compressed string which exceeds the budget,
// will return ERROR_ENCOUNTERED.
// This function may not be called again if DISALLOWED or ERROR_ENCOUNTERED
// were ever returned before, or after CreateFooter() was called.
virtual Result Compress(const std::string& input, std::string* output) = 0;
// Produces a compression footer and writes it to |output|.
// The footer does not count towards the max size limit.
// May not be called more than once, or if Compress() returned ERROR.
virtual bool CreateFooter(std::string* output) = 0;
};
// Estimates the compressed size, without performing compression (except in
// unit tests, where performance is of lesser importance).
// This interface allows unit tests to simulate specific cases, such as
// over/under-estimation, and show that the code using the LogCompressor
// deals with them correctly. (E.g., if the estimation expects the compression
// to not go over-budget, but then it does.)
// The estimator is expected to be stateful. That is, the order of calls to
// EstimateCompressedSize() should correspond to the order of calls
// to Compress().
class CompressedSizeEstimator {
public:
class Factory {
public:
virtual ~Factory() = default;
virtual std::unique_ptr<CompressedSizeEstimator> Create() const = 0;
};
virtual ~CompressedSizeEstimator() = default;
virtual size_t EstimateCompressedSize(const std::string& input) const = 0;
};
// Provides a conservative estimation of the number of bytes required to
// compress a string using GZIP. This estimation is not expected to ever
// be overly optimistic, but the code using it should nevertheless be prepared
// to deal with that theoretical possibility.
class DefaultGzippedSizeEstimator : public CompressedSizeEstimator {
public:
class Factory : public CompressedSizeEstimator::Factory {
public:
~Factory() override = default;
std::unique_ptr<CompressedSizeEstimator> Create() const override;
};
~DefaultGzippedSizeEstimator() override = default;
size_t EstimateCompressedSize(const std::string& input) const override;
};
// Interface for producing LogCompressorGzip objects.
class GzipLogCompressorFactory : public LogCompressor::Factory {
public:
explicit GzipLogCompressorFactory(
std::unique_ptr<CompressedSizeEstimator::Factory> estimator_factory);
~GzipLogCompressorFactory() override;
size_t MinSizeBytes() const override;
std::unique_ptr<LogCompressor> Create(
base::Optional<size_t> max_size_bytes) const override;
private:
std::unique_ptr<CompressedSizeEstimator::Factory> estimator_factory_;
};
// Produces LogFileWriter instances that perform compression using GZIP.
class GzippedLogFileWriterFactory : public LogFileWriter::Factory {
public:
explicit GzippedLogFileWriterFactory(
std::unique_ptr<GzipLogCompressorFactory> gzip_compressor_factory);
~GzippedLogFileWriterFactory() override;
size_t MinFileSizeBytes() const override;
base::FilePath::StringPieceType Extension() const override;
std::unique_ptr<LogFileWriter> Create(
const base::FilePath& path,
base::Optional<size_t> max_file_size_bytes) const override;
private:
std::unique_ptr<GzipLogCompressorFactory> gzip_compressor_factory_;
};
// Create a random identifier of 32 hexadecimal (uppercase) characters.
std::string CreateWebRtcEventLogId();
// Translate a BrowserContext into an ID. This lets us associate PeerConnections
// with BrowserContexts, while making sure that we never call the
// BrowserContext's methods outside of the UI thread (because we can't call them
// at all without a cast that would alert us to the danger).
WebRtcEventLogPeerConnectionKey::BrowserContextId GetBrowserContextId(
const content::BrowserContext* browser_context);
// Fetches the BrowserContext associated with the render process ID, then
// returns its BrowserContextId. (If the render process has already died,
// it would have no BrowserContext associated, so the ID associated with a
// null BrowserContext will be returned.)
WebRtcEventLogPeerConnectionKey::BrowserContextId GetBrowserContextId(
int render_process_id);
// Given a BrowserContext's directory, return the path to the directory where
// we store the pending remote-bound logs associated with this BrowserContext.
// This function may be called on any task queue.
base::FilePath GetRemoteBoundWebRtcEventLogsDir(
const base::FilePath& browser_context_dir);
// Produce the path to a remote-bound WebRTC event log file with the given
// log ID, web-app ID and extension, in the given directory.
base::FilePath WebRtcEventLogPath(
const base::FilePath& remote_logs_dir,
const std::string& log_id,
size_t web_app_id,
const base::FilePath::StringPieceType& extension);
// Checks whether the path/filename would be a valid reference to a remote-bound
// even log. These functions do not examine the file's content or its extension.
bool IsValidRemoteBoundLogFilename(const std::string& filename);
bool IsValidRemoteBoundLogFilePath(const base::FilePath& path);
// Given WebRTC event log's path, return the path to the history file that
// is, or would be, associated with it.
base::FilePath GetWebRtcEventLogHistoryFilePath(const base::FilePath& path);
// Attempts to extract the local ID from the file's path. Returns the empty
// string in case of an error.
std::string ExtractRemoteBoundWebRtcEventLogLocalIdFromPath(
const base::FilePath& path);
// Attempts to extract the web-app ID from the file's path.
// Returns kInvalidWebRtcEventLogWebAppId in case of an error.
size_t ExtractRemoteBoundWebRtcEventLogWebAppIdFromPath(
const base::FilePath& path);
} // namespace webrtc_event_logging
#endif // CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_MANAGER_COMMON_H_