Eric Tan | ace588c | 2018-09-12 11:44:43 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2018 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 | #define LOG_TAG "NBLog" |
| 18 | //#define LOG_NDEBUG 0 |
| 19 | |
| 20 | #include <memory> |
| 21 | #include <stddef.h> |
| 22 | #include <string> |
| 23 | #include <unordered_set> |
| 24 | |
| 25 | #include <audio_utils/fifo.h> |
| 26 | #include <binder/IMemory.h> |
| 27 | #include <media/nblog/Entry.h> |
| 28 | #include <media/nblog/Events.h> |
| 29 | #include <media/nblog/Reader.h> |
| 30 | #include <media/nblog/Timeline.h> |
| 31 | #include <utils/Log.h> |
| 32 | #include <utils/String8.h> |
| 33 | |
| 34 | namespace android { |
| 35 | namespace NBLog { |
| 36 | |
| 37 | Reader::Reader(const void *shared, size_t size, const std::string &name) |
| 38 | : mName(name), |
| 39 | mShared((/*const*/ Shared *) shared), /*mIMemory*/ |
| 40 | mFifo(mShared != NULL ? |
| 41 | new audio_utils_fifo(size, sizeof(uint8_t), |
| 42 | mShared->mBuffer, mShared->mRear, NULL /*throttlesFront*/) : NULL), |
| 43 | mFifoReader(mFifo != NULL ? new audio_utils_fifo_reader(*mFifo) : NULL) |
| 44 | { |
| 45 | } |
| 46 | |
| 47 | Reader::Reader(const sp<IMemory>& iMemory, size_t size, const std::string &name) |
Ytai Ben-Tsvi | 7dd3972 | 2019-09-05 15:14:30 -0700 | [diff] [blame] | 48 | // TODO: Using unsecurePointer() has some associated security pitfalls |
| 49 | // (see declaration for details). |
| 50 | // Either document why it is safe in this case or address the |
| 51 | // issue (e.g. by copying). |
| 52 | : Reader(iMemory != 0 ? (Shared *) iMemory->unsecurePointer() : NULL, size, |
| 53 | name) |
Eric Tan | ace588c | 2018-09-12 11:44:43 -0700 | [diff] [blame] | 54 | { |
| 55 | mIMemory = iMemory; |
| 56 | } |
| 57 | |
| 58 | Reader::~Reader() |
| 59 | { |
| 60 | delete mFifoReader; |
| 61 | delete mFifo; |
| 62 | } |
| 63 | |
| 64 | // Copies content of a Reader FIFO into its Snapshot |
| 65 | // The Snapshot has the same raw data, but represented as a sequence of entries |
| 66 | // and an EntryIterator making it possible to process the data. |
Eric Tan | bc8281a | 2018-09-20 10:16:14 -0700 | [diff] [blame] | 67 | std::unique_ptr<Snapshot> Reader::getSnapshot(bool flush) |
Eric Tan | ace588c | 2018-09-12 11:44:43 -0700 | [diff] [blame] | 68 | { |
| 69 | if (mFifoReader == NULL) { |
| 70 | return std::unique_ptr<Snapshot>(new Snapshot()); |
| 71 | } |
| 72 | |
| 73 | // This emulates the behaviour of audio_utils_fifo_reader::read, but without incrementing the |
| 74 | // reader index. The index is incremented after handling corruption, to after the last complete |
| 75 | // entry of the buffer |
| 76 | size_t lost = 0; |
| 77 | audio_utils_iovec iovec[2]; |
| 78 | const size_t capacity = mFifo->capacity(); |
| 79 | ssize_t availToRead; |
| 80 | // A call to audio_utils_fifo_reader::obtain() places the read pointer one buffer length |
| 81 | // before the writer's pointer (since mFifoReader was constructed with flush=false). The |
| 82 | // do while loop is an attempt to read all of the FIFO's contents regardless of how behind |
| 83 | // the reader is with respect to the writer. However, the following scheduling sequence is |
| 84 | // possible and can lead to a starvation situation: |
| 85 | // - Writer T1 writes, overrun with respect to Reader T2 |
| 86 | // - T2 calls obtain() and gets EOVERFLOW, T2 ptr placed one buffer size behind T1 ptr |
| 87 | // - T1 write, overrun |
| 88 | // - T2 obtain(), EOVERFLOW (and so on...) |
| 89 | // To address this issue, we limit the number of tries for the reader to catch up with |
| 90 | // the writer. |
| 91 | int tries = 0; |
| 92 | size_t lostTemp; |
| 93 | do { |
| 94 | availToRead = mFifoReader->obtain(iovec, capacity, NULL /*timeout*/, &lostTemp); |
| 95 | lost += lostTemp; |
| 96 | } while (availToRead < 0 || ++tries <= kMaxObtainTries); |
| 97 | |
| 98 | if (availToRead <= 0) { |
| 99 | ALOGW_IF(availToRead < 0, "NBLog Reader %s failed to catch up with Writer", mName.c_str()); |
| 100 | return std::unique_ptr<Snapshot>(new Snapshot()); |
| 101 | } |
| 102 | |
| 103 | // Change to #if 1 for debugging. This statement is useful for checking buffer fullness levels |
| 104 | // (as seen by reader) and how much data was lost. If you find that the fullness level is |
| 105 | // getting close to full, or that data loss is happening to often, then you should |
| 106 | // probably try some of the following: |
| 107 | // - log less data |
| 108 | // - log less often |
| 109 | // - increase the initial shared memory allocation for the buffer |
| 110 | #if 0 |
| 111 | ALOGD("getSnapshot name=%s, availToRead=%zd, capacity=%zu, fullness=%.3f, lost=%zu", |
| 112 | name().c_str(), availToRead, capacity, (double)availToRead / (double)capacity, lost); |
| 113 | #endif |
| 114 | std::unique_ptr<Snapshot> snapshot(new Snapshot(availToRead)); |
| 115 | memcpy(snapshot->mData, (const char *) mFifo->buffer() + iovec[0].mOffset, iovec[0].mLength); |
| 116 | if (iovec[1].mLength > 0) { |
| 117 | memcpy(snapshot->mData + (iovec[0].mLength), |
| 118 | (const char *) mFifo->buffer() + iovec[1].mOffset, iovec[1].mLength); |
| 119 | } |
| 120 | |
| 121 | // Handle corrupted buffer |
| 122 | // Potentially, a buffer has corrupted data on both beginning (due to overflow) and end |
| 123 | // (due to incomplete format entry). But even if the end format entry is incomplete, |
| 124 | // it ends in a complete entry (which is not an FMT_END). So is safe to traverse backwards. |
| 125 | // TODO: handle client corruption (in the middle of a buffer) |
| 126 | |
| 127 | const uint8_t *back = snapshot->mData + availToRead; |
| 128 | const uint8_t *front = snapshot->mData; |
| 129 | |
| 130 | // Find last FMT_END. <back> is sitting on an entry which might be the middle of a FormatEntry. |
| 131 | // We go backwards until we find an EVENT_FMT_END. |
| 132 | const uint8_t *lastEnd = findLastValidEntry(front, back, invalidEndTypes); |
| 133 | if (lastEnd == nullptr) { |
| 134 | snapshot->mEnd = snapshot->mBegin = EntryIterator(front); |
| 135 | } else { |
| 136 | // end of snapshot points to after last FMT_END entry |
| 137 | snapshot->mEnd = EntryIterator(lastEnd).next(); |
| 138 | // find first FMT_START |
| 139 | const uint8_t *firstStart = nullptr; |
| 140 | const uint8_t *firstStartTmp = snapshot->mEnd; |
| 141 | while ((firstStartTmp = findLastValidEntry(front, firstStartTmp, invalidBeginTypes)) |
| 142 | != nullptr) { |
| 143 | firstStart = firstStartTmp; |
| 144 | } |
| 145 | // firstStart is null if no FMT_START entry was found before lastEnd |
| 146 | if (firstStart == nullptr) { |
| 147 | snapshot->mBegin = snapshot->mEnd; |
| 148 | } else { |
| 149 | snapshot->mBegin = EntryIterator(firstStart); |
| 150 | } |
| 151 | } |
| 152 | |
| 153 | // advance fifo reader index to after last entry read. |
Eric Tan | bc8281a | 2018-09-20 10:16:14 -0700 | [diff] [blame] | 154 | if (flush) { |
| 155 | mFifoReader->release(snapshot->mEnd - front); |
| 156 | } |
Eric Tan | ace588c | 2018-09-12 11:44:43 -0700 | [diff] [blame] | 157 | |
| 158 | snapshot->mLost = lost; |
| 159 | return snapshot; |
| 160 | } |
| 161 | |
| 162 | bool Reader::isIMemory(const sp<IMemory>& iMemory) const |
| 163 | { |
Ytai Ben-Tsvi | 7dd3972 | 2019-09-05 15:14:30 -0700 | [diff] [blame] | 164 | return iMemory != 0 && mIMemory != 0 && |
| 165 | iMemory->unsecurePointer() == mIMemory->unsecurePointer(); |
Eric Tan | ace588c | 2018-09-12 11:44:43 -0700 | [diff] [blame] | 166 | } |
| 167 | |
| 168 | // We make a set of the invalid types rather than the valid types when aligning |
| 169 | // Snapshot EntryIterators to valid entries during log corruption checking. |
| 170 | // This is done in order to avoid the maintenance overhead of adding a new Event |
| 171 | // type to the two sets below whenever a new Event type is created, as it is |
| 172 | // very likely that new types added will be valid types. |
| 173 | // Currently, invalidBeginTypes and invalidEndTypes are used to handle the special |
| 174 | // case of a Format Entry, which consists of a variable number of simple log entries. |
| 175 | // If a new Event is added that consists of a variable number of simple log entries, |
| 176 | // then these sets need to be updated. |
| 177 | |
| 178 | // We want the beginning of a Snapshot to point to an entry that is not in |
| 179 | // the middle of a formatted entry and not an FMT_END. |
| 180 | const std::unordered_set<Event> Reader::invalidBeginTypes { |
| 181 | EVENT_FMT_AUTHOR, |
| 182 | EVENT_FMT_END, |
| 183 | EVENT_FMT_FLOAT, |
| 184 | EVENT_FMT_HASH, |
| 185 | EVENT_FMT_INTEGER, |
| 186 | EVENT_FMT_PID, |
| 187 | EVENT_FMT_STRING, |
| 188 | EVENT_FMT_TIMESTAMP, |
| 189 | }; |
| 190 | |
| 191 | // We want the end of a Snapshot to point to an entry that is not in |
| 192 | // the middle of a formatted entry and not a FMT_START. |
| 193 | const std::unordered_set<Event> Reader::invalidEndTypes { |
| 194 | EVENT_FMT_AUTHOR, |
| 195 | EVENT_FMT_FLOAT, |
| 196 | EVENT_FMT_HASH, |
| 197 | EVENT_FMT_INTEGER, |
| 198 | EVENT_FMT_PID, |
| 199 | EVENT_FMT_START, |
| 200 | EVENT_FMT_STRING, |
| 201 | EVENT_FMT_TIMESTAMP, |
| 202 | }; |
| 203 | |
| 204 | const uint8_t *Reader::findLastValidEntry(const uint8_t *front, const uint8_t *back, |
| 205 | const std::unordered_set<Event> &invalidTypes) { |
| 206 | if (front == nullptr || back == nullptr) { |
| 207 | return nullptr; |
| 208 | } |
| 209 | while (back + Entry::kPreviousLengthOffset >= front) { |
| 210 | const uint8_t *prev = back - back[Entry::kPreviousLengthOffset] - Entry::kOverhead; |
| 211 | const Event type = (const Event)prev[offsetof(entry, type)]; |
| 212 | if (prev < front |
| 213 | || prev + prev[offsetof(entry, length)] + Entry::kOverhead != back |
| 214 | || type <= EVENT_RESERVED || type >= EVENT_UPPER_BOUND) { |
| 215 | // prev points to an out of limits or inconsistent entry |
| 216 | return nullptr; |
| 217 | } |
| 218 | // if invalidTypes does not contain the type, then the type is valid. |
| 219 | if (invalidTypes.find(type) == invalidTypes.end()) { |
| 220 | return prev; |
| 221 | } |
| 222 | back = prev; |
| 223 | } |
| 224 | return nullptr; // no entry found |
| 225 | } |
| 226 | |
| 227 | // TODO for future compatibility, would prefer to have a dump() go to string, and then go |
| 228 | // to fd only when invoked through binder. |
| 229 | void DumpReader::dump(int fd, size_t indent) |
| 230 | { |
| 231 | if (fd < 0) return; |
Eric Tan | bc8281a | 2018-09-20 10:16:14 -0700 | [diff] [blame] | 232 | std::unique_ptr<Snapshot> snapshot = getSnapshot(false /*flush*/); |
Eric Tan | ace588c | 2018-09-12 11:44:43 -0700 | [diff] [blame] | 233 | if (snapshot == nullptr) { |
| 234 | return; |
| 235 | } |
| 236 | String8 timestamp, body; |
| 237 | |
| 238 | // TODO all logged types should have a printable format. |
Eric Tan | bc8281a | 2018-09-20 10:16:14 -0700 | [diff] [blame] | 239 | // TODO can we make the printing generic? |
Eric Tan | ace588c | 2018-09-12 11:44:43 -0700 | [diff] [blame] | 240 | for (EntryIterator it = snapshot->begin(); it != snapshot->end(); ++it) { |
| 241 | switch (it->type) { |
| 242 | case EVENT_FMT_START: |
| 243 | it = handleFormat(FormatEntry(it), ×tamp, &body); |
| 244 | break; |
Eric Tan | ace588c | 2018-09-12 11:44:43 -0700 | [diff] [blame] | 245 | case EVENT_LATENCY: { |
| 246 | const double latencyMs = it.payload<double>(); |
Eric Tan | bc8281a | 2018-09-20 10:16:14 -0700 | [diff] [blame] | 247 | body.appendFormat("EVENT_LATENCY,%.3f", latencyMs); |
| 248 | } break; |
| 249 | case EVENT_OVERRUN: { |
| 250 | const int64_t ts = it.payload<int64_t>(); |
| 251 | body.appendFormat("EVENT_OVERRUN,%lld", static_cast<long long>(ts)); |
| 252 | } break; |
| 253 | case EVENT_THREAD_INFO: { |
| 254 | const thread_info_t info = it.payload<thread_info_t>(); |
| 255 | body.appendFormat("EVENT_THREAD_INFO,%d,%s", static_cast<int>(info.id), |
| 256 | threadTypeToString(info.type)); |
| 257 | } break; |
| 258 | case EVENT_UNDERRUN: { |
| 259 | const int64_t ts = it.payload<int64_t>(); |
| 260 | body.appendFormat("EVENT_UNDERRUN,%lld", static_cast<long long>(ts)); |
Eric Tan | ace588c | 2018-09-12 11:44:43 -0700 | [diff] [blame] | 261 | } break; |
| 262 | case EVENT_WARMUP_TIME: { |
| 263 | const double timeMs = it.payload<double>(); |
Eric Tan | bc8281a | 2018-09-20 10:16:14 -0700 | [diff] [blame] | 264 | body.appendFormat("EVENT_WARMUP_TIME,%.3f", timeMs); |
Eric Tan | ace588c | 2018-09-12 11:44:43 -0700 | [diff] [blame] | 265 | } break; |
Eric Tan | bc8281a | 2018-09-20 10:16:14 -0700 | [diff] [blame] | 266 | case EVENT_WORK_TIME: { |
| 267 | const int64_t monotonicNs = it.payload<int64_t>(); |
| 268 | body.appendFormat("EVENT_WORK_TIME,%lld", static_cast<long long>(monotonicNs)); |
| 269 | } break; |
| 270 | case EVENT_THREAD_PARAMS: { |
| 271 | const thread_params_t params = it.payload<thread_params_t>(); |
| 272 | body.appendFormat("EVENT_THREAD_PARAMS,%zu,%u", params.frameCount, params.sampleRate); |
| 273 | } break; |
Eric Tan | ace588c | 2018-09-12 11:44:43 -0700 | [diff] [blame] | 274 | case EVENT_FMT_END: |
| 275 | case EVENT_RESERVED: |
| 276 | case EVENT_UPPER_BOUND: |
| 277 | body.appendFormat("warning: unexpected event %d", it->type); |
Andy Hung | 320fd85 | 2018-10-09 14:06:37 -0700 | [diff] [blame] | 278 | break; |
Eric Tan | ace588c | 2018-09-12 11:44:43 -0700 | [diff] [blame] | 279 | default: |
| 280 | break; |
| 281 | } |
| 282 | if (!body.isEmpty()) { |
| 283 | dprintf(fd, "%.*s%s %s\n", (int)indent, "", timestamp.string(), body.string()); |
| 284 | body.clear(); |
| 285 | } |
| 286 | timestamp.clear(); |
| 287 | } |
| 288 | } |
| 289 | |
| 290 | EntryIterator DumpReader::handleFormat(const FormatEntry &fmtEntry, |
| 291 | String8 *timestamp, String8 *body) |
| 292 | { |
Eric Tan | 9e01951 | 2018-09-21 16:27:00 -0700 | [diff] [blame] | 293 | String8 timestampLocal; |
| 294 | String8 bodyLocal; |
| 295 | if (timestamp == nullptr) { |
| 296 | timestamp = ×tampLocal; |
| 297 | } |
| 298 | if (body == nullptr) { |
| 299 | body = &bodyLocal; |
| 300 | } |
| 301 | |
Eric Tan | ace588c | 2018-09-12 11:44:43 -0700 | [diff] [blame] | 302 | // log timestamp |
| 303 | const int64_t ts = fmtEntry.timestamp(); |
| 304 | timestamp->clear(); |
| 305 | timestamp->appendFormat("[%d.%03d]", (int) (ts / (1000 * 1000 * 1000)), |
| 306 | (int) ((ts / (1000 * 1000)) % 1000)); |
| 307 | |
| 308 | // log unique hash |
| 309 | log_hash_t hash = fmtEntry.hash(); |
| 310 | // print only lower 16bit of hash as hex and line as int to reduce spam in the log |
| 311 | body->appendFormat("%.4X-%d ", (int)(hash >> 16) & 0xFFFF, (int) hash & 0xFFFF); |
| 312 | |
| 313 | // log author (if present) |
| 314 | handleAuthor(fmtEntry, body); |
| 315 | |
| 316 | // log string |
| 317 | EntryIterator arg = fmtEntry.args(); |
| 318 | |
| 319 | const char* fmt = fmtEntry.formatString(); |
| 320 | size_t fmt_length = fmtEntry.formatStringLength(); |
| 321 | |
| 322 | for (size_t fmt_offset = 0; fmt_offset < fmt_length; ++fmt_offset) { |
| 323 | if (fmt[fmt_offset] != '%') { |
| 324 | body->append(&fmt[fmt_offset], 1); // TODO optimize to write consecutive strings at once |
| 325 | continue; |
| 326 | } |
| 327 | // case "%%"" |
| 328 | if (fmt[++fmt_offset] == '%') { |
| 329 | body->append("%"); |
| 330 | continue; |
| 331 | } |
| 332 | // case "%\0" |
| 333 | if (fmt_offset == fmt_length) { |
| 334 | continue; |
| 335 | } |
| 336 | |
| 337 | Event event = (Event) arg->type; |
| 338 | size_t length = arg->length; |
| 339 | |
| 340 | // TODO check length for event type is correct |
| 341 | |
| 342 | if (event == EVENT_FMT_END) { |
| 343 | break; |
| 344 | } |
| 345 | |
| 346 | // TODO: implement more complex formatting such as %.3f |
| 347 | const uint8_t *datum = arg->data; // pointer to the current event args |
| 348 | switch(fmt[fmt_offset]) |
| 349 | { |
| 350 | case 's': // string |
| 351 | ALOGW_IF(event != EVENT_FMT_STRING, |
| 352 | "NBLog Reader incompatible event for string specifier: %d", event); |
| 353 | body->append((const char*) datum, length); |
| 354 | break; |
| 355 | |
| 356 | case 't': // timestamp |
| 357 | ALOGW_IF(event != EVENT_FMT_TIMESTAMP, |
| 358 | "NBLog Reader incompatible event for timestamp specifier: %d", event); |
| 359 | appendTimestamp(body, datum); |
| 360 | break; |
| 361 | |
| 362 | case 'd': // integer |
| 363 | ALOGW_IF(event != EVENT_FMT_INTEGER, |
| 364 | "NBLog Reader incompatible event for integer specifier: %d", event); |
| 365 | appendInt(body, datum); |
| 366 | break; |
| 367 | |
| 368 | case 'f': // float |
| 369 | ALOGW_IF(event != EVENT_FMT_FLOAT, |
| 370 | "NBLog Reader incompatible event for float specifier: %d", event); |
| 371 | appendFloat(body, datum); |
| 372 | break; |
| 373 | |
| 374 | case 'p': // pid |
| 375 | ALOGW_IF(event != EVENT_FMT_PID, |
| 376 | "NBLog Reader incompatible event for pid specifier: %d", event); |
| 377 | appendPID(body, datum, length); |
| 378 | break; |
| 379 | |
| 380 | default: |
| 381 | ALOGW("NBLog Reader encountered unknown character %c", fmt[fmt_offset]); |
| 382 | } |
| 383 | ++arg; |
| 384 | } |
| 385 | ALOGW_IF(arg->type != EVENT_FMT_END, "Expected end of format, got %d", arg->type); |
| 386 | return arg; |
| 387 | } |
| 388 | |
| 389 | void DumpReader::appendInt(String8 *body, const void *data) |
| 390 | { |
| 391 | if (body == nullptr || data == nullptr) { |
| 392 | return; |
| 393 | } |
| 394 | //int x = *((int*) data); |
| 395 | int x; |
| 396 | memcpy(&x, data, sizeof(x)); |
| 397 | body->appendFormat("<%d>", x); |
| 398 | } |
| 399 | |
| 400 | void DumpReader::appendFloat(String8 *body, const void *data) |
| 401 | { |
| 402 | if (body == nullptr || data == nullptr) { |
| 403 | return; |
| 404 | } |
| 405 | float f; |
| 406 | memcpy(&f, data, sizeof(f)); |
| 407 | body->appendFormat("<%f>", f); |
| 408 | } |
| 409 | |
| 410 | void DumpReader::appendPID(String8 *body, const void* data, size_t length) |
| 411 | { |
| 412 | if (body == nullptr || data == nullptr) { |
| 413 | return; |
| 414 | } |
| 415 | pid_t id = *((pid_t*) data); |
| 416 | char * name = &((char*) data)[sizeof(pid_t)]; |
| 417 | body->appendFormat("<PID: %d, name: %.*s>", id, (int) (length - sizeof(pid_t)), name); |
| 418 | } |
| 419 | |
| 420 | void DumpReader::appendTimestamp(String8 *body, const void *data) |
| 421 | { |
| 422 | if (body == nullptr || data == nullptr) { |
| 423 | return; |
| 424 | } |
| 425 | int64_t ts; |
| 426 | memcpy(&ts, data, sizeof(ts)); |
| 427 | body->appendFormat("[%d.%03d]", (int) (ts / (1000 * 1000 * 1000)), |
| 428 | (int) ((ts / (1000 * 1000)) % 1000)); |
| 429 | } |
| 430 | |
| 431 | String8 DumpReader::bufferDump(const uint8_t *buffer, size_t size) |
| 432 | { |
| 433 | String8 str; |
| 434 | if (buffer == nullptr) { |
| 435 | return str; |
| 436 | } |
| 437 | str.append("[ "); |
| 438 | for(size_t i = 0; i < size; i++) { |
| 439 | str.appendFormat("%d ", buffer[i]); |
| 440 | } |
| 441 | str.append("]"); |
| 442 | return str; |
| 443 | } |
| 444 | |
| 445 | String8 DumpReader::bufferDump(const EntryIterator &it) |
| 446 | { |
| 447 | return bufferDump(it, it->length + Entry::kOverhead); |
| 448 | } |
| 449 | |
| 450 | } // namespace NBLog |
| 451 | } // namespace android |