| 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 |