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