1 /* 2 * Copyright (C) 2010 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 package com.android.server.wifi; 18 19 import android.content.Context; 20 import android.util.Base64; 21 import android.util.Log; 22 23 import com.android.internal.annotations.VisibleForTesting; 24 import com.android.internal.R; 25 import com.android.server.wifi.util.ByteArrayRingBuffer; 26 import com.android.server.wifi.util.StringUtil; 27 28 import java.io.BufferedReader; 29 import java.io.ByteArrayOutputStream; 30 import java.io.FileDescriptor; 31 import java.io.IOException; 32 import java.io.InputStreamReader; 33 import java.io.PrintWriter; 34 import java.lang.StringBuilder; 35 import java.nio.charset.Charset; 36 import java.util.ArrayList; 37 import java.util.Calendar; 38 import java.util.Collections; 39 import java.util.Comparator; 40 import java.util.HashMap; 41 import java.util.zip.Deflater; 42 43 /** 44 * Tracks various logs for framework. 45 */ 46 class WifiLogger extends BaseWifiLogger { 47 /** 48 * Thread-safety: 49 * 1) All non-private methods are |synchronized|. 50 * 2) Callbacks into WifiLogger use non-private (and hence, synchronized) methods. See, e.g, 51 * onRingBufferData(), onWifiAlert(). 52 */ 53 54 private static final String TAG = "WifiLogger"; 55 private static final boolean DBG = false; 56 57 /** log level flags; keep these consistent with wifi_logger.h */ 58 59 /** No logs whatsoever */ 60 public static final int VERBOSE_NO_LOG = 0; 61 /** No logs whatsoever */ 62 public static final int VERBOSE_NORMAL_LOG = 1; 63 /** Be careful since this one can affect performance and power */ 64 public static final int VERBOSE_LOG_WITH_WAKEUP = 2; 65 /** Be careful since this one can affect performance and power and memory */ 66 public static final int VERBOSE_DETAILED_LOG_WITH_WAKEUP = 3; 67 68 /** ring buffer flags; keep these consistent with wifi_logger.h */ 69 public static final int RING_BUFFER_FLAG_HAS_BINARY_ENTRIES = 0x00000001; 70 public static final int RING_BUFFER_FLAG_HAS_ASCII_ENTRIES = 0x00000002; 71 public static final int RING_BUFFER_FLAG_HAS_PER_PACKET_ENTRIES = 0x00000004; 72 73 /** various reason codes */ 74 public static final int REPORT_REASON_NONE = 0; 75 public static final int REPORT_REASON_ASSOC_FAILURE = 1; 76 public static final int REPORT_REASON_AUTH_FAILURE = 2; 77 public static final int REPORT_REASON_AUTOROAM_FAILURE = 3; 78 public static final int REPORT_REASON_DHCP_FAILURE = 4; 79 public static final int REPORT_REASON_UNEXPECTED_DISCONNECT = 5; 80 public static final int REPORT_REASON_SCAN_FAILURE = 6; 81 public static final int REPORT_REASON_USER_ACTION = 7; 82 83 /** number of bug reports to hold */ 84 public static final int MAX_BUG_REPORTS = 4; 85 86 /** number of alerts to hold */ 87 public static final int MAX_ALERT_REPORTS = 1; 88 89 /** minimum wakeup interval for each of the log levels */ 90 private static final int MinWakeupIntervals[] = new int[] { 0, 3600, 60, 10 }; 91 /** minimum buffer size for each of the log levels */ 92 private static final int MinBufferSizes[] = new int[] { 0, 16384, 16384, 65536 }; 93 94 @VisibleForTesting public static final String FIRMWARE_DUMP_SECTION_HEADER = 95 "FW Memory dump"; 96 @VisibleForTesting public static final String DRIVER_DUMP_SECTION_HEADER = 97 "Driver state dump"; 98 99 private final int RING_BUFFER_BYTE_LIMIT_SMALL; 100 private final int RING_BUFFER_BYTE_LIMIT_LARGE; 101 private int mLogLevel = VERBOSE_NO_LOG; 102 private boolean mIsLoggingEventHandlerRegistered; 103 private WifiNative.RingBufferStatus[] mRingBuffers; 104 private WifiNative.RingBufferStatus mPerPacketRingBuffer; 105 private WifiStateMachine mWifiStateMachine; 106 private final WifiNative mWifiNative; 107 private final BuildProperties mBuildProperties; 108 private int mMaxRingBufferSizeBytes; 109 110 public WifiLogger(Context context, WifiStateMachine wifiStateMachine, WifiNative wifiNative, 111 BuildProperties buildProperties) { 112 RING_BUFFER_BYTE_LIMIT_SMALL = context.getResources().getInteger( 113 R.integer.config_wifi_logger_ring_buffer_default_size_limit_kb) * 1024; 114 RING_BUFFER_BYTE_LIMIT_LARGE = context.getResources().getInteger( 115 R.integer.config_wifi_logger_ring_buffer_verbose_size_limit_kb) * 1024; 116 117 mWifiStateMachine = wifiStateMachine; 118 mWifiNative = wifiNative; 119 mBuildProperties = buildProperties; 120 mIsLoggingEventHandlerRegistered = false; 121 mMaxRingBufferSizeBytes = RING_BUFFER_BYTE_LIMIT_SMALL; 122 } 123 124 @Override 125 public synchronized void startLogging(boolean verboseEnabled) { 126 mFirmwareVersion = mWifiNative.getFirmwareVersion(); 127 mDriverVersion = mWifiNative.getDriverVersion(); 128 mSupportedFeatureSet = mWifiNative.getSupportedLoggerFeatureSet(); 129 130 if (!mIsLoggingEventHandlerRegistered) { 131 mIsLoggingEventHandlerRegistered = mWifiNative.setLoggingEventHandler(mHandler); 132 } 133 134 if (verboseEnabled) { 135 mLogLevel = VERBOSE_LOG_WITH_WAKEUP; 136 mMaxRingBufferSizeBytes = RING_BUFFER_BYTE_LIMIT_LARGE; 137 } else { 138 mLogLevel = VERBOSE_NORMAL_LOG; 139 mMaxRingBufferSizeBytes = enableVerboseLoggingForDogfood() 140 ? RING_BUFFER_BYTE_LIMIT_LARGE : RING_BUFFER_BYTE_LIMIT_SMALL; 141 clearVerboseLogs(); 142 } 143 144 if (mRingBuffers == null) { 145 fetchRingBuffers(); 146 } 147 148 if (mRingBuffers != null) { 149 /* log level may have changed, so restart logging with new levels */ 150 stopLoggingAllBuffers(); 151 resizeRingBuffers(); 152 startLoggingAllExceptPerPacketBuffers(); 153 } 154 155 if (!mWifiNative.startPktFateMonitoring()) { 156 Log.e(TAG, "Failed to start packet fate monitoring"); 157 } 158 } 159 160 @Override 161 public synchronized void startPacketLog() { 162 if (mPerPacketRingBuffer != null) { 163 startLoggingRingBuffer(mPerPacketRingBuffer); 164 } else { 165 if (DBG) Log.d(TAG, "There is no per packet ring buffer"); 166 } 167 } 168 169 @Override 170 public synchronized void stopPacketLog() { 171 if (mPerPacketRingBuffer != null) { 172 stopLoggingRingBuffer(mPerPacketRingBuffer); 173 } else { 174 if (DBG) Log.d(TAG, "There is no per packet ring buffer"); 175 } 176 } 177 178 @Override 179 public synchronized void stopLogging() { 180 if (mIsLoggingEventHandlerRegistered) { 181 if (!mWifiNative.resetLogHandler()) { 182 Log.e(TAG, "Fail to reset log handler"); 183 } else { 184 if (DBG) Log.d(TAG, "Reset log handler"); 185 } 186 // Clear mIsLoggingEventHandlerRegistered even if resetLogHandler() failed, because 187 // the log handler is in an indeterminate state. 188 mIsLoggingEventHandlerRegistered = false; 189 } 190 if (mLogLevel != VERBOSE_NO_LOG) { 191 stopLoggingAllBuffers(); 192 mRingBuffers = null; 193 mLogLevel = VERBOSE_NO_LOG; 194 } 195 } 196 197 @Override 198 synchronized void reportConnectionFailure() { 199 mPacketFatesForLastFailure = fetchPacketFates(); 200 } 201 202 @Override 203 public synchronized void captureBugReportData(int reason) { 204 BugReport report = captureBugreport(reason, isVerboseLoggingEnabled()); 205 mLastBugReports.addLast(report); 206 } 207 208 @Override 209 public synchronized void captureAlertData(int errorCode, byte[] alertData) { 210 BugReport report = captureBugreport(errorCode, isVerboseLoggingEnabled()); 211 report.alertData = alertData; 212 mLastAlerts.addLast(report); 213 } 214 215 @Override 216 public synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 217 super.dump(pw); 218 219 for (int i = 0; i < mLastAlerts.size(); i++) { 220 pw.println("--------------------------------------------------------------------"); 221 pw.println("Alert dump " + i); 222 pw.print(mLastAlerts.get(i)); 223 pw.println("--------------------------------------------------------------------"); 224 } 225 226 for (int i = 0; i < mLastBugReports.size(); i++) { 227 pw.println("--------------------------------------------------------------------"); 228 pw.println("Bug dump " + i); 229 pw.print(mLastBugReports.get(i)); 230 pw.println("--------------------------------------------------------------------"); 231 } 232 233 dumpPacketFates(pw); 234 pw.println("--------------------------------------------------------------------"); 235 236 pw.println("WifiNative - Log Begin ----"); 237 mWifiNative.getLocalLog().dump(fd, pw, args); 238 pw.println("WifiNative - Log End ----"); 239 } 240 241 /* private methods and data */ 242 class BugReport { 243 long systemTimeMs; 244 long kernelTimeNanos; 245 int errorCode; 246 HashMap<String, byte[][]> ringBuffers = new HashMap(); 247 byte[] fwMemoryDump; 248 byte[] mDriverStateDump; 249 byte[] alertData; 250 LimitedCircularArray<String> kernelLogLines; 251 ArrayList<String> logcatLines; 252 253 void clearVerboseLogs() { 254 fwMemoryDump = null; 255 mDriverStateDump = null; 256 } 257 258 public String toString() { 259 StringBuilder builder = new StringBuilder(); 260 261 Calendar c = Calendar.getInstance(); 262 c.setTimeInMillis(systemTimeMs); 263 builder.append("system time = ").append( 264 String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c)).append("\n"); 265 266 long kernelTimeMs = kernelTimeNanos/(1000*1000); 267 builder.append("kernel time = ").append(kernelTimeMs/1000).append(".").append 268 (kernelTimeMs%1000).append("\n"); 269 270 if (alertData == null) 271 builder.append("reason = ").append(errorCode).append("\n"); 272 else { 273 builder.append("errorCode = ").append(errorCode); 274 builder.append("data \n"); 275 builder.append(compressToBase64(alertData)).append("\n"); 276 } 277 278 if (kernelLogLines != null) { 279 builder.append("kernel log: \n"); 280 for (int i = 0; i < kernelLogLines.size(); i++) { 281 builder.append(kernelLogLines.get(i)).append("\n"); 282 } 283 builder.append("\n"); 284 } 285 286 if (logcatLines != null) { 287 builder.append("system log: \n"); 288 for (int i = 0; i < logcatLines.size(); i++) { 289 builder.append(logcatLines.get(i)).append("\n"); 290 } 291 builder.append("\n"); 292 } 293 294 for (HashMap.Entry<String, byte[][]> e : ringBuffers.entrySet()) { 295 String ringName = e.getKey(); 296 byte[][] buffers = e.getValue(); 297 builder.append("ring-buffer = ").append(ringName).append("\n"); 298 299 int size = 0; 300 for (int i = 0; i < buffers.length; i++) { 301 size += buffers[i].length; 302 } 303 304 byte[] buffer = new byte[size]; 305 int index = 0; 306 for (int i = 0; i < buffers.length; i++) { 307 System.arraycopy(buffers[i], 0, buffer, index, buffers[i].length); 308 index += buffers[i].length; 309 } 310 311 builder.append(compressToBase64(buffer)); 312 builder.append("\n"); 313 } 314 315 if (fwMemoryDump != null) { 316 builder.append(FIRMWARE_DUMP_SECTION_HEADER); 317 builder.append("\n"); 318 builder.append(compressToBase64(fwMemoryDump)); 319 builder.append("\n"); 320 } 321 322 if (mDriverStateDump != null) { 323 builder.append(DRIVER_DUMP_SECTION_HEADER); 324 if (StringUtil.isAsciiPrintable(mDriverStateDump)) { 325 builder.append(" (ascii)\n"); 326 builder.append(new String(mDriverStateDump, Charset.forName("US-ASCII"))); 327 builder.append("\n"); 328 } else { 329 builder.append(" (base64)\n"); 330 builder.append(compressToBase64(mDriverStateDump)); 331 } 332 } 333 334 return builder.toString(); 335 } 336 } 337 338 class LimitedCircularArray<E> { 339 private ArrayList<E> mArrayList; 340 private int mMax; 341 LimitedCircularArray(int max) { 342 mArrayList = new ArrayList<E>(max); 343 mMax = max; 344 } 345 346 public final void addLast(E e) { 347 if (mArrayList.size() >= mMax) 348 mArrayList.remove(0); 349 mArrayList.add(e); 350 } 351 352 public final int size() { 353 return mArrayList.size(); 354 } 355 356 public final E get(int i) { 357 return mArrayList.get(i); 358 } 359 } 360 361 private final LimitedCircularArray<BugReport> mLastAlerts = 362 new LimitedCircularArray<BugReport>(MAX_ALERT_REPORTS); 363 private final LimitedCircularArray<BugReport> mLastBugReports = 364 new LimitedCircularArray<BugReport>(MAX_BUG_REPORTS); 365 private final HashMap<String, ByteArrayRingBuffer> mRingBufferData = new HashMap(); 366 367 private final WifiNative.WifiLoggerEventHandler mHandler = 368 new WifiNative.WifiLoggerEventHandler() { 369 @Override 370 public void onRingBufferData(WifiNative.RingBufferStatus status, byte[] buffer) { 371 WifiLogger.this.onRingBufferData(status, buffer); 372 } 373 374 @Override 375 public void onWifiAlert(int errorCode, byte[] buffer) { 376 WifiLogger.this.onWifiAlert(errorCode, buffer); 377 } 378 }; 379 380 synchronized void onRingBufferData(WifiNative.RingBufferStatus status, byte[] buffer) { 381 ByteArrayRingBuffer ring = mRingBufferData.get(status.name); 382 if (ring != null) { 383 ring.appendBuffer(buffer); 384 } 385 } 386 387 synchronized void onWifiAlert(int errorCode, byte[] buffer) { 388 if (mWifiStateMachine != null) { 389 mWifiStateMachine.sendMessage( 390 WifiStateMachine.CMD_FIRMWARE_ALERT, errorCode, 0, buffer); 391 } 392 } 393 394 private boolean isVerboseLoggingEnabled() { 395 return mLogLevel > VERBOSE_NORMAL_LOG; 396 } 397 398 private void clearVerboseLogs() { 399 mPacketFatesForLastFailure = null; 400 401 for (int i = 0; i < mLastAlerts.size(); i++) { 402 mLastAlerts.get(i).clearVerboseLogs(); 403 } 404 405 for (int i = 0; i < mLastBugReports.size(); i++) { 406 mLastBugReports.get(i).clearVerboseLogs(); 407 } 408 } 409 410 private boolean fetchRingBuffers() { 411 if (mRingBuffers != null) return true; 412 413 mRingBuffers = mWifiNative.getRingBufferStatus(); 414 if (mRingBuffers != null) { 415 for (WifiNative.RingBufferStatus buffer : mRingBuffers) { 416 if (DBG) Log.d(TAG, "RingBufferStatus is: \n" + buffer.name); 417 if (mRingBufferData.containsKey(buffer.name) == false) { 418 mRingBufferData.put(buffer.name, 419 new ByteArrayRingBuffer(mMaxRingBufferSizeBytes)); 420 } 421 if ((buffer.flag & RING_BUFFER_FLAG_HAS_PER_PACKET_ENTRIES) != 0) { 422 mPerPacketRingBuffer = buffer; 423 } 424 } 425 } else { 426 Log.e(TAG, "no ring buffers found"); 427 } 428 429 return mRingBuffers != null; 430 } 431 432 private void resizeRingBuffers() { 433 for (ByteArrayRingBuffer byteArrayRingBuffer : mRingBufferData.values()) { 434 byteArrayRingBuffer.resize(mMaxRingBufferSizeBytes); 435 } 436 } 437 438 private boolean startLoggingAllExceptPerPacketBuffers() { 439 440 if (mRingBuffers == null) { 441 if (DBG) Log.d(TAG, "No ring buffers to log anything!"); 442 return false; 443 } 444 445 for (WifiNative.RingBufferStatus buffer : mRingBuffers){ 446 447 if ((buffer.flag & RING_BUFFER_FLAG_HAS_PER_PACKET_ENTRIES) != 0) { 448 /* skip per-packet-buffer */ 449 if (DBG) Log.d(TAG, "skipped per packet logging ring " + buffer.name); 450 continue; 451 } 452 453 startLoggingRingBuffer(buffer); 454 } 455 456 return true; 457 } 458 459 private boolean startLoggingRingBuffer(WifiNative.RingBufferStatus buffer) { 460 461 int minInterval = MinWakeupIntervals[mLogLevel]; 462 int minDataSize = MinBufferSizes[mLogLevel]; 463 464 if (mWifiNative.startLoggingRingBuffer( 465 mLogLevel, 0, minInterval, minDataSize, buffer.name) == false) { 466 if (DBG) Log.e(TAG, "Could not start logging ring " + buffer.name); 467 return false; 468 } 469 470 return true; 471 } 472 473 private boolean stopLoggingRingBuffer(WifiNative.RingBufferStatus buffer) { 474 if (mWifiNative.startLoggingRingBuffer(0, 0, 0, 0, buffer.name) == false) { 475 if (DBG) Log.e(TAG, "Could not stop logging ring " + buffer.name); 476 } 477 return true; 478 } 479 480 private boolean stopLoggingAllBuffers() { 481 if (mRingBuffers != null) { 482 for (WifiNative.RingBufferStatus buffer : mRingBuffers) { 483 stopLoggingRingBuffer(buffer); 484 } 485 } 486 return true; 487 } 488 489 private boolean getAllRingBufferData() { 490 if (mRingBuffers == null) { 491 Log.e(TAG, "Not ring buffers available to collect data!"); 492 return false; 493 } 494 495 for (WifiNative.RingBufferStatus element : mRingBuffers){ 496 boolean result = mWifiNative.getRingBufferData(element.name); 497 if (!result) { 498 Log.e(TAG, "Fail to get ring buffer data of: " + element.name); 499 return false; 500 } 501 } 502 503 Log.d(TAG, "getAllRingBufferData Successfully!"); 504 return true; 505 } 506 507 private boolean enableVerboseLoggingForDogfood() { 508 return false; 509 } 510 511 private BugReport captureBugreport(int errorCode, boolean captureFWDump) { 512 BugReport report = new BugReport(); 513 report.errorCode = errorCode; 514 report.systemTimeMs = System.currentTimeMillis(); 515 report.kernelTimeNanos = System.nanoTime(); 516 517 if (mRingBuffers != null) { 518 for (WifiNative.RingBufferStatus buffer : mRingBuffers) { 519 /* this will push data in mRingBuffers */ 520 mWifiNative.getRingBufferData(buffer.name); 521 ByteArrayRingBuffer data = mRingBufferData.get(buffer.name); 522 byte[][] buffers = new byte[data.getNumBuffers()][]; 523 for (int i = 0; i < data.getNumBuffers(); i++) { 524 buffers[i] = data.getBuffer(i).clone(); 525 } 526 report.ringBuffers.put(buffer.name, buffers); 527 } 528 } 529 530 report.logcatLines = getLogcat(127); 531 report.kernelLogLines = getKernelLog(127); 532 533 if (captureFWDump) { 534 report.fwMemoryDump = mWifiNative.getFwMemoryDump(); 535 report.mDriverStateDump = mWifiNative.getDriverStateDump(); 536 } 537 return report; 538 } 539 540 @VisibleForTesting 541 LimitedCircularArray<BugReport> getBugReports() { 542 return mLastBugReports; 543 } 544 545 private static String compressToBase64(byte[] input) { 546 String result; 547 //compress 548 Deflater compressor = new Deflater(); 549 compressor.setLevel(Deflater.BEST_SPEED); 550 compressor.setInput(input); 551 compressor.finish(); 552 ByteArrayOutputStream bos = new ByteArrayOutputStream(input.length); 553 final byte[] buf = new byte[1024]; 554 555 while (!compressor.finished()) { 556 int count = compressor.deflate(buf); 557 bos.write(buf, 0, count); 558 } 559 560 try { 561 compressor.end(); 562 bos.close(); 563 } catch (IOException e) { 564 Log.e(TAG, "ByteArrayOutputStream close error"); 565 result = android.util.Base64.encodeToString(input, Base64.DEFAULT); 566 return result; 567 } 568 569 byte[] compressed = bos.toByteArray(); 570 if (DBG) { 571 Log.d(TAG," length is:" + (compressed == null? "0" : compressed.length)); 572 } 573 574 //encode 575 result = android.util.Base64.encodeToString( 576 compressed.length < input.length ? compressed : input , Base64.DEFAULT); 577 578 if (DBG) { 579 Log.d(TAG, "FwMemoryDump length is :" + result.length()); 580 } 581 582 return result; 583 } 584 585 private ArrayList<String> getLogcat(int maxLines) { 586 ArrayList<String> lines = new ArrayList<String>(maxLines); 587 try { 588 Process process = Runtime.getRuntime().exec(String.format("logcat -t %d", maxLines)); 589 BufferedReader reader = new BufferedReader( 590 new InputStreamReader(process.getInputStream())); 591 String line; 592 while ((line = reader.readLine()) != null) { 593 lines.add(line); 594 } 595 reader = new BufferedReader( 596 new InputStreamReader(process.getErrorStream())); 597 while ((line = reader.readLine()) != null) { 598 lines.add(line); 599 } 600 process.waitFor(); 601 } catch (InterruptedException|IOException e) { 602 Log.e(TAG, "Exception while capturing logcat" + e); 603 } 604 return lines; 605 } 606 607 private LimitedCircularArray<String> getKernelLog(int maxLines) { 608 if (DBG) Log.d(TAG, "Reading kernel log ..."); 609 LimitedCircularArray<String> lines = new LimitedCircularArray<String>(maxLines); 610 String log = mWifiNative.readKernelLog(); 611 String logLines[] = log.split("\n"); 612 for (int i = 0; i < logLines.length; i++) { 613 lines.addLast(logLines[i]); 614 } 615 if (DBG) Log.d(TAG, "Added " + logLines.length + " lines"); 616 return lines; 617 } 618 619 /** Packet fate reporting */ 620 private ArrayList<WifiNative.FateReport> mPacketFatesForLastFailure; 621 622 private ArrayList<WifiNative.FateReport> fetchPacketFates() { 623 ArrayList<WifiNative.FateReport> mergedFates = new ArrayList<WifiNative.FateReport>(); 624 WifiNative.TxFateReport[] txFates = 625 new WifiNative.TxFateReport[WifiLoggerHal.MAX_FATE_LOG_LEN]; 626 if (mWifiNative.getTxPktFates(txFates)) { 627 for (int i = 0; i < txFates.length && txFates[i] != null; i++) { 628 mergedFates.add(txFates[i]); 629 } 630 } 631 632 WifiNative.RxFateReport[] rxFates = 633 new WifiNative.RxFateReport[WifiLoggerHal.MAX_FATE_LOG_LEN]; 634 if (mWifiNative.getRxPktFates(rxFates)) { 635 for (int i = 0; i < rxFates.length && rxFates[i] != null; i++) { 636 mergedFates.add(rxFates[i]); 637 } 638 } 639 640 Collections.sort(mergedFates, new Comparator<WifiNative.FateReport>() { 641 @Override 642 public int compare(WifiNative.FateReport lhs, WifiNative.FateReport rhs) { 643 return Long.compare(lhs.mDriverTimestampUSec, rhs.mDriverTimestampUSec); 644 } 645 }); 646 647 return mergedFates; 648 } 649 650 private void dumpPacketFates(PrintWriter pw) { 651 dumpPacketFatesInternal(pw, "Last failed connection fates", mPacketFatesForLastFailure, 652 isVerboseLoggingEnabled()); 653 dumpPacketFatesInternal(pw, "Latest fates", fetchPacketFates(), isVerboseLoggingEnabled()); 654 } 655 656 private static void dumpPacketFatesInternal(PrintWriter pw, String description, 657 ArrayList<WifiNative.FateReport> fates, boolean verbose) { 658 if (fates == null) { 659 pw.format("No fates fetched for \"%s\"\n", description); 660 return; 661 } 662 663 if (fates.size() == 0) { 664 pw.format("HAL provided zero fates for \"%s\"\n", description); 665 return; 666 } 667 668 pw.format("--------------------- %s ----------------------\n", description); 669 670 StringBuilder verboseOutput = new StringBuilder(); 671 pw.print(WifiNative.FateReport.getTableHeader()); 672 for (WifiNative.FateReport fate : fates) { 673 pw.print(fate.toTableRowString()); 674 if (verbose) { 675 // Important: only print Personally Identifiable Information (PII) if verbose 676 // logging is turned on. 677 verboseOutput.append(fate.toVerboseStringWithPiiAllowed()); 678 verboseOutput.append("\n"); 679 } 680 } 681 682 if (verbose) { 683 pw.format("\n>>> VERBOSE PACKET FATE DUMP <<<\n\n"); 684 pw.print(verboseOutput.toString()); 685 } 686 687 pw.println("--------------------------------------------------------------------"); 688 } 689 } 690