/src/rocksdb/trace_replay/io_tracer.cc
Line | Count | Source |
1 | | // Copyright (c) 2011-present, Facebook, Inc. All rights reserved. |
2 | | // This source code is licensed under both the GPLv2 (found in the |
3 | | // COPYING file in the root directory) and Apache 2.0 License |
4 | | // (found in the LICENSE.Apache file in the root directory). |
5 | | |
6 | | #include "trace_replay/io_tracer.h" |
7 | | |
8 | | #include <cinttypes> |
9 | | #include <cstdio> |
10 | | #include <cstdlib> |
11 | | |
12 | | #include "db/db_impl/db_impl.h" |
13 | | #include "db/dbformat.h" |
14 | | #include "rocksdb/slice.h" |
15 | | #include "rocksdb/system_clock.h" |
16 | | #include "rocksdb/trace_reader_writer.h" |
17 | | #include "util/coding.h" |
18 | | #include "util/hash.h" |
19 | | #include "util/string_util.h" |
20 | | |
21 | | namespace ROCKSDB_NAMESPACE { |
22 | | IOTraceWriter::IOTraceWriter(SystemClock* clock, |
23 | | const TraceOptions& trace_options, |
24 | | std::unique_ptr<TraceWriter>&& trace_writer) |
25 | 0 | : clock_(clock), |
26 | 0 | trace_options_(trace_options), |
27 | 0 | trace_writer_(std::move(trace_writer)) {} |
28 | | |
29 | | Status IOTraceWriter::WriteIOOp(const IOTraceRecord& record, |
30 | 0 | IODebugContext* dbg) { |
31 | 0 | uint64_t trace_file_size = trace_writer_->GetFileSize(); |
32 | 0 | if (trace_file_size > trace_options_.max_trace_file_size) { |
33 | 0 | return Status::OK(); |
34 | 0 | } |
35 | 0 | Trace trace; |
36 | 0 | trace.ts = record.access_timestamp; |
37 | 0 | trace.type = record.trace_type; |
38 | 0 | PutFixed64(&trace.payload, record.io_op_data); |
39 | 0 | Slice file_operation(record.file_operation); |
40 | 0 | PutLengthPrefixedSlice(&trace.payload, file_operation); |
41 | 0 | PutFixed64(&trace.payload, record.latency); |
42 | 0 | Slice io_status(record.io_status); |
43 | 0 | PutLengthPrefixedSlice(&trace.payload, io_status); |
44 | 0 | Slice file_name(record.file_name); |
45 | 0 | PutLengthPrefixedSlice(&trace.payload, file_name); |
46 | | |
47 | | // Each bit in io_op_data stores which corresponding info from IOTraceOp will |
48 | | // be added in the trace. Foreg, if bit at position 1 is set then |
49 | | // IOTraceOp::kIOLen (length) will be logged in the record (Since |
50 | | // IOTraceOp::kIOLen = 1 in the enum). So find all the set positions in |
51 | | // io_op_data one by one and, update corresponsing info in the trace record, |
52 | | // unset that bit to find other set bits until io_op_data = 0. |
53 | | /* Write remaining options based on io_op_data set by file operation */ |
54 | 0 | int64_t io_op_data = static_cast<int64_t>(record.io_op_data); |
55 | 0 | while (io_op_data) { |
56 | | // Find the rightmost set bit. |
57 | 0 | uint32_t set_pos = static_cast<uint32_t>(log2(io_op_data & -io_op_data)); |
58 | 0 | switch (set_pos) { |
59 | 0 | case IOTraceOp::kIOFileSize: |
60 | 0 | PutFixed64(&trace.payload, record.file_size); |
61 | 0 | break; |
62 | 0 | case IOTraceOp::kIOLen: |
63 | 0 | PutFixed64(&trace.payload, record.len); |
64 | 0 | break; |
65 | 0 | case IOTraceOp::kIOOffset: |
66 | 0 | PutFixed64(&trace.payload, record.offset); |
67 | 0 | break; |
68 | 0 | default: |
69 | 0 | assert(false); |
70 | 0 | } |
71 | | // unset the rightmost bit. |
72 | 0 | io_op_data &= (io_op_data - 1); |
73 | 0 | } |
74 | | |
75 | 0 | int64_t trace_data = 0; |
76 | 0 | if (dbg) { |
77 | 0 | trace_data = static_cast<int64_t>(dbg->trace_data); |
78 | 0 | } |
79 | 0 | PutFixed64(&trace.payload, trace_data); |
80 | 0 | while (trace_data) { |
81 | | // Find the rightmost set bit. |
82 | 0 | uint32_t set_pos = static_cast<uint32_t>(log2(trace_data & -trace_data)); |
83 | 0 | switch (set_pos) { |
84 | 0 | case IODebugContext::TraceData::kRequestID: { |
85 | 0 | Slice request_id(*dbg->request_id); |
86 | 0 | PutLengthPrefixedSlice(&trace.payload, request_id); |
87 | 0 | } break; |
88 | 0 | default: |
89 | 0 | assert(false); |
90 | 0 | } |
91 | | // unset the rightmost bit. |
92 | 0 | trace_data &= (trace_data - 1); |
93 | 0 | } |
94 | | |
95 | 0 | std::string encoded_trace; |
96 | 0 | TracerHelper::EncodeTrace(trace, &encoded_trace); |
97 | 0 | return trace_writer_->Write(encoded_trace); |
98 | 0 | } |
99 | | |
100 | 0 | Status IOTraceWriter::WriteHeader() { |
101 | 0 | Trace trace; |
102 | 0 | trace.ts = clock_->NowMicros(); |
103 | 0 | trace.type = TraceType::kTraceBegin; |
104 | 0 | PutLengthPrefixedSlice(&trace.payload, kTraceMagic); |
105 | 0 | PutFixed32(&trace.payload, kMajorVersion); |
106 | 0 | PutFixed32(&trace.payload, kMinorVersion); |
107 | 0 | std::string encoded_trace; |
108 | 0 | TracerHelper::EncodeTrace(trace, &encoded_trace); |
109 | 0 | return trace_writer_->Write(encoded_trace); |
110 | 0 | } |
111 | | |
112 | | IOTraceReader::IOTraceReader(std::unique_ptr<TraceReader>&& reader) |
113 | 0 | : trace_reader_(std::move(reader)) {} |
114 | | |
115 | 0 | Status IOTraceReader::ReadHeader(IOTraceHeader* header) { |
116 | 0 | assert(header != nullptr); |
117 | 0 | std::string encoded_trace; |
118 | 0 | Status s = trace_reader_->Read(&encoded_trace); |
119 | 0 | if (!s.ok()) { |
120 | 0 | return s; |
121 | 0 | } |
122 | 0 | Trace trace; |
123 | 0 | s = TracerHelper::DecodeTrace(encoded_trace, &trace); |
124 | 0 | if (!s.ok()) { |
125 | 0 | return s; |
126 | 0 | } |
127 | 0 | header->start_time = trace.ts; |
128 | 0 | Slice enc_slice = Slice(trace.payload); |
129 | 0 | Slice magic_number; |
130 | 0 | if (!GetLengthPrefixedSlice(&enc_slice, &magic_number)) { |
131 | 0 | return Status::Corruption( |
132 | 0 | "Corrupted header in the trace file: Failed to read the magic number."); |
133 | 0 | } |
134 | 0 | if (magic_number.ToString() != kTraceMagic) { |
135 | 0 | return Status::Corruption( |
136 | 0 | "Corrupted header in the trace file: Magic number does not match."); |
137 | 0 | } |
138 | 0 | if (!GetFixed32(&enc_slice, &header->rocksdb_major_version)) { |
139 | 0 | return Status::Corruption( |
140 | 0 | "Corrupted header in the trace file: Failed to read rocksdb major " |
141 | 0 | "version number."); |
142 | 0 | } |
143 | 0 | if (!GetFixed32(&enc_slice, &header->rocksdb_minor_version)) { |
144 | 0 | return Status::Corruption( |
145 | 0 | "Corrupted header in the trace file: Failed to read rocksdb minor " |
146 | 0 | "version number."); |
147 | 0 | } |
148 | | // We should have retrieved all information in the header. |
149 | 0 | if (!enc_slice.empty()) { |
150 | 0 | return Status::Corruption( |
151 | 0 | "Corrupted header in the trace file: The length of header is too " |
152 | 0 | "long."); |
153 | 0 | } |
154 | 0 | return Status::OK(); |
155 | 0 | } |
156 | | |
157 | 0 | Status IOTraceReader::ReadIOOp(IOTraceRecord* record) { |
158 | 0 | assert(record); |
159 | 0 | std::string encoded_trace; |
160 | 0 | Status s = trace_reader_->Read(&encoded_trace); |
161 | 0 | if (!s.ok()) { |
162 | 0 | return s; |
163 | 0 | } |
164 | 0 | Trace trace; |
165 | 0 | s = TracerHelper::DecodeTrace(encoded_trace, &trace); |
166 | 0 | if (!s.ok()) { |
167 | 0 | return s; |
168 | 0 | } |
169 | 0 | record->access_timestamp = trace.ts; |
170 | 0 | record->trace_type = trace.type; |
171 | 0 | Slice enc_slice = Slice(trace.payload); |
172 | |
|
173 | 0 | if (!GetFixed64(&enc_slice, &record->io_op_data)) { |
174 | 0 | return Status::Incomplete( |
175 | 0 | "Incomplete access record: Failed to read trace data."); |
176 | 0 | } |
177 | 0 | Slice file_operation; |
178 | 0 | if (!GetLengthPrefixedSlice(&enc_slice, &file_operation)) { |
179 | 0 | return Status::Incomplete( |
180 | 0 | "Incomplete access record: Failed to read file operation."); |
181 | 0 | } |
182 | 0 | record->file_operation = file_operation.ToString(); |
183 | 0 | if (!GetFixed64(&enc_slice, &record->latency)) { |
184 | 0 | return Status::Incomplete( |
185 | 0 | "Incomplete access record: Failed to read latency."); |
186 | 0 | } |
187 | 0 | Slice io_status; |
188 | 0 | if (!GetLengthPrefixedSlice(&enc_slice, &io_status)) { |
189 | 0 | return Status::Incomplete( |
190 | 0 | "Incomplete access record: Failed to read IO status."); |
191 | 0 | } |
192 | 0 | record->io_status = io_status.ToString(); |
193 | 0 | Slice file_name; |
194 | 0 | if (!GetLengthPrefixedSlice(&enc_slice, &file_name)) { |
195 | 0 | return Status::Incomplete( |
196 | 0 | "Incomplete access record: Failed to read file name."); |
197 | 0 | } |
198 | 0 | record->file_name = file_name.ToString(); |
199 | | |
200 | | // Each bit in io_op_data stores which corresponding info from IOTraceOp will |
201 | | // be added in the trace. Foreg, if bit at position 1 is set then |
202 | | // IOTraceOp::kIOLen (length) will be logged in the record (Since |
203 | | // IOTraceOp::kIOLen = 1 in the enum). So find all the set positions in |
204 | | // io_op_data one by one and, update corresponsing info in the trace record, |
205 | | // unset that bit to find other set bits until io_op_data = 0. |
206 | | /* Read remaining options based on io_op_data set by file operation */ |
207 | | // Assuming 63 bits will be used at max. |
208 | 0 | int64_t io_op_data = static_cast<int64_t>(record->io_op_data); |
209 | 0 | while (io_op_data) { |
210 | | // Find the rightmost set bit. |
211 | 0 | uint32_t set_pos = static_cast<uint32_t>(log2(io_op_data & -io_op_data)); |
212 | 0 | switch (set_pos) { |
213 | 0 | case IOTraceOp::kIOFileSize: |
214 | 0 | if (!GetFixed64(&enc_slice, &record->file_size)) { |
215 | 0 | return Status::Incomplete( |
216 | 0 | "Incomplete access record: Failed to read file size."); |
217 | 0 | } |
218 | 0 | break; |
219 | 0 | case IOTraceOp::kIOLen: |
220 | 0 | if (!GetFixed64(&enc_slice, &record->len)) { |
221 | 0 | return Status::Incomplete( |
222 | 0 | "Incomplete access record: Failed to read length."); |
223 | 0 | } |
224 | 0 | break; |
225 | 0 | case IOTraceOp::kIOOffset: |
226 | 0 | if (!GetFixed64(&enc_slice, &record->offset)) { |
227 | 0 | return Status::Incomplete( |
228 | 0 | "Incomplete access record: Failed to read offset."); |
229 | 0 | } |
230 | 0 | break; |
231 | 0 | default: |
232 | 0 | assert(false); |
233 | 0 | } |
234 | | // unset the rightmost bit. |
235 | 0 | io_op_data &= (io_op_data - 1); |
236 | 0 | } |
237 | | |
238 | 0 | if (!GetFixed64(&enc_slice, &record->trace_data)) { |
239 | 0 | return Status::Incomplete( |
240 | 0 | "Incomplete access record: Failed to read trace op."); |
241 | 0 | } |
242 | 0 | int64_t trace_data = static_cast<int64_t>(record->trace_data); |
243 | 0 | while (trace_data) { |
244 | | // Find the rightmost set bit. |
245 | 0 | uint32_t set_pos = static_cast<uint32_t>(log2(trace_data & -trace_data)); |
246 | 0 | switch (set_pos) { |
247 | 0 | case IODebugContext::TraceData::kRequestID: { |
248 | 0 | Slice request_id; |
249 | 0 | if (!GetLengthPrefixedSlice(&enc_slice, &request_id)) { |
250 | 0 | return Status::Incomplete( |
251 | 0 | "Incomplete access record: Failed to request id."); |
252 | 0 | } |
253 | 0 | record->request_id = request_id.ToString(); |
254 | 0 | } break; |
255 | 0 | default: |
256 | 0 | assert(false); |
257 | 0 | } |
258 | | // unset the rightmost bit. |
259 | 0 | trace_data &= (trace_data - 1); |
260 | 0 | } |
261 | | |
262 | 0 | return Status::OK(); |
263 | 0 | } |
264 | | |
265 | 44.8k | IOTracer::IOTracer() : tracing_enabled(false) { writer_.store(nullptr); } |
266 | | |
267 | 44.8k | IOTracer::~IOTracer() { EndIOTrace(); } |
268 | | |
269 | | Status IOTracer::StartIOTrace(SystemClock* clock, |
270 | | const TraceOptions& trace_options, |
271 | 0 | std::unique_ptr<TraceWriter>&& trace_writer) { |
272 | 0 | InstrumentedMutexLock lock_guard(&trace_writer_mutex_); |
273 | 0 | if (writer_.load()) { |
274 | 0 | return Status::Busy(); |
275 | 0 | } |
276 | 0 | trace_options_ = trace_options; |
277 | 0 | writer_.store( |
278 | 0 | new IOTraceWriter(clock, trace_options, std::move(trace_writer))); |
279 | 0 | tracing_enabled = true; |
280 | 0 | return writer_.load()->WriteHeader(); |
281 | 0 | } |
282 | | |
283 | 44.8k | void IOTracer::EndIOTrace() { |
284 | 44.8k | InstrumentedMutexLock lock_guard(&trace_writer_mutex_); |
285 | 44.8k | if (!writer_.load()) { |
286 | 44.8k | return; |
287 | 44.8k | } |
288 | 0 | delete writer_.load(); |
289 | 0 | writer_.store(nullptr); |
290 | 0 | tracing_enabled = false; |
291 | 0 | } |
292 | | |
293 | 0 | void IOTracer::WriteIOOp(const IOTraceRecord& record, IODebugContext* dbg) { |
294 | 0 | if (!writer_.load()) { |
295 | 0 | return; |
296 | 0 | } |
297 | 0 | InstrumentedMutexLock lock_guard(&trace_writer_mutex_); |
298 | 0 | if (!writer_.load()) { |
299 | 0 | return; |
300 | 0 | } |
301 | 0 | writer_.load()->WriteIOOp(record, dbg).PermitUncheckedError(); |
302 | 0 | } |
303 | | } // namespace ROCKSDB_NAMESPACE |