blob: cb680243ccc2cd1f7f0bb37b443933b62fabaa0f [file]
// 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