Yabin Cui | ed91cd9 | 2015-04-28 15:54:13 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2015 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | #include "record_file.h" |
| 18 | |
| 19 | #include <fcntl.h> |
| 20 | #include <string.h> |
| 21 | #include <sys/mman.h> |
| 22 | #include <unistd.h> |
| 23 | #include <set> |
Yabin Cui | 568b82a | 2015-05-05 19:58:07 -0700 | [diff] [blame] | 24 | #include <vector> |
Yabin Cui | ed91cd9 | 2015-04-28 15:54:13 -0700 | [diff] [blame] | 25 | |
| 26 | #include <base/logging.h> |
| 27 | |
| 28 | #include "event_fd.h" |
| 29 | #include "perf_event.h" |
| 30 | #include "record.h" |
| 31 | #include "utils.h" |
| 32 | |
| 33 | using namespace PerfFileFormat; |
| 34 | |
| 35 | std::unique_ptr<RecordFileWriter> RecordFileWriter::CreateInstance( |
| 36 | const std::string& filename, const perf_event_attr& event_attr, |
| 37 | const std::vector<std::unique_ptr<EventFd>>& event_fds) { |
| 38 | FILE* fp = fopen(filename.c_str(), "web+"); |
| 39 | if (fp == nullptr) { |
| 40 | PLOG(ERROR) << "failed to open record file '" << filename << "'"; |
| 41 | return nullptr; |
| 42 | } |
| 43 | |
| 44 | auto writer = std::unique_ptr<RecordFileWriter>(new RecordFileWriter(filename, fp)); |
| 45 | if (!writer->WriteAttrSection(event_attr, event_fds)) { |
| 46 | return nullptr; |
| 47 | } |
| 48 | return writer; |
| 49 | } |
| 50 | |
| 51 | RecordFileWriter::RecordFileWriter(const std::string& filename, FILE* fp) |
Yabin Cui | 568b82a | 2015-05-05 19:58:07 -0700 | [diff] [blame] | 52 | : filename_(filename), |
| 53 | record_fp_(fp), |
| 54 | attr_section_offset_(0), |
| 55 | attr_section_size_(0), |
| 56 | data_section_offset_(0), |
| 57 | data_section_size_(0), |
| 58 | feature_count_(0), |
| 59 | current_feature_index_(0) { |
Yabin Cui | ed91cd9 | 2015-04-28 15:54:13 -0700 | [diff] [blame] | 60 | } |
| 61 | |
| 62 | RecordFileWriter::~RecordFileWriter() { |
| 63 | if (record_fp_ != nullptr) { |
| 64 | Close(); |
| 65 | } |
| 66 | } |
| 67 | |
| 68 | bool RecordFileWriter::WriteAttrSection(const perf_event_attr& event_attr, |
| 69 | const std::vector<std::unique_ptr<EventFd>>& event_fds) { |
| 70 | // Skip file header part. |
| 71 | if (fseek(record_fp_, sizeof(FileHeader), SEEK_SET) == -1) { |
| 72 | return false; |
| 73 | } |
| 74 | |
| 75 | // Write id section. |
| 76 | std::vector<uint64_t> ids; |
| 77 | for (auto& event_fd : event_fds) { |
| 78 | ids.push_back(event_fd->Id()); |
| 79 | } |
| 80 | long id_section_offset = ftell(record_fp_); |
| 81 | if (id_section_offset == -1) { |
| 82 | return false; |
| 83 | } |
| 84 | if (!Write(ids.data(), ids.size() * sizeof(uint64_t))) { |
| 85 | return false; |
| 86 | } |
| 87 | |
| 88 | // Write attr section. |
| 89 | FileAttr attr; |
| 90 | attr.attr = event_attr; |
| 91 | attr.ids.offset = id_section_offset; |
| 92 | attr.ids.size = ids.size() * sizeof(uint64_t); |
| 93 | |
| 94 | long attr_section_offset = ftell(record_fp_); |
| 95 | if (attr_section_offset == -1) { |
| 96 | return false; |
| 97 | } |
| 98 | if (!Write(&attr, sizeof(attr))) { |
| 99 | return false; |
| 100 | } |
| 101 | |
| 102 | long data_section_offset = ftell(record_fp_); |
| 103 | if (data_section_offset == -1) { |
| 104 | return false; |
| 105 | } |
| 106 | |
| 107 | attr_section_offset_ = attr_section_offset; |
| 108 | attr_section_size_ = sizeof(attr); |
| 109 | data_section_offset_ = data_section_offset; |
| 110 | |
| 111 | // Save event_attr for use when reading records. |
| 112 | event_attr_ = event_attr; |
| 113 | return true; |
| 114 | } |
| 115 | |
| 116 | bool RecordFileWriter::WriteData(const void* buf, size_t len) { |
| 117 | if (!Write(buf, len)) { |
| 118 | return false; |
| 119 | } |
| 120 | data_section_size_ += len; |
| 121 | return true; |
| 122 | } |
| 123 | |
| 124 | bool RecordFileWriter::Write(const void* buf, size_t len) { |
| 125 | if (fwrite(buf, len, 1, record_fp_) != 1) { |
| 126 | PLOG(ERROR) << "failed to write to record file '" << filename_ << "'"; |
| 127 | return false; |
| 128 | } |
| 129 | return true; |
| 130 | } |
| 131 | |
Yabin Cui | 568b82a | 2015-05-05 19:58:07 -0700 | [diff] [blame] | 132 | void RecordFileWriter::GetHitModulesInBuffer(const char* p, const char* end, |
| 133 | std::vector<std::string>* hit_kernel_modules, |
| 134 | std::vector<std::string>* hit_user_files) { |
| 135 | std::vector<std::unique_ptr<const Record>> kernel_mmaps; |
| 136 | std::vector<std::unique_ptr<const Record>> user_mmaps; |
| 137 | std::set<std::string> hit_kernel_set; |
| 138 | std::set<std::string> hit_user_set; |
| 139 | |
| 140 | while (p < end) { |
| 141 | auto header = reinterpret_cast<const perf_event_header*>(p); |
| 142 | CHECK_LE(p + header->size, end); |
| 143 | p += header->size; |
| 144 | std::unique_ptr<const Record> record = ReadRecordFromBuffer(event_attr_, header); |
| 145 | CHECK(record != nullptr); |
| 146 | if (record->header.type == PERF_RECORD_MMAP) { |
| 147 | if (record->header.misc & PERF_RECORD_MISC_KERNEL) { |
| 148 | kernel_mmaps.push_back(std::move(record)); |
| 149 | } else { |
| 150 | user_mmaps.push_back(std::move(record)); |
| 151 | } |
| 152 | } else if (record->header.type == PERF_RECORD_SAMPLE) { |
| 153 | auto& r = *static_cast<const SampleRecord*>(record.get()); |
| 154 | if (!(r.sample_type & PERF_SAMPLE_IP) || !(r.sample_type & PERF_SAMPLE_TID)) { |
| 155 | continue; |
| 156 | } |
| 157 | uint32_t pid = r.tid_data.pid; |
| 158 | uint64_t ip = r.ip_data.ip; |
| 159 | if (r.header.misc & PERF_RECORD_MISC_KERNEL) { |
| 160 | // Loop from back to front, because new MmapRecords are inserted at the end of the mmaps, |
| 161 | // and we want to match the newest one. |
| 162 | for (auto it = kernel_mmaps.rbegin(); it != kernel_mmaps.rend(); ++it) { |
| 163 | auto& m_record = *reinterpret_cast<const MmapRecord*>(it->get()); |
| 164 | if (ip >= m_record.data.addr && ip < m_record.data.addr + m_record.data.len) { |
| 165 | hit_kernel_set.insert(m_record.filename); |
| 166 | break; |
| 167 | } |
| 168 | } |
| 169 | } else { |
| 170 | for (auto it = user_mmaps.rbegin(); it != user_mmaps.rend(); ++it) { |
| 171 | auto& m_record = *reinterpret_cast<const MmapRecord*>(it->get()); |
| 172 | if (pid == m_record.data.pid && ip >= m_record.data.addr && |
| 173 | ip < m_record.data.addr + m_record.data.len) { |
| 174 | hit_user_set.insert(m_record.filename); |
| 175 | break; |
| 176 | } |
| 177 | } |
| 178 | } |
| 179 | } |
| 180 | } |
| 181 | hit_kernel_modules->clear(); |
| 182 | hit_kernel_modules->insert(hit_kernel_modules->begin(), hit_kernel_set.begin(), |
| 183 | hit_kernel_set.end()); |
| 184 | hit_user_files->clear(); |
| 185 | hit_user_files->insert(hit_user_files->begin(), hit_user_set.begin(), hit_user_set.end()); |
| 186 | } |
| 187 | |
| 188 | bool RecordFileWriter::GetHitModules(std::vector<std::string>* hit_kernel_modules, |
| 189 | std::vector<std::string>* hit_user_files) { |
| 190 | if (fflush(record_fp_) != 0) { |
| 191 | PLOG(ERROR) << "fflush() failed"; |
| 192 | return false; |
| 193 | } |
| 194 | if (fseek(record_fp_, 0, SEEK_END) == -1) { |
| 195 | PLOG(ERROR) << "fseek() failed"; |
| 196 | return false; |
| 197 | } |
| 198 | long file_size = ftell(record_fp_); |
| 199 | if (file_size == -1) { |
| 200 | PLOG(ERROR) << "ftell() failed"; |
| 201 | return false; |
| 202 | } |
| 203 | size_t mmap_len = file_size; |
| 204 | void* mmap_addr = mmap(nullptr, mmap_len, PROT_READ, MAP_SHARED, fileno(record_fp_), 0); |
| 205 | if (mmap_addr == MAP_FAILED) { |
| 206 | PLOG(ERROR) << "mmap() failed"; |
| 207 | return false; |
| 208 | } |
| 209 | const char* data_section_p = reinterpret_cast<const char*>(mmap_addr) + data_section_offset_; |
| 210 | const char* data_section_end = data_section_p + data_section_size_; |
| 211 | GetHitModulesInBuffer(data_section_p, data_section_end, hit_kernel_modules, hit_user_files); |
| 212 | |
| 213 | if (munmap(mmap_addr, mmap_len) == -1) { |
| 214 | PLOG(ERROR) << "munmap() failed"; |
| 215 | return false; |
| 216 | } |
| 217 | return true; |
| 218 | } |
| 219 | |
| 220 | bool RecordFileWriter::WriteFeatureHeader(size_t feature_count) { |
| 221 | feature_count_ = feature_count; |
| 222 | current_feature_index_ = 0; |
| 223 | uint64_t feature_header_size = feature_count * sizeof(SectionDesc); |
| 224 | |
| 225 | // Reserve enough space in the record file for the feature header. |
| 226 | std::vector<unsigned char> zero_data(feature_header_size); |
| 227 | if (fseek(record_fp_, data_section_offset_ + data_section_size_, SEEK_SET) == -1) { |
| 228 | PLOG(ERROR) << "fseek() failed"; |
| 229 | return false; |
| 230 | } |
| 231 | return Write(zero_data.data(), zero_data.size()); |
| 232 | } |
| 233 | |
| 234 | bool RecordFileWriter::WriteBuildIdFeature(const std::vector<BuildIdRecord>& build_id_records) { |
| 235 | if (current_feature_index_ >= feature_count_) { |
| 236 | return false; |
| 237 | } |
| 238 | // Always write features at the end of the file. |
| 239 | if (fseek(record_fp_, 0, SEEK_END) == -1) { |
| 240 | PLOG(ERROR) << "fseek() failed"; |
| 241 | return false; |
| 242 | } |
| 243 | long section_start = ftell(record_fp_); |
| 244 | if (section_start == -1) { |
| 245 | PLOG(ERROR) << "ftell() failed"; |
| 246 | return false; |
| 247 | } |
| 248 | for (auto& record : build_id_records) { |
| 249 | std::vector<char> data = record.BinaryFormat(); |
| 250 | if (!Write(data.data(), data.size())) { |
| 251 | return false; |
| 252 | } |
| 253 | } |
| 254 | long section_end = ftell(record_fp_); |
| 255 | if (section_end == -1) { |
| 256 | return false; |
| 257 | } |
| 258 | |
| 259 | // Write feature section descriptor for build_id feature. |
| 260 | SectionDesc desc; |
| 261 | desc.offset = section_start; |
| 262 | desc.size = section_end - section_start; |
| 263 | uint64_t feature_offset = data_section_offset_ + data_section_size_; |
| 264 | if (fseek(record_fp_, feature_offset + current_feature_index_ * sizeof(SectionDesc), SEEK_SET) == |
| 265 | -1) { |
| 266 | PLOG(ERROR) << "fseek() failed"; |
| 267 | return false; |
| 268 | } |
| 269 | if (fwrite(&desc, sizeof(SectionDesc), 1, record_fp_) != 1) { |
| 270 | PLOG(ERROR) << "fwrite() failed"; |
| 271 | return false; |
| 272 | } |
| 273 | ++current_feature_index_; |
| 274 | features_.push_back(FEAT_BUILD_ID); |
| 275 | return true; |
| 276 | } |
| 277 | |
Yabin Cui | ed91cd9 | 2015-04-28 15:54:13 -0700 | [diff] [blame] | 278 | bool RecordFileWriter::WriteFileHeader() { |
| 279 | FileHeader header; |
| 280 | memset(&header, 0, sizeof(header)); |
| 281 | memcpy(header.magic, PERF_MAGIC, sizeof(header.magic)); |
| 282 | header.header_size = sizeof(header); |
| 283 | header.attr_size = sizeof(FileAttr); |
| 284 | header.attrs.offset = attr_section_offset_; |
| 285 | header.attrs.size = attr_section_size_; |
| 286 | header.data.offset = data_section_offset_; |
| 287 | header.data.size = data_section_size_; |
| 288 | for (auto& feature : features_) { |
| 289 | int i = feature / 8; |
| 290 | int j = feature % 8; |
| 291 | header.features[i] |= (1 << j); |
| 292 | } |
| 293 | |
| 294 | if (fseek(record_fp_, 0, SEEK_SET) == -1) { |
| 295 | return false; |
| 296 | } |
| 297 | if (!Write(&header, sizeof(header))) { |
| 298 | return false; |
| 299 | } |
| 300 | return true; |
| 301 | } |
| 302 | |
| 303 | bool RecordFileWriter::Close() { |
| 304 | CHECK(record_fp_ != nullptr); |
| 305 | bool result = true; |
| 306 | |
| 307 | // Write file header. We gather enough information to write file header only after |
| 308 | // writing data section and feature section. |
| 309 | if (!WriteFileHeader()) { |
| 310 | result = false; |
| 311 | } |
| 312 | |
| 313 | if (fclose(record_fp_) != 0) { |
| 314 | PLOG(ERROR) << "failed to close record file '" << filename_ << "'"; |
| 315 | result = false; |
| 316 | } |
| 317 | record_fp_ = nullptr; |
| 318 | return result; |
| 319 | } |
| 320 | |
| 321 | std::unique_ptr<RecordFileReader> RecordFileReader::CreateInstance(const std::string& filename) { |
| 322 | int fd = open(filename.c_str(), O_RDONLY | O_CLOEXEC); |
| 323 | if (fd == -1) { |
| 324 | PLOG(ERROR) << "failed to open record file '" << filename << "'"; |
| 325 | return nullptr; |
| 326 | } |
| 327 | auto reader = std::unique_ptr<RecordFileReader>(new RecordFileReader(filename, fd)); |
| 328 | if (!reader->MmapFile()) { |
| 329 | return nullptr; |
| 330 | } |
| 331 | return reader; |
| 332 | } |
| 333 | |
| 334 | RecordFileReader::RecordFileReader(const std::string& filename, int fd) |
| 335 | : filename_(filename), record_fd_(fd), mmap_addr_(nullptr), mmap_len_(0) { |
| 336 | } |
| 337 | |
| 338 | RecordFileReader::~RecordFileReader() { |
| 339 | if (record_fd_ != -1) { |
| 340 | Close(); |
| 341 | } |
| 342 | } |
| 343 | |
| 344 | bool RecordFileReader::Close() { |
| 345 | bool result = true; |
| 346 | if (munmap(const_cast<char*>(mmap_addr_), mmap_len_) == -1) { |
| 347 | PLOG(ERROR) << "failed to munmap() record file '" << filename_ << "'"; |
| 348 | result = false; |
| 349 | } |
| 350 | if (close(record_fd_) == -1) { |
| 351 | PLOG(ERROR) << "failed to close record file '" << filename_ << "'"; |
| 352 | result = false; |
| 353 | } |
| 354 | record_fd_ = -1; |
| 355 | return result; |
| 356 | } |
| 357 | |
| 358 | bool RecordFileReader::MmapFile() { |
| 359 | off64_t file_size = lseek64(record_fd_, 0, SEEK_END); |
| 360 | if (file_size == -1) { |
| 361 | return false; |
| 362 | } |
| 363 | size_t mmap_len = file_size; |
| 364 | void* mmap_addr = mmap(nullptr, mmap_len, PROT_READ, MAP_SHARED, record_fd_, 0); |
| 365 | if (mmap_addr == MAP_FAILED) { |
| 366 | PLOG(ERROR) << "failed to mmap() record file '" << filename_ << "'"; |
| 367 | return false; |
| 368 | } |
| 369 | |
| 370 | mmap_addr_ = reinterpret_cast<const char*>(mmap_addr); |
| 371 | mmap_len_ = mmap_len; |
| 372 | return true; |
| 373 | } |
| 374 | |
| 375 | const FileHeader* RecordFileReader::FileHeader() { |
| 376 | return reinterpret_cast<const struct FileHeader*>(mmap_addr_); |
| 377 | } |
| 378 | |
| 379 | std::vector<const FileAttr*> RecordFileReader::AttrSection() { |
| 380 | std::vector<const FileAttr*> result; |
| 381 | const struct FileHeader* header = FileHeader(); |
| 382 | size_t attr_count = header->attrs.size / header->attr_size; |
| 383 | const FileAttr* attr = reinterpret_cast<const FileAttr*>(mmap_addr_ + header->attrs.offset); |
| 384 | for (size_t i = 0; i < attr_count; ++i) { |
| 385 | result.push_back(attr++); |
| 386 | } |
| 387 | return result; |
| 388 | } |
| 389 | |
| 390 | std::vector<uint64_t> RecordFileReader::IdsForAttr(const FileAttr* attr) { |
| 391 | std::vector<uint64_t> result; |
| 392 | size_t id_count = attr->ids.size / sizeof(uint64_t); |
| 393 | const uint64_t* id = reinterpret_cast<const uint64_t*>(mmap_addr_ + attr->ids.offset); |
| 394 | for (size_t i = 0; i < id_count; ++i) { |
| 395 | result.push_back(*id++); |
| 396 | } |
| 397 | return result; |
| 398 | } |
| 399 | |
| 400 | std::vector<std::unique_ptr<const Record>> RecordFileReader::DataSection() { |
| 401 | std::vector<std::unique_ptr<const Record>> result; |
| 402 | const struct FileHeader* header = FileHeader(); |
| 403 | auto file_attrs = AttrSection(); |
| 404 | CHECK(file_attrs.size() > 0); |
| 405 | perf_event_attr attr = file_attrs[0]->attr; |
| 406 | |
| 407 | const char* end = mmap_addr_ + header->data.offset + header->data.size; |
| 408 | const char* p = mmap_addr_ + header->data.offset; |
| 409 | while (p < end) { |
| 410 | const perf_event_header* header = reinterpret_cast<const perf_event_header*>(p); |
| 411 | if (p + header->size <= end) { |
| 412 | result.push_back(std::move(ReadRecordFromBuffer(attr, header))); |
| 413 | } |
| 414 | p += header->size; |
| 415 | } |
| 416 | return result; |
| 417 | } |
Yabin Cui | 568b82a | 2015-05-05 19:58:07 -0700 | [diff] [blame] | 418 | |
| 419 | std::vector<SectionDesc> RecordFileReader::FeatureSectionDescriptors() { |
| 420 | std::vector<SectionDesc> result; |
| 421 | const struct FileHeader* header = FileHeader(); |
| 422 | size_t feature_count = 0; |
| 423 | for (size_t i = 0; i < sizeof(header->features); ++i) { |
| 424 | for (size_t j = 0; j < 8; ++j) { |
| 425 | if (header->features[i] & (1 << j)) { |
| 426 | ++feature_count; |
| 427 | } |
| 428 | } |
| 429 | } |
| 430 | uint64_t feature_section_offset = header->data.offset + header->data.size; |
| 431 | const SectionDesc* p = reinterpret_cast<const SectionDesc*>(mmap_addr_ + feature_section_offset); |
| 432 | for (size_t i = 0; i < feature_count; ++i) { |
| 433 | result.push_back(*p++); |
| 434 | } |
| 435 | return result; |
| 436 | } |