| // Copyright 2016 The Fuchsia 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 TRACE_READER_READER_H_ |
| #define TRACE_READER_READER_H_ |
| |
| #include <zircon/assert.h> |
| |
| #include <memory> |
| #include <string_view> |
| #include <utility> |
| |
| #include <fbl/algorithm.h> |
| #include <fbl/function.h> |
| #include <fbl/intrusive_hash_table.h> |
| #include <fbl/macros.h> |
| #include <fbl/string.h> |
| #include <trace-reader/records.h> |
| |
| namespace trace { |
| |
| class Chunk; |
| |
| // Reads trace records. |
| // The input is a collection of |Chunk| objects (see class Chunk below). |
| // |
| // One use-case is reading records across an entire trace, which means across |
| // multiple providers, and makes no assumptions about the ordering of records |
| // it receives other than requiring objects referenced by ID (threads, strings) |
| // are defined before they are used. Note that as a consequence of this, one |
| // |TraceReader| class will maintain state of reading the entire trace: it |
| // generally doesn't work to create multiple |TraceReader| classes for one |
| // trace. |
| class TraceReader { |
| public: |
| // Called once for each record read by |ReadRecords|. |
| // TODO(jeffbrown): It would be nice to get rid of this by making |ReadRecords| |
| // return std::optional<Record> as an out parameter. |
| using RecordConsumer = fbl::Function<void(Record)>; |
| |
| // Callback invoked when decoding errors are detected in the trace. |
| using ErrorHandler = fbl::Function<void(fbl::String)>; |
| |
| explicit TraceReader(RecordConsumer record_consumer, ErrorHandler error_handler); |
| |
| // Reads as many records as possible from the chunk, invoking the |
| // record consumer for each one. Returns true if the stream could possibly |
| // contain more records if the chunk were extended with new data. |
| // Returns false if the trace stream is unrecoverably corrupt and no |
| // further decoding is possible. May be called repeatedly with new |
| // chunks as they become available to resume decoding. |
| bool ReadRecords(Chunk& chunk); |
| |
| // Gets the current trace provider id. |
| // Returns 0 if no providers have been registered yet. |
| ProviderId current_provider_id() const { return current_provider_->id; } |
| |
| // Gets the name of the current trace provider. |
| // Returns an empty string if the current provider id is 0. |
| const fbl::String& current_provider_name() const { return current_provider_->name; } |
| |
| // Gets the name of the specified provider, or an empty string if there is |
| // no such provider. |
| fbl::String GetProviderName(ProviderId id) const; |
| |
| const ErrorHandler& error_handler() const { return error_handler_; } |
| |
| protected: |
| void ReportError(fbl::String error) const; |
| |
| private: |
| bool ReadMetadataRecord(Chunk& record, RecordHeader header); |
| bool ReadInitializationRecord(Chunk& record, RecordHeader header); |
| bool ReadStringRecord(Chunk& record, RecordHeader header); |
| bool ReadThreadRecord(Chunk& record, RecordHeader header); |
| bool ReadEventRecord(Chunk& record, RecordHeader header); |
| bool ReadBlobRecord(Chunk& record, RecordHeader header, void** out_ptr); |
| bool ReadKernelObjectRecord(Chunk& record, RecordHeader header); |
| bool ReadContextSwitchRecord(Chunk& record, RecordHeader header); |
| bool ReadLogRecord(Chunk& record, RecordHeader header); |
| bool ReadArguments(Chunk& record, size_t count, fbl::Vector<Argument>* out_arguments); |
| |
| bool ReadLargeRecord(Chunk& record, RecordHeader header); |
| bool ReadLargeBlob(Chunk& record, RecordHeader header); |
| |
| void SetCurrentProvider(ProviderId id); |
| void RegisterProvider(ProviderId id, fbl::String name); |
| void RegisterString(trace_string_index_t index, fbl::String string); |
| void RegisterThread(trace_thread_index_t index, const ProcessThread& process_thread); |
| |
| bool DecodeStringRef(Chunk& chunk, trace_encoded_string_ref_t string_ref, |
| fbl::String* out_string) const; |
| bool DecodeThreadRef(Chunk& chunk, trace_encoded_thread_ref_t thread_ref, |
| ProcessThread* out_process_thread) const; |
| |
| RecordConsumer const record_consumer_; |
| ErrorHandler const error_handler_; |
| |
| RecordHeader pending_header_ = 0u; |
| |
| struct StringTableEntry : public fbl::SinglyLinkedListable<std::unique_ptr<StringTableEntry>> { |
| StringTableEntry(trace_string_index_t index, fbl::String string) |
| : index(index), string(std::move(string)) {} |
| |
| trace_string_index_t const index; |
| fbl::String const string; |
| |
| // Used by the hash table. |
| trace_string_index_t GetKey() const { return index; } |
| static size_t GetHash(trace_string_index_t key) { return key; } |
| }; |
| |
| struct ThreadTableEntry : public fbl::SinglyLinkedListable<std::unique_ptr<ThreadTableEntry>> { |
| ThreadTableEntry(trace_thread_index_t index, const ProcessThread& process_thread) |
| : index(index), process_thread(process_thread) {} |
| |
| trace_thread_index_t const index; |
| ProcessThread const process_thread; |
| |
| // Used by the hash table. |
| trace_thread_index_t GetKey() const { return index; } |
| static size_t GetHash(trace_thread_index_t key) { return key; } |
| }; |
| |
| struct ProviderInfo : public fbl::SinglyLinkedListable<std::unique_ptr<ProviderInfo>> { |
| ProviderId id; |
| fbl::String name; |
| |
| // TODO(fxbug.dev/30999): It would be more efficient to use something like |
| // std::unordered_map<> here. In particular, the table entries are |
| // small enough that it doesn't make sense to heap allocate them |
| // individually. |
| fbl::HashTable<trace_string_index_t, std::unique_ptr<StringTableEntry>> string_table; |
| fbl::HashTable<trace_thread_index_t, std::unique_ptr<ThreadTableEntry>> thread_table; |
| |
| // Used by the hash table. |
| ProviderId GetKey() const { return id; } |
| static size_t GetHash(ProviderId key) { return key; } |
| }; |
| |
| fbl::HashTable<ProviderId, std::unique_ptr<ProviderInfo>> providers_; |
| ProviderInfo* current_provider_ = nullptr; |
| |
| DISALLOW_COPY_ASSIGN_AND_MOVE(TraceReader); |
| }; |
| |
| // Provides support for reading sequences of 64-bit words from a contiguous |
| // region of memory. The main use-case of this class is input to |TraceReader|. |
| class Chunk final { |
| public: |
| Chunk(); |
| explicit Chunk(const uint64_t* begin, size_t num_words); |
| |
| uint64_t current_byte_offset() const { |
| return (reinterpret_cast<const uint8_t*>(current_) - reinterpret_cast<const uint8_t*>(begin_)); |
| } |
| uint64_t remaining_words() const { return end_ - current_; } |
| |
| // Reads from the chunk, maintaining proper alignment. |
| // Returns true on success, false if the chunk has insufficient remaining |
| // words to satisfy the request. |
| bool ReadUint64(uint64_t* out_value); |
| bool ReadInt64(int64_t* out_value); |
| bool ReadDouble(double* out_value); |
| bool ReadString(size_t length, std::string_view* out_string); |
| bool ReadChunk(size_t num_words, Chunk* out_chunk); |
| bool ReadInPlace(size_t num_words, const void** out_ptr); |
| |
| private: |
| const uint64_t* begin_; |
| const uint64_t* current_; |
| const uint64_t* end_; |
| }; |
| |
| } // namespace trace |
| |
| #endif // TRACE_READER_READER_H_ |