diff options
Diffstat (limited to 'src/serialize/protobuf_io.cc')
-rw-r--r-- | src/serialize/protobuf_io.cc | 173 |
1 files changed, 173 insertions, 0 deletions
diff --git a/src/serialize/protobuf_io.cc b/src/serialize/protobuf_io.cc new file mode 100644 index 0000000..1b6420f --- /dev/null +++ b/src/serialize/protobuf_io.cc @@ -0,0 +1,173 @@ +// Copyright (C) 2017 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "protobuf_io.h" + +#include "common/trace.h" +#include "serialize/arena_ptr.h" + +#include <android-base/chrono_utils.h> +#include <android-base/logging.h> +#include <android-base/unique_fd.h> +#include <fcntl.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> +#include <utils/Trace.h> + +#include "google/protobuf/io/zero_copy_stream_impl_lite.h" +#include "system/iorap/src/serialize/TraceFile.pb.h" + +namespace iorap { +namespace serialize { + +ArenaPtr<proto::TraceFile> ProtobufIO::Open(std::string file_path) { + // TODO: file a bug about this. + // Note: can't use {} here, clang think it's narrowing from long->int. + android::base::unique_fd fd(TEMP_FAILURE_RETRY(::open(file_path.c_str(), O_RDONLY))); + if (fd.get() < 0) { + PLOG(DEBUG) << "ProtobufIO: open failed: " << file_path; + return nullptr; + } + + return Open(fd.get(), file_path.c_str()); +} + +ArenaPtr<proto::TraceFile> ProtobufIO::Open(int fd, const char* file_path) { + + ScopedFormatTrace atrace_protobuf_io_open(ATRACE_TAG_ACTIVITY_MANAGER, + "ProtobufIO::Open %s", + file_path); + android::base::Timer timer{}; + + struct stat buf; + if (fstat(fd, /*out*/&buf) < 0) { + PLOG(ERROR) << "ProtobufIO: open error, fstat failed: " << file_path; + return nullptr; + } + // XX: off64_t for stat::st_size ? + + // Using the mmap appears to be the only way to do zero-copy with protobuf lite. + void* data = mmap(/*addr*/nullptr, + buf.st_size, + PROT_READ, MAP_SHARED | MAP_POPULATE, + fd, + /*offset*/0); + if (data == nullptr) { + PLOG(ERROR) << "ProtobufIO: open error, mmap failed: " << file_path; + return nullptr; + } + + ArenaPtr<proto::TraceFile> protobuf_trace_file = ArenaPtr<proto::TraceFile>::Make(); + if (protobuf_trace_file == nullptr) { + LOG(ERROR) << "ProtobufIO: open error, failed to create arena: " << file_path; + return nullptr; + } + + google::protobuf::io::ArrayInputStream protobuf_input_stream{data, static_cast<int>(buf.st_size)}; + if (!protobuf_trace_file->ParseFromZeroCopyStream(/*in*/&protobuf_input_stream)) { + // XX: Does protobuf on android already have the right LogHandler ? + LOG(ERROR) << "ProtobufIO: open error, protobuf parsing failed: " << file_path; + return nullptr; + } + + if (munmap(data, buf.st_size) < 0) { + PLOG(WARNING) << "ProtobufIO: open problem, munmap failed, possibly memory leak? " + << file_path; + } + + LOG(VERBOSE) << "ProtobufIO: open succeeded: " << file_path << ", duration: " << timer; + return protobuf_trace_file; +} + +iorap::expected<size_t /*bytes written*/, int /*errno*/> ProtobufIO::WriteFully( + const ::google::protobuf::MessageLite& message, + std::string_view file_path) { + + std::string str{file_path}; + android::base::unique_fd fd(TEMP_FAILURE_RETRY( + ::open(str.c_str(), + O_CREAT | O_TRUNC | O_RDWR, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP))); // ugo: rw-rw---- + if (fd.get() < 0) { + int err = errno; + PLOG(ERROR) << "ProtobufIO: open failed: " << file_path; + return unexpected{err}; + } + + return WriteFully(message, fd.get(), file_path); +} + +iorap::expected<size_t /*bytes written*/, int /*errno*/> ProtobufIO::WriteFully( + const ::google::protobuf::MessageLite& message, + int fd, + std::string_view file_path) { + + int byte_size = message.ByteSize(); + if (byte_size < 0) { + DCHECK(false) << "Invalid protobuf size: " << byte_size; + LOG(ERROR) << "ProtobufIO: Invalid protobuf size: " << byte_size; + return unexpected{EDOM}; + } + size_t serialized_size = static_cast<size_t>(byte_size); + + // Change the file to be exactly the length of the protobuf. + if (ftruncate(fd, static_cast<off_t>(serialized_size)) < 0) { + int err = errno; + PLOG(ERROR) << "ProtobufIO: ftruncate (size=" << serialized_size << ") failed"; + return unexpected{err}; + } + + // Using the mmap appears to be the only way to do zero-copy with protobuf lite. + void* data = mmap(/*addr*/nullptr, + serialized_size, + PROT_WRITE, + MAP_SHARED, + fd, + /*offset*/0); + if (data == nullptr) { + int err = errno; + PLOG(ERROR) << "ProtobufIO: mmap failed: " << file_path; + return unexpected{err}; + } + + // Zero-copy write from protobuf to file via memory-map. + ::google::protobuf::io::ArrayOutputStream output_stream{data, byte_size}; + if (!message.SerializeToZeroCopyStream(/*inout*/&output_stream)) { + // This should never happen since we pre-allocated the file and memory map to be large + // enough to store the full protobuf. + DCHECK(false) << "ProtobufIO:: SerializeToZeroCopyStream failed despite precalculating size"; + LOG(ERROR) << "ProtobufIO: SerializeToZeroCopyStream failed"; + return unexpected{EXFULL}; + } + + // Guarantee that changes are written back prior to munmap. + if (msync(data, static_cast<size_t>(serialized_size), MS_SYNC) < 0) { + int err = errno; + PLOG(ERROR) << "ProtobufIO: msync failed"; + return unexpected{err}; + } + + if (munmap(data, serialized_size) < 0) { + PLOG(WARNING) << "ProtobufIO: munmap failed, possibly memory leak? " + << file_path; + } + + return serialized_size; +} + +} // namespace serialize +} // namespace iorap + |