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