mirror of
https://github.com/klzgrad/naiveproxy.git
synced 2024-11-28 08:16:09 +03:00
808 lines
30 KiB
C++
808 lines
30 KiB
C++
// 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.
|
|
|
|
#include "net/log/file_net_log_observer.h"
|
|
|
|
#include <algorithm>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <utility>
|
|
|
|
#include "base/bind.h"
|
|
#include "base/containers/queue.h"
|
|
#include "base/files/file.h"
|
|
#include "base/files/file_util.h"
|
|
#include "base/json/json_writer.h"
|
|
#include "base/logging.h"
|
|
#include "base/sequenced_task_runner.h"
|
|
#include "base/strings/string_number_conversions.h"
|
|
#include "base/synchronization/lock.h"
|
|
#include "base/task/post_task.h"
|
|
#include "base/values.h"
|
|
#include "net/log/net_log_capture_mode.h"
|
|
#include "net/log/net_log_entry.h"
|
|
#include "net/log/net_log_util.h"
|
|
#include "net/url_request/url_request_context.h"
|
|
|
|
namespace {
|
|
|
|
// Number of events that can build up in |write_queue_| before a task is posted
|
|
// to the file task runner to flush them to disk.
|
|
const int kNumWriteQueueEvents = 15;
|
|
|
|
// TODO(eroman): Should use something other than 10 for number of files?
|
|
const int kDefaultNumFiles = 10;
|
|
|
|
scoped_refptr<base::SequencedTaskRunner> CreateFileTaskRunner() {
|
|
// The tasks posted to this sequenced task runner do synchronous File I/O for
|
|
// the purposes of writing NetLog files.
|
|
//
|
|
// These intentionally block shutdown to ensure the log file has finished
|
|
// being written.
|
|
return base::CreateSequencedTaskRunnerWithTraits(
|
|
{base::MayBlock(), base::TaskPriority::USER_VISIBLE,
|
|
base::TaskShutdownBehavior::BLOCK_SHUTDOWN});
|
|
}
|
|
|
|
// Truncates a file, also reseting the seek position.
|
|
void TruncateFile(base::File* file) {
|
|
if (!file->IsValid())
|
|
return;
|
|
file->Seek(base::File::FROM_BEGIN, 0);
|
|
file->SetLength(0);
|
|
}
|
|
|
|
// Opens |path| in write mode.
|
|
base::File OpenFileForWrite(const base::FilePath& path) {
|
|
base::File result(path,
|
|
base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
|
|
LOG_IF(ERROR, !result.IsValid()) << "Failed opening: " << path.value();
|
|
return result;
|
|
}
|
|
|
|
// Helper that writes data to a file. |file->IsValid()| may be false,
|
|
// in which case nothing will be written. Returns the number of bytes
|
|
// successfully written (may be less than input data in case of errors).
|
|
size_t WriteToFile(base::File* file,
|
|
base::StringPiece data1,
|
|
base::StringPiece data2 = base::StringPiece(),
|
|
base::StringPiece data3 = base::StringPiece()) {
|
|
size_t bytes_written = 0;
|
|
|
|
if (file->IsValid()) {
|
|
// Append each of data1, data2 and data3.
|
|
if (!data1.empty())
|
|
bytes_written +=
|
|
std::max(0, file->WriteAtCurrentPos(data1.data(), data1.size()));
|
|
if (!data2.empty())
|
|
bytes_written +=
|
|
std::max(0, file->WriteAtCurrentPos(data2.data(), data2.size()));
|
|
if (!data3.empty())
|
|
bytes_written +=
|
|
std::max(0, file->WriteAtCurrentPos(data3.data(), data3.size()));
|
|
}
|
|
|
|
return bytes_written;
|
|
}
|
|
|
|
// Copies all of the data at |source_path| and appends it to |destination_file|,
|
|
// then deletes |source_path|.
|
|
void AppendToFileThenDelete(const base::FilePath& source_path,
|
|
base::File* destination_file,
|
|
char* read_buffer,
|
|
size_t read_buffer_size) {
|
|
base::ScopedFILE source_file(base::OpenFile(source_path, "rb"));
|
|
if (!source_file)
|
|
return;
|
|
|
|
// Read |source_path|'s contents in chunks of read_buffer_size and append
|
|
// to |destination_file|.
|
|
size_t num_bytes_read;
|
|
while ((num_bytes_read =
|
|
fread(read_buffer, 1, read_buffer_size, source_file.get())) > 0) {
|
|
WriteToFile(destination_file,
|
|
base::StringPiece(read_buffer, num_bytes_read));
|
|
}
|
|
|
|
// Now that it has been copied, delete the source file.
|
|
source_file.reset();
|
|
base::DeleteFile(source_path, false);
|
|
}
|
|
|
|
base::FilePath SiblingInprogressDirectory(const base::FilePath& log_path) {
|
|
return log_path.AddExtension(FILE_PATH_LITERAL(".inprogress"));
|
|
}
|
|
|
|
} // namespace
|
|
|
|
namespace net {
|
|
|
|
// Used to store events to be written to file.
|
|
using EventQueue = base::queue<std::unique_ptr<std::string>>;
|
|
|
|
// WriteQueue receives events from FileNetLogObserver on the main thread and
|
|
// holds them in a queue until they are drained from the queue and written to
|
|
// file on the file task runner.
|
|
//
|
|
// WriteQueue contains the resources shared between the main thread and the
|
|
// file task runner. |lock_| must be acquired to read or write to |queue_| and
|
|
// |memory_|.
|
|
//
|
|
// WriteQueue is refcounted and should be destroyed once all events on the
|
|
// file task runner have finished executing.
|
|
class FileNetLogObserver::WriteQueue
|
|
: public base::RefCountedThreadSafe<WriteQueue> {
|
|
public:
|
|
// |memory_max| indicates the maximum amount of memory that the virtual write
|
|
// queue can use. If |memory_| exceeds |memory_max_|, the |queue_| of events
|
|
// is overwritten.
|
|
explicit WriteQueue(uint64_t memory_max);
|
|
|
|
// Adds |event| to |queue_|. Also manages the size of |memory_|; if it
|
|
// exceeds |memory_max_|, then old events are dropped from |queue_| without
|
|
// being written to file.
|
|
//
|
|
// Returns the number of events in the |queue_|.
|
|
size_t AddEntryToQueue(std::unique_ptr<std::string> event);
|
|
|
|
// Swaps |queue_| with |local_queue|. |local_queue| should be empty, so that
|
|
// |queue_| is emptied. Resets |memory_| to 0.
|
|
void SwapQueue(EventQueue* local_queue);
|
|
|
|
private:
|
|
friend class base::RefCountedThreadSafe<WriteQueue>;
|
|
|
|
~WriteQueue();
|
|
|
|
// Queue of events to be written, shared between main thread and file task
|
|
// runner. Main thread adds events to the queue and the file task runner
|
|
// drains them and writes the events to file.
|
|
//
|
|
// |lock_| must be acquired to read or write to this.
|
|
EventQueue queue_;
|
|
|
|
// Tracks how much memory is being used by the virtual write queue.
|
|
// Incremented in AddEntryToQueue() when events are added to the
|
|
// buffer, and decremented when SwapQueue() is called and the file task
|
|
// runner's local queue is swapped with the shared write queue.
|
|
//
|
|
// |lock_| must be acquired to read or write to this.
|
|
uint64_t memory_;
|
|
|
|
// Indicates the maximum amount of memory that the |queue_| is allowed to
|
|
// use.
|
|
const uint64_t memory_max_;
|
|
|
|
// Protects access to |queue_| and |memory_|.
|
|
//
|
|
// A lock is necessary because |queue_| and |memory_| are shared between the
|
|
// file task runner and the main thread. NetLog's lock protects OnAddEntry(),
|
|
// which calls AddEntryToQueue(), but it does not protect access to the
|
|
// observer's member variables. Thus, a race condition exists if a thread is
|
|
// calling OnAddEntry() at the same time that the file task runner is
|
|
// accessing |memory_| and |queue_| to write events to file. The |queue_| and
|
|
// |memory_| counter are necessary to bound the amount of memory that is used
|
|
// for the queue in the event that the file task runner lags significantly
|
|
// behind the main thread in writing events to file.
|
|
base::Lock lock_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(WriteQueue);
|
|
};
|
|
|
|
// FileWriter is responsible for draining events from a WriteQueue and writing
|
|
// them to disk. FileWriter can be constructed on any thread, and
|
|
// afterwards is only accessed on the file task runner.
|
|
class FileNetLogObserver::FileWriter {
|
|
public:
|
|
// If max_event_file_size == kNoLimit, then no limit is enforced.
|
|
FileWriter(const base::FilePath& log_path,
|
|
const base::FilePath& inprogress_dir_path,
|
|
base::Optional<base::File> pre_existing_log_file,
|
|
uint64_t max_event_file_size,
|
|
size_t total_num_event_files,
|
|
scoped_refptr<base::SequencedTaskRunner> task_runner);
|
|
|
|
~FileWriter();
|
|
|
|
// Writes |constants_value| to disk and opens the events array (closed in
|
|
// Stop()).
|
|
void Initialize(std::unique_ptr<base::Value> constants_value);
|
|
|
|
// Closes the events array opened in Initialize() and writes |polled_data| to
|
|
// disk. If |polled_data| cannot be converted to proper JSON, then it
|
|
// is ignored.
|
|
void Stop(std::unique_ptr<base::Value> polled_data);
|
|
|
|
// Drains |queue_| from WriteQueue into a local file queue and writes the
|
|
// events in the queue to disk.
|
|
void Flush(scoped_refptr<WriteQueue> write_queue);
|
|
|
|
// Deletes all netlog files. It is not valid to call any method of
|
|
// FileNetLogObserver after DeleteAllFiles().
|
|
void DeleteAllFiles();
|
|
|
|
void FlushThenStop(scoped_refptr<WriteQueue> write_queue,
|
|
std::unique_ptr<base::Value> polled_data);
|
|
|
|
private:
|
|
// Returns true if there is no file size bound to enforce.
|
|
//
|
|
// When operating in unbounded mode, the implementation is optimized to stream
|
|
// writes to a single file, rather than chunking them across temporary event
|
|
// files.
|
|
bool IsUnbounded() const;
|
|
bool IsBounded() const;
|
|
|
|
// Increments |current_event_file_number_|, and updates all state relating to
|
|
// the current event file (open file handle, num bytes written, current file
|
|
// number).
|
|
void IncrementCurrentEventFile();
|
|
|
|
// Returns the path to the event file having |index|. This looks like
|
|
// "LOGDIR/event_file_<index>.json".
|
|
base::FilePath GetEventFilePath(size_t index) const;
|
|
|
|
// Gets the file path where constants are saved at the start of
|
|
// logging. This looks like "LOGDIR/constants.json".
|
|
base::FilePath GetConstantsFilePath() const;
|
|
|
|
// Gets the file path where the final data is written at the end of logging.
|
|
// This looks like "LOGDIR/end_netlog.json".
|
|
base::FilePath GetClosingFilePath() const;
|
|
|
|
// Returns the corresponding index for |file_number|. File "numbers" are a
|
|
// monotonically increasing identifier that start at 1 (a value of zero means
|
|
// it is uninitialized), whereas the file "index" is a bounded value that
|
|
// wraps and identifies the file path to use.
|
|
//
|
|
// Keeping track of the current number rather than index makes it a bit easier
|
|
// to assemble a file at the end, since it is unambiguous which paths have
|
|
// been used/re-used.
|
|
size_t FileNumberToIndex(size_t file_number) const;
|
|
|
|
// Writes |constants_value| to a file.
|
|
static void WriteConstantsToFile(std::unique_ptr<base::Value> constants_value,
|
|
base::File* file);
|
|
|
|
// Writes |polled_data| to a file.
|
|
static void WritePolledDataToFile(std::unique_ptr<base::Value> polled_data,
|
|
base::File* file);
|
|
|
|
// If any events were written (wrote_event_bytes_), rewinds |file| by 2 bytes
|
|
// in order to overwrite the trailing ",\n" that was written by the last event
|
|
// line.
|
|
void RewindIfWroteEventBytes(base::File* file) const;
|
|
|
|
// Concatenates all the log files to assemble the final
|
|
// |final_log_file_|. This single "stitched" file is what other
|
|
// log ingesting tools expect.
|
|
void StitchFinalLogFile();
|
|
|
|
// Creates the .inprogress directory used by bounded mode.
|
|
void CreateInprogressDirectory();
|
|
|
|
// The file the final netlog is written to. In bounded mode this is mostly
|
|
// written to once logging is stopped, whereas in unbounded mode events will
|
|
// be directly written to it.
|
|
base::File final_log_file_;
|
|
|
|
// If non-empty, this is the path to |final_log_file_| created and owned
|
|
// by FileWriter itself (rather than passed in to Create*PreExisting
|
|
// methods of FileNetLogObserver).
|
|
const base::FilePath final_log_path_;
|
|
|
|
// Path to a (temporary) directory where files are written in bounded mode.
|
|
// When logging is stopped these files are stitched together and written
|
|
// to the final log path.
|
|
const base::FilePath inprogress_dir_path_;
|
|
|
|
// Holds the numbered events file where data is currently being written to.
|
|
// The file path of this file is GetEventFilePath(current_event_file_number_).
|
|
// The file may be !IsValid() if an error previously occurred opening the
|
|
// file, or logging has been stopped.
|
|
base::File current_event_file_;
|
|
uint64_t current_event_file_size_;
|
|
|
|
// Indicates the total number of netlog event files allowed.
|
|
// (The files GetConstantsFilePath() and GetClosingFilePath() do
|
|
// not count against the total.)
|
|
const size_t total_num_event_files_;
|
|
|
|
// Counter for the events file currently being written into. See
|
|
// FileNumberToIndex() for an explanation of what "number" vs "index" mean.
|
|
size_t current_event_file_number_;
|
|
|
|
// Indicates the maximum size of each individual events file. May be kNoLimit
|
|
// to indicate that it can grow arbitrarily large.
|
|
const uint64_t max_event_file_size_;
|
|
|
|
// Whether any bytes were written for events. This is used to properly format
|
|
// JSON (events list shouldn't end with a comma).
|
|
bool wrote_event_bytes_;
|
|
|
|
// Task runner for doing file operations.
|
|
const scoped_refptr<base::SequencedTaskRunner> task_runner_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(FileWriter);
|
|
};
|
|
|
|
std::unique_ptr<FileNetLogObserver> FileNetLogObserver::CreateBounded(
|
|
const base::FilePath& log_path,
|
|
uint64_t max_total_size,
|
|
std::unique_ptr<base::Value> constants) {
|
|
return CreateInternal(log_path, SiblingInprogressDirectory(log_path),
|
|
base::nullopt, max_total_size, kDefaultNumFiles,
|
|
std::move(constants));
|
|
}
|
|
|
|
std::unique_ptr<FileNetLogObserver> FileNetLogObserver::CreateUnbounded(
|
|
const base::FilePath& log_path,
|
|
std::unique_ptr<base::Value> constants) {
|
|
return CreateInternal(log_path, base::FilePath(), base::nullopt, kNoLimit,
|
|
kDefaultNumFiles, std::move(constants));
|
|
}
|
|
|
|
std::unique_ptr<FileNetLogObserver>
|
|
FileNetLogObserver::CreateBoundedPreExisting(
|
|
const base::FilePath& inprogress_dir_path,
|
|
base::File output_file,
|
|
uint64_t max_total_size,
|
|
std::unique_ptr<base::Value> constants) {
|
|
return CreateInternal(base::FilePath(), inprogress_dir_path,
|
|
base::make_optional<base::File>(std::move(output_file)),
|
|
max_total_size, kDefaultNumFiles, std::move(constants));
|
|
}
|
|
|
|
std::unique_ptr<FileNetLogObserver>
|
|
FileNetLogObserver::CreateUnboundedPreExisting(
|
|
base::File output_file,
|
|
std::unique_ptr<base::Value> constants) {
|
|
return CreateInternal(base::FilePath(), base::FilePath(),
|
|
base::make_optional<base::File>(std::move(output_file)),
|
|
kNoLimit, kDefaultNumFiles, std::move(constants));
|
|
}
|
|
|
|
FileNetLogObserver::~FileNetLogObserver() {
|
|
if (net_log()) {
|
|
// StopObserving was not called.
|
|
net_log()->RemoveObserver(this);
|
|
file_task_runner_->PostTask(
|
|
FROM_HERE, base::Bind(&FileNetLogObserver::FileWriter::DeleteAllFiles,
|
|
base::Unretained(file_writer_.get())));
|
|
}
|
|
file_task_runner_->DeleteSoon(FROM_HERE, file_writer_.release());
|
|
}
|
|
|
|
void FileNetLogObserver::StartObserving(NetLog* net_log,
|
|
NetLogCaptureMode capture_mode) {
|
|
net_log->AddObserver(this, capture_mode);
|
|
}
|
|
|
|
void FileNetLogObserver::StopObserving(std::unique_ptr<base::Value> polled_data,
|
|
base::OnceClosure optional_callback) {
|
|
net_log()->RemoveObserver(this);
|
|
|
|
base::OnceClosure bound_flush_then_stop =
|
|
base::Bind(&FileNetLogObserver::FileWriter::FlushThenStop,
|
|
base::Unretained(file_writer_.get()), write_queue_,
|
|
base::Passed(&polled_data));
|
|
|
|
// Note that PostTaskAndReply() requires a non-null closure.
|
|
if (!optional_callback.is_null()) {
|
|
file_task_runner_->PostTaskAndReply(FROM_HERE,
|
|
std::move(bound_flush_then_stop),
|
|
std::move(optional_callback));
|
|
} else {
|
|
file_task_runner_->PostTask(FROM_HERE, std::move(bound_flush_then_stop));
|
|
}
|
|
}
|
|
|
|
void FileNetLogObserver::OnAddEntry(const NetLogEntry& entry) {
|
|
std::unique_ptr<std::string> json(new std::string);
|
|
|
|
// If |entry| cannot be converted to proper JSON, ignore it.
|
|
if (!base::JSONWriter::Write(*entry.ToValue(), json.get()))
|
|
return;
|
|
|
|
size_t queue_size = write_queue_->AddEntryToQueue(std::move(json));
|
|
|
|
// If events build up in |write_queue_|, trigger the file task runner to drain
|
|
// the queue. Because only 1 item is added to the queue at a time, if
|
|
// queue_size > kNumWriteQueueEvents a task has already been posted, or will
|
|
// be posted.
|
|
if (queue_size == kNumWriteQueueEvents) {
|
|
file_task_runner_->PostTask(
|
|
FROM_HERE,
|
|
base::Bind(&FileNetLogObserver::FileWriter::Flush,
|
|
base::Unretained(file_writer_.get()), write_queue_));
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<FileNetLogObserver> FileNetLogObserver::CreateBoundedForTests(
|
|
const base::FilePath& log_path,
|
|
uint64_t max_total_size,
|
|
size_t total_num_event_files,
|
|
std::unique_ptr<base::Value> constants) {
|
|
return CreateInternal(log_path, SiblingInprogressDirectory(log_path),
|
|
base::nullopt, max_total_size, total_num_event_files,
|
|
std::move(constants));
|
|
}
|
|
|
|
std::unique_ptr<FileNetLogObserver> FileNetLogObserver::CreateInternal(
|
|
const base::FilePath& log_path,
|
|
const base::FilePath& inprogress_dir_path,
|
|
base::Optional<base::File> pre_existing_log_file,
|
|
uint64_t max_total_size,
|
|
size_t total_num_event_files,
|
|
std::unique_ptr<base::Value> constants) {
|
|
DCHECK_GT(total_num_event_files, 0u);
|
|
|
|
scoped_refptr<base::SequencedTaskRunner> file_task_runner =
|
|
CreateFileTaskRunner();
|
|
|
|
const uint64_t max_event_file_size =
|
|
max_total_size == kNoLimit ? kNoLimit
|
|
: max_total_size / total_num_event_files;
|
|
|
|
// The FileWriter uses a soft limit to write events to file that allows
|
|
// the size of the file to exceed the limit, but the WriteQueue uses a hard
|
|
// limit which the size of |WriteQueue::queue_| cannot exceed. Thus, the
|
|
// FileWriter may write more events to file than can be contained by
|
|
// the WriteQueue if they have the same size limit. The maximum size of the
|
|
// WriteQueue is doubled to allow |WriteQueue::queue_| to hold enough events
|
|
// for the FileWriter to fill all files. As long as all events have
|
|
// sizes <= the size of an individual event file, the discrepancy between the
|
|
// hard limit and the soft limit will not cause an issue.
|
|
// TODO(dconnol): Handle the case when the WriteQueue still doesn't
|
|
// contain enough events to fill all files, because of very large events
|
|
// relative to file size.
|
|
std::unique_ptr<FileWriter> file_writer(new FileWriter(
|
|
log_path, inprogress_dir_path, std::move(pre_existing_log_file),
|
|
max_event_file_size, total_num_event_files, file_task_runner));
|
|
|
|
scoped_refptr<WriteQueue> write_queue(new WriteQueue(max_total_size * 2));
|
|
|
|
return std::unique_ptr<FileNetLogObserver>(
|
|
new FileNetLogObserver(file_task_runner, std::move(file_writer),
|
|
std::move(write_queue), std::move(constants)));
|
|
}
|
|
|
|
FileNetLogObserver::FileNetLogObserver(
|
|
scoped_refptr<base::SequencedTaskRunner> file_task_runner,
|
|
std::unique_ptr<FileWriter> file_writer,
|
|
scoped_refptr<WriteQueue> write_queue,
|
|
std::unique_ptr<base::Value> constants)
|
|
: file_task_runner_(std::move(file_task_runner)),
|
|
write_queue_(std::move(write_queue)),
|
|
file_writer_(std::move(file_writer)) {
|
|
if (!constants)
|
|
constants = GetNetConstants();
|
|
file_task_runner_->PostTask(
|
|
FROM_HERE, base::Bind(&FileNetLogObserver::FileWriter::Initialize,
|
|
base::Unretained(file_writer_.get()),
|
|
base::Passed(&constants)));
|
|
}
|
|
|
|
FileNetLogObserver::WriteQueue::WriteQueue(uint64_t memory_max)
|
|
: memory_(0), memory_max_(memory_max) {}
|
|
|
|
size_t FileNetLogObserver::WriteQueue::AddEntryToQueue(
|
|
std::unique_ptr<std::string> event) {
|
|
base::AutoLock lock(lock_);
|
|
|
|
memory_ += event->size();
|
|
queue_.push(std::move(event));
|
|
|
|
while (memory_ > memory_max_ && !queue_.empty()) {
|
|
// Delete oldest events in the queue.
|
|
DCHECK(queue_.front());
|
|
memory_ -= queue_.front()->size();
|
|
queue_.pop();
|
|
}
|
|
|
|
return queue_.size();
|
|
}
|
|
|
|
void FileNetLogObserver::WriteQueue::SwapQueue(EventQueue* local_queue) {
|
|
DCHECK(local_queue->empty());
|
|
base::AutoLock lock(lock_);
|
|
queue_.swap(*local_queue);
|
|
memory_ = 0;
|
|
}
|
|
|
|
FileNetLogObserver::WriteQueue::~WriteQueue() = default;
|
|
|
|
FileNetLogObserver::FileWriter::FileWriter(
|
|
const base::FilePath& log_path,
|
|
const base::FilePath& inprogress_dir_path,
|
|
base::Optional<base::File> pre_existing_log_file,
|
|
uint64_t max_event_file_size,
|
|
size_t total_num_event_files,
|
|
scoped_refptr<base::SequencedTaskRunner> task_runner)
|
|
: final_log_path_(log_path),
|
|
inprogress_dir_path_(inprogress_dir_path),
|
|
total_num_event_files_(total_num_event_files),
|
|
current_event_file_number_(0),
|
|
max_event_file_size_(max_event_file_size),
|
|
wrote_event_bytes_(false),
|
|
task_runner_(std::move(task_runner)) {
|
|
DCHECK_EQ(pre_existing_log_file.has_value(), log_path.empty());
|
|
DCHECK_EQ(IsBounded(), !inprogress_dir_path.empty());
|
|
|
|
if (pre_existing_log_file.has_value()) {
|
|
// pre_existing_log_file.IsValid() being false is fine.
|
|
final_log_file_ = std::move(pre_existing_log_file.value());
|
|
}
|
|
}
|
|
|
|
FileNetLogObserver::FileWriter::~FileWriter() = default;
|
|
|
|
void FileNetLogObserver::FileWriter::Initialize(
|
|
std::unique_ptr<base::Value> constants_value) {
|
|
DCHECK(task_runner_->RunsTasksInCurrentSequence());
|
|
|
|
// Open the final log file, and keep it open for the duration of logging (even
|
|
// in bounded mode).
|
|
if (!final_log_path_.empty())
|
|
final_log_file_ = OpenFileForWrite(final_log_path_);
|
|
else
|
|
TruncateFile(&final_log_file_);
|
|
|
|
if (IsBounded()) {
|
|
CreateInprogressDirectory();
|
|
base::File constants_file = OpenFileForWrite(GetConstantsFilePath());
|
|
WriteConstantsToFile(std::move(constants_value), &constants_file);
|
|
} else {
|
|
WriteConstantsToFile(std::move(constants_value), &final_log_file_);
|
|
}
|
|
}
|
|
|
|
void FileNetLogObserver::FileWriter::Stop(
|
|
std::unique_ptr<base::Value> polled_data) {
|
|
DCHECK(task_runner_->RunsTasksInCurrentSequence());
|
|
|
|
// Write out the polled data.
|
|
if (IsBounded()) {
|
|
base::File closing_file = OpenFileForWrite(GetClosingFilePath());
|
|
WritePolledDataToFile(std::move(polled_data), &closing_file);
|
|
} else {
|
|
RewindIfWroteEventBytes(&final_log_file_);
|
|
WritePolledDataToFile(std::move(polled_data), &final_log_file_);
|
|
}
|
|
|
|
// If operating in bounded mode, the events were written to separate files
|
|
// within |inprogress_dir_path_|. Assemble them into the final destination
|
|
// file.
|
|
if (IsBounded())
|
|
StitchFinalLogFile();
|
|
|
|
// Ensure the final log file has been flushed.
|
|
final_log_file_.Close();
|
|
}
|
|
|
|
void FileNetLogObserver::FileWriter::Flush(
|
|
scoped_refptr<FileNetLogObserver::WriteQueue> write_queue) {
|
|
DCHECK(task_runner_->RunsTasksInCurrentSequence());
|
|
|
|
EventQueue local_file_queue;
|
|
write_queue->SwapQueue(&local_file_queue);
|
|
|
|
while (!local_file_queue.empty()) {
|
|
base::File* output_file;
|
|
|
|
// If in bounded mode, output events to the current event file. Otherwise
|
|
// output events to the final log path.
|
|
if (IsBounded()) {
|
|
if (current_event_file_number_ == 0 ||
|
|
current_event_file_size_ >= max_event_file_size_) {
|
|
IncrementCurrentEventFile();
|
|
}
|
|
output_file = ¤t_event_file_;
|
|
} else {
|
|
output_file = &final_log_file_;
|
|
}
|
|
|
|
size_t bytes_written =
|
|
WriteToFile(output_file, *local_file_queue.front(), ",\n");
|
|
|
|
wrote_event_bytes_ |= bytes_written > 0;
|
|
|
|
// Keep track of the filesize for current event file when in bounded mode.
|
|
if (IsBounded())
|
|
current_event_file_size_ += bytes_written;
|
|
|
|
local_file_queue.pop();
|
|
}
|
|
}
|
|
|
|
void FileNetLogObserver::FileWriter::DeleteAllFiles() {
|
|
DCHECK(task_runner_->RunsTasksInCurrentSequence());
|
|
|
|
final_log_file_.Close();
|
|
|
|
if (IsBounded()) {
|
|
current_event_file_.Close();
|
|
base::DeleteFile(inprogress_dir_path_, true);
|
|
}
|
|
|
|
// Only delete |final_log_file_| if it was created internally.
|
|
// (If it was provided as a base::File by the caller, don't try to delete it).
|
|
if (!final_log_path_.empty())
|
|
base::DeleteFile(final_log_path_, false);
|
|
}
|
|
|
|
void FileNetLogObserver::FileWriter::FlushThenStop(
|
|
scoped_refptr<FileNetLogObserver::WriteQueue> write_queue,
|
|
std::unique_ptr<base::Value> polled_data) {
|
|
Flush(write_queue);
|
|
Stop(std::move(polled_data));
|
|
}
|
|
|
|
bool FileNetLogObserver::FileWriter::IsUnbounded() const {
|
|
return max_event_file_size_ == kNoLimit;
|
|
}
|
|
|
|
bool FileNetLogObserver::FileWriter::IsBounded() const {
|
|
return !IsUnbounded();
|
|
}
|
|
|
|
void FileNetLogObserver::FileWriter::IncrementCurrentEventFile() {
|
|
DCHECK(task_runner_->RunsTasksInCurrentSequence());
|
|
DCHECK(IsBounded());
|
|
|
|
current_event_file_number_++;
|
|
current_event_file_ = OpenFileForWrite(
|
|
GetEventFilePath(FileNumberToIndex(current_event_file_number_)));
|
|
current_event_file_size_ = 0;
|
|
}
|
|
|
|
base::FilePath FileNetLogObserver::FileWriter::GetEventFilePath(
|
|
size_t index) const {
|
|
DCHECK_LT(index, total_num_event_files_);
|
|
DCHECK(IsBounded());
|
|
return inprogress_dir_path_.AppendASCII(
|
|
"event_file_" + base::NumberToString(index) + ".json");
|
|
}
|
|
|
|
base::FilePath FileNetLogObserver::FileWriter::GetConstantsFilePath() const {
|
|
return inprogress_dir_path_.AppendASCII("constants.json");
|
|
}
|
|
|
|
base::FilePath FileNetLogObserver::FileWriter::GetClosingFilePath() const {
|
|
return inprogress_dir_path_.AppendASCII("end_netlog.json");
|
|
}
|
|
|
|
size_t FileNetLogObserver::FileWriter::FileNumberToIndex(
|
|
size_t file_number) const {
|
|
DCHECK_GT(file_number, 0u);
|
|
// Note that "file numbers" start at 1 not 0.
|
|
return (file_number - 1) % total_num_event_files_;
|
|
}
|
|
|
|
void FileNetLogObserver::FileWriter::WriteConstantsToFile(
|
|
std::unique_ptr<base::Value> constants_value,
|
|
base::File* file) {
|
|
// Print constants to file and open events array.
|
|
std::string json;
|
|
|
|
// It should always be possible to convert constants to JSON.
|
|
if (!base::JSONWriter::Write(*constants_value, &json))
|
|
DCHECK(false);
|
|
WriteToFile(file, "{\"constants\":", json, ",\n\"events\": [\n");
|
|
}
|
|
|
|
void FileNetLogObserver::FileWriter::WritePolledDataToFile(
|
|
std::unique_ptr<base::Value> polled_data,
|
|
base::File* file) {
|
|
// Close the events array.
|
|
WriteToFile(file, "]");
|
|
|
|
// Write the polled data (if any).
|
|
if (polled_data) {
|
|
std::string polled_data_json;
|
|
base::JSONWriter::Write(*polled_data, &polled_data_json);
|
|
if (!polled_data_json.empty())
|
|
WriteToFile(file, ",\n\"polledData\": ", polled_data_json, "\n");
|
|
}
|
|
|
|
// Close the log.
|
|
WriteToFile(file, "}\n");
|
|
}
|
|
|
|
void FileNetLogObserver::FileWriter::RewindIfWroteEventBytes(
|
|
base::File* file) const {
|
|
if (file->IsValid() && wrote_event_bytes_) {
|
|
// To be valid JSON the events array should not end with a comma. If events
|
|
// were written though, they will have been terminated with "\n," so strip
|
|
// it before closing the events array.
|
|
file->Seek(base::File::FROM_END, -2);
|
|
}
|
|
}
|
|
|
|
void FileNetLogObserver::FileWriter::StitchFinalLogFile() {
|
|
// Make sure all the events files are flushed (as will read them next).
|
|
current_event_file_.Close();
|
|
|
|
// Allocate a 64K buffer used for reading the files. At most kReadBufferSize
|
|
// bytes will be in memory at a time.
|
|
const size_t kReadBufferSize = 1 << 16; // 64KiB
|
|
std::unique_ptr<char[]> read_buffer(new char[kReadBufferSize]);
|
|
|
|
if (final_log_file_.IsValid()) {
|
|
// Truncate the final log file.
|
|
TruncateFile(&final_log_file_);
|
|
|
|
// Append the constants file.
|
|
AppendToFileThenDelete(GetConstantsFilePath(), &final_log_file_,
|
|
read_buffer.get(), kReadBufferSize);
|
|
|
|
// Iterate over the events files, from oldest to most recent, and append
|
|
// them to the final destination. Note that "file numbers" start at 1 not 0.
|
|
size_t end_filenumber = current_event_file_number_ + 1;
|
|
size_t begin_filenumber =
|
|
current_event_file_number_ <= total_num_event_files_
|
|
? 1
|
|
: end_filenumber - total_num_event_files_;
|
|
for (size_t filenumber = begin_filenumber; filenumber < end_filenumber;
|
|
++filenumber) {
|
|
AppendToFileThenDelete(GetEventFilePath(FileNumberToIndex(filenumber)),
|
|
&final_log_file_, read_buffer.get(),
|
|
kReadBufferSize);
|
|
}
|
|
|
|
// Account for the final event line ending in a ",\n". Strip it to form
|
|
// valid JSON.
|
|
RewindIfWroteEventBytes(&final_log_file_);
|
|
|
|
// Append the polled data.
|
|
AppendToFileThenDelete(GetClosingFilePath(), &final_log_file_,
|
|
read_buffer.get(), kReadBufferSize);
|
|
}
|
|
|
|
// Delete the inprogress directory (and anything that may still be left inside
|
|
// it).
|
|
base::DeleteFile(inprogress_dir_path_, true);
|
|
}
|
|
|
|
void FileNetLogObserver::FileWriter::CreateInprogressDirectory() {
|
|
DCHECK(IsBounded());
|
|
|
|
// If an output file couldn't be created, either creation of intermediate
|
|
// files will also fail (if they're in a sibling directory), or are they are
|
|
// hidden somewhere the user would be unlikely to find them, so there is
|
|
// little reason to progress.
|
|
if (!final_log_file_.IsValid())
|
|
return;
|
|
|
|
if (!base::CreateDirectory(inprogress_dir_path_)) {
|
|
LOG(WARNING) << "Failed creating directory: "
|
|
<< inprogress_dir_path_.value();
|
|
return;
|
|
}
|
|
|
|
// It is OK if the path is wrong due to encoding - this is really just a
|
|
// convenience display for the user in understanding what the file means.
|
|
std::string in_progress_path = inprogress_dir_path_.AsUTF8Unsafe();
|
|
|
|
// Since |final_log_file_| will not be written to until the very end, leave
|
|
// some data in it explaining that the real data is currently in the
|
|
// .inprogress directory. This ordinarily won't be visible (overwritten when
|
|
// stopping) however if logging does not end gracefully the comments are
|
|
// useful for recovery.
|
|
WriteToFile(
|
|
&final_log_file_, "Logging is in progress writing data to:\n ",
|
|
in_progress_path,
|
|
"\n\n"
|
|
"That data will be stitched into a single file (this one) once logging\n"
|
|
"has stopped.\n"
|
|
"\n"
|
|
"If logging was interrupted, you can stitch a NetLog file out of the\n"
|
|
".inprogress directory manually using:\n"
|
|
"\n"
|
|
"https://chromium.googlesource.com/chromium/src/+/master/net/tools/"
|
|
"stitch_net_log_files.py\n");
|
|
}
|
|
|
|
} // namespace net
|