| // Licensed to the Apache Software Foundation (ASF) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The ASF licenses this file |
| // to you 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. |
| |
| #pragma once |
| |
| #include <fstream> |
| #include <memory> |
| #include <string> |
| #include <vector> |
| |
| #include "common/status.h" |
| #include "kudu/util/slice.h" |
| |
| namespace kudu { |
| class AsyncLogger; |
| } |
| |
| namespace impala { |
| namespace io { |
| |
| class DataCache; |
| |
| // When working on data cache tuning or optimization, the actual cache eviction behavior |
| // is only a function of the metadata operations (which file locations are accessed in |
| // what order) and do not require actual data. Since the data for caching workloads is |
| // often large and sensitive, the data cache trace provides a way to improve cache |
| // policies without access to the original data/workloads. It also reduces the cost of |
| // testing the cache, since the metadata operations are significantly faster than the |
| // data operations. |
| // |
| // The DataCache is instrumented to trace interesting events (e.g. hits misses, stores, |
| // etc). This file implements the Tracer that writes the events out to disk and a |
| // corresponding TraceReplayer which can read the events and replay them against a |
| // cache with a particular configuration. |
| namespace trace { |
| |
| class SimpleLoggerWrapper; |
| |
| enum class EventType { |
| HIT, |
| MISS, |
| STORE, |
| STORE_FAILED_BUSY, |
| STORE_FAILED |
| }; |
| |
| // Functions to convert EventType to a string representation and back |
| EventType StringToEventType(const std::string& s); |
| std::string EventTypeToString(EventType s); |
| |
| struct TraceEvent { |
| TraceEvent(EventType type, double timestamp, std::string filename, int64_t mtime, |
| int64_t offset, int64_t entry_length, int64_t lookup_length) |
| : type(type), |
| timestamp(timestamp), |
| filename(std::move(filename)), |
| mtime(mtime), |
| offset(offset), |
| entry_length(entry_length), |
| lookup_length(lookup_length) {} |
| |
| TraceEvent() = default; |
| |
| // Type of cache event: HIT, MISS, etc. |
| EventType type; |
| |
| // Floating point timestamp of the event in seconds (see WallTime_Now()) |
| double timestamp; |
| |
| // These fields are the standard pieces of a cache key: filename, mtime, offset |
| // The one distinction is that the filename can be anonymized by being hashed. |
| // See Tracer::AnonymizeFilename() |
| std::string filename; |
| int64_t mtime; |
| int64_t offset; |
| |
| // The 'entry_length' is the size of the cache entry. For a HIT, this may be |
| // larger or smaller than the lookup length. For a MISS, this is always -1. |
| // For STORE*, this is the size of the cache entry being added. |
| int64_t entry_length; |
| // The lookup length is the size requested by the cache user. For a HIT/MISS, |
| // this is the total size that the user requested. For STORE*, this is always -1. |
| int64_t lookup_length; |
| |
| // Implement the == operator to make test logic cleaner |
| bool operator==(const TraceEvent& rhs) const { |
| return type == rhs.type && timestamp == rhs.timestamp && |
| filename == rhs.filename && mtime == rhs.mtime && offset == rhs.offset && |
| entry_length == rhs.entry_length && lookup_length == rhs.lookup_length; |
| } |
| }; |
| |
| // Parse the json input and fill in a TraceEvent representing the data |
| Status JsonToTraceEvent(const std::string& json, TraceEvent* event); |
| |
| // Construct a JSON entry from the provided TraceEvent |
| std::string TraceEventToJson(const TraceEvent& event); |
| |
| /// The file name prefix pattern used for the access trace. |
| extern const string TRACE_FILE_PREFIX; |
| |
| class Tracer { |
| public: |
| /// The Tracer records data cache trace events to a representation on disk. |
| /// 'log_dir' is the directory that the Tracer uses for output. It is using |
| /// the SimpleLogger under the covers, so it obeys the 'max_entries_per_file' |
| /// and 'max_trace_files' to limit the total number of entries on disk. |
| /// If 'anonymize_trace' is true, the filenames in the trace are anonymized |
| /// by hashing them (see AnonymizeFilename()). |
| explicit Tracer(std::string log_dir, uint64_t max_entries_per_file, |
| int32_t max_trace_files, bool anonymize_trace); |
| |
| ~Tracer(); |
| |
| // Initialize the tracer |
| Status Init(); |
| |
| // Force the logger to flush its buffers |
| void Flush(); |
| |
| // Record an entry into the log |
| void Trace(EventType type, double timestamp, kudu::Slice filename, |
| int64_t mtime, int64_t offset, int64_t lookup_len, int64_t entry_len); |
| |
| private: |
| // The underlying logger that we wrap with the AsyncLogger wrapper |
| // 'logger_'. NOTE: AsyncLogger consumes a raw pointer which must |
| // outlive the AsyncLogger instance, so it's important that these |
| // are declared in this order (logger_ must destruct before |
| // underlying_logger_). |
| std::unique_ptr<SimpleLoggerWrapper> underlying_logger_; |
| // The async wrapper around underlying_logger_ (see above). |
| std::unique_ptr<kudu::AsyncLogger> logger_; |
| |
| // Whether to anonymize the trace by hashing the filenames |
| bool anonymize_trace_; |
| |
| // This anonymizes the filename by hashing it and encoding that in a Base64 string. |
| // This returns a 22 character string. |
| std::string AnonymizeFilename(kudu::Slice filename); |
| }; |
| |
| // Information about the cache outcomes for a particular workload |
| struct CacheHitStatistics { |
| uint64_t hits = 0; |
| uint64_t partial_hits = 0; |
| uint64_t hit_bytes = 0; |
| uint64_t misses = 0; |
| uint64_t miss_bytes = 0; |
| uint64_t stores = 0; |
| uint64_t failed_stores = 0; |
| }; |
| |
| // The access trace has a TraceEvent JSON entry per line in the file. This is a helper |
| // class to iterate over the TraceEvents from a single file. |
| class TraceFileIterator { |
| public: |
| TraceFileIterator(std::string filename) : filename_(std::move(filename)) {} |
| |
| // Initialize the iterator |
| Status Init(); |
| |
| // Get the next TraceEvent. If not at the end of the file, 'event' is filled with |
| // the next TraceEvent. If at the end of the file, 'done' is set to true and |
| // 'event' is untouched. Returns status in the event of an error. |
| Status GetNextEvent(TraceEvent* event, bool* done); |
| |
| private: |
| // File to iterate over |
| std::string filename_; |
| // Set to true in Init() |
| bool initialized_ = false; |
| // Input stream reading the file |
| std::ifstream stream_; |
| }; |
| |
| class TraceReplayer { |
| public: |
| // The TraceReplayer creates a DataCache with the provided 'trace_configuration' |
| // in the special trace replay mode. This allows replaying the trace without any |
| // underlying data or filesystem operations. The 'trace_configuration' matches the |
| // configuration format from the DataCache constructor. See data-cache.h. |
| TraceReplayer(std::string trace_configuration); |
| |
| ~TraceReplayer(); |
| |
| // Initialize the replayer |
| Status Init(); |
| |
| // Replay a single trace file |
| Status ReplayFile(std::string filename); |
| |
| // Replay a directory of trace files as generated by the Tracer above. |
| Status ReplayDirectory(std::string directory); |
| |
| // Get the hit statistics that are recorded in the original trace. |
| CacheHitStatistics GetOriginalTraceStatistics() const { |
| DCHECK(initialized_); |
| return original_trace_stats_; |
| } |
| |
| // Get the hit statistics for the replay of the trace. |
| CacheHitStatistics GetReplayStatistics() const { |
| DCHECK(initialized_); |
| return replay_stats_; |
| } |
| |
| private: |
| |
| // Update the original trace statistics given this TraceEvent |
| void UpdateTraceStats(const TraceEvent& entry); |
| |
| // Replay an individual TraceEvent against the current cache. |
| void ReplayEntry(const TraceEvent& entry); |
| |
| // Configuration of the cache used for the replay |
| std::string trace_configuration_; |
| |
| // Set to true in Init(). |
| bool initialized_ = false; |
| |
| // DataCache used for the trace replay |
| std::unique_ptr<DataCache> data_cache_; |
| |
| // The hit statistics that are recorded in the original trace. This is |
| // purely a summation of the contents in the trace (i.e. hits, misses, etc). |
| CacheHitStatistics original_trace_stats_; |
| |
| // The hit statistics for the replay of the trace. The outcomes of the |
| // trace replay are independent from the original trace and are dependent |
| // on the cache configuration. Even with equivalent cache configurations, the |
| // outcome can differ from the original trace due to concurrency. |
| CacheHitStatistics replay_stats_; |
| }; |
| } // namespace trace |
| } // namespace io |
| } // namespace impala |