Home | History | Annotate | Download | only in wifi
      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