/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.wifi; import com.android.internal.app.IBatteryStats; import com.android.internal.util.Protocol; import android.support.v4.util.CircularArray; import android.util.Base64; import android.util.LocalLog; import android.util.Log; import java.io.ByteArrayOutputStream; import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; import java.util.Calendar; import java.util.HashMap; import java.util.zip.Deflater; /** * Tracks various logs for framework */ class WifiLogger { private static final String TAG = "WifiLogger"; private static final boolean DBG = false; /** log level flags; keep these consistent with wifi_logger.h */ /** No logs whatsoever */ public static final int VERBOSE_NO_LOG = 0; /** No logs whatsoever */ public static final int VERBOSE_NORMAL_LOG = 1; /** Be careful since this one can affect performance and power */ public static final int VERBOSE_LOG_WITH_WAKEUP = 2; /** Be careful since this one can affect performance and power and memory */ public static final int VERBOSE_DETAILED_LOG_WITH_WAKEUP = 3; /** ring buffer flags; keep these consistent with wifi_logger.h */ public static final int RING_BUFFER_FLAG_HAS_BINARY_ENTRIES = 0x00000001; public static final int RING_BUFFER_FLAG_HAS_ASCII_ENTRIES = 0x00000002; public static final int RING_BUFFER_FLAG_HAS_PER_PACKET_ENTRIES = 0x00000004; /** various reason codes */ public static final int REPORT_REASON_NONE = 0; public static final int REPORT_REASON_ASSOC_FAILURE = 1; public static final int REPORT_REASON_AUTH_FAILURE = 2; public static final int REPORT_REASON_AUTOROAM_FAILURE = 3; public static final int REPORT_REASON_DHCP_FAILURE = 4; public static final int REPORT_REASON_UNEXPECTED_DISCONNECT = 5; public static final int REPORT_REASON_SCAN_FAILURE = 6; public static final int REPORT_REASON_USER_ACTION = 7; /** number of ring buffer entries to cache */ public static final int MAX_RING_BUFFERS = 10; /** number of bug reports to hold */ public static final int MAX_BUG_REPORTS = 4; /** number of alerts to hold */ public static final int MAX_ALERT_REPORTS = 1; /** minimum wakeup interval for each of the log levels */ private static final int MinWakeupIntervals[] = new int[] { 0, 3600, 60, 10 }; /** minimum buffer size for each of the log levels */ private static final int MinBufferSizes[] = new int[] { 0, 16384, 16384, 65536 }; private int mLogLevel = VERBOSE_NO_LOG; private String mFirmwareVersion; private String mDriverVersion; private int mSupportedFeatureSet; private WifiNative.RingBufferStatus[] mRingBuffers; private WifiNative.RingBufferStatus mPerPacketRingBuffer; private WifiStateMachine mWifiStateMachine; public WifiLogger(WifiStateMachine wifiStateMachine) { mWifiStateMachine = wifiStateMachine; } public synchronized void startLogging(boolean verboseEnabled) { mFirmwareVersion = WifiNative.getFirmwareVersion(); mDriverVersion = WifiNative.getDriverVersion(); mSupportedFeatureSet = WifiNative.getSupportedLoggerFeatureSet(); if (mLogLevel == VERBOSE_NO_LOG) WifiNative.setLoggingEventHandler(mHandler); if (verboseEnabled) { mLogLevel = VERBOSE_LOG_WITH_WAKEUP; } else { mLogLevel = VERBOSE_NORMAL_LOG; } if (mRingBuffers == null) { if (fetchRingBuffers()) { startLoggingAllExceptPerPacketBuffers(); } } } public synchronized void startPacketLog() { if (mPerPacketRingBuffer != null) { startLoggingRingBuffer(mPerPacketRingBuffer); } else { if (DBG) Log.d(TAG, "There is no per packet ring buffer"); } } public synchronized void stopPacketLog() { if (mPerPacketRingBuffer != null) { stopLoggingRingBuffer(mPerPacketRingBuffer); } else { if (DBG) Log.d(TAG, "There is no per packet ring buffer"); } } public synchronized void stopLogging() { if (mLogLevel != VERBOSE_NO_LOG) { //resetLogHandler only can be used when you terminate all logging since all handler will //be removed. This also stop alert logging if(!WifiNative.resetLogHandler()) { Log.e(TAG, "Fail to reset log handler"); } else { if (DBG) Log.d(TAG,"Reset log handler"); } stopLoggingAllBuffers(); mRingBuffers = null; mLogLevel = VERBOSE_NO_LOG; } } public synchronized void captureBugReportData(int reason) { BugReport report = captureBugreport(reason, true); mLastBugReports.addLast(report); } public synchronized void captureAlertData(int errorCode, byte[] alertData) { BugReport report = captureBugreport(errorCode, /* captureFWDump = */ true); report.alertData = alertData; mLastAlerts.addLast(report); } public synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("Chipset information :-----------------------------------------------"); pw.println("FW Version is: " + mFirmwareVersion); pw.println("Driver Version is: " + mDriverVersion); pw.println("Supported Feature set: " + mSupportedFeatureSet); for (int i = 0; i < mLastAlerts.size(); i++) { pw.println("--------------------------------------------------------------------"); pw.println("Alert dump " + i); pw.print(mLastAlerts.get(i)); pw.println("--------------------------------------------------------------------"); } for (int i = 0; i < mLastBugReports.size(); i++) { pw.println("--------------------------------------------------------------------"); pw.println("Bug dump " + i); pw.print(mLastBugReports.get(i)); pw.println("--------------------------------------------------------------------"); } pw.println("--------------------------------------------------------------------"); } /* private methods and data */ private static class BugReport { long systemTimeMs; long kernelTimeNanos; int errorCode; HashMap<String, byte[][]> ringBuffers = new HashMap(); byte[] fwMemoryDump; byte[] alertData; public String toString() { StringBuilder builder = new StringBuilder(); Calendar c = Calendar.getInstance(); c.setTimeInMillis(systemTimeMs); builder.append("system time = ").append( String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c)).append("\n"); long kernelTimeMs = kernelTimeNanos/(1000*1000); builder.append("kernel time = ").append(kernelTimeMs/1000).append(".").append (kernelTimeMs%1000).append("\n"); if (alertData == null) builder.append("reason = ").append(errorCode).append("\n"); else { builder.append("errorCode = ").append(errorCode); builder.append("data \n"); builder.append(compressToBase64(alertData)).append("\n"); } for (HashMap.Entry<String, byte[][]> e : ringBuffers.entrySet()) { String ringName = e.getKey(); byte[][] buffers = e.getValue(); builder.append("ring-buffer = ").append(ringName).append("\n"); int size = 0; for (int i = 0; i < buffers.length; i++) { size += buffers[i].length; } byte[] buffer = new byte[size]; int index = 0; for (int i = 0; i < buffers.length; i++) { System.arraycopy(buffers[i], 0, buffer, index, buffers[i].length); index += buffers[i].length; } builder.append(compressToBase64(buffer)); builder.append("\n"); } if (fwMemoryDump != null) { builder.append("FW Memory dump \n"); builder.append(compressToBase64(fwMemoryDump)); } return builder.toString(); } } static class LimitedCircularArray<E> { private CircularArray<E> mArray; private int mMax; LimitedCircularArray(int max) { mArray = new CircularArray<E>(); mMax = max; } public final void addLast(E e) { if (mArray.size() >= mMax) mArray.popFirst(); mArray.addLast(e); } public final int size() { return mArray.size(); } public final E get(int i) { return mArray.get(i); } } private final LimitedCircularArray<BugReport> mLastAlerts = new LimitedCircularArray<BugReport>(MAX_ALERT_REPORTS); private final LimitedCircularArray<BugReport> mLastBugReports = new LimitedCircularArray<BugReport>(MAX_BUG_REPORTS); private final HashMap<String, LimitedCircularArray<byte[]>> mRingBufferData = new HashMap(); private final WifiNative.WifiLoggerEventHandler mHandler = new WifiNative.WifiLoggerEventHandler() { @Override public void onRingBufferData(WifiNative.RingBufferStatus status, byte[] buffer) { WifiLogger.this.onRingBufferData(status, buffer); } @Override public void onWifiAlert(int errorCode, byte[] buffer) { WifiLogger.this.onWifiAlert(errorCode, buffer); } }; synchronized void onRingBufferData(WifiNative.RingBufferStatus status, byte[] buffer) { LimitedCircularArray<byte[]> ring = mRingBufferData.get(status.name); if (ring != null) { ring.addLast(buffer); } } synchronized void onWifiAlert(int errorCode, byte[] buffer) { if (mWifiStateMachine != null) { mWifiStateMachine.sendMessage( WifiStateMachine.CMD_FIRMWARE_ALERT, errorCode, 0, buffer); } } private boolean fetchRingBuffers() { if (mRingBuffers != null) return true; mRingBuffers = WifiNative.getRingBufferStatus(); if (mRingBuffers != null) { for (WifiNative.RingBufferStatus buffer : mRingBuffers) { if (DBG) Log.d(TAG, "RingBufferStatus is: \n" + buffer.name); if (mRingBufferData.containsKey(buffer.name) == false) { mRingBufferData.put(buffer.name, new LimitedCircularArray<byte[]>(MAX_RING_BUFFERS)); } if ((buffer.flag & RING_BUFFER_FLAG_HAS_PER_PACKET_ENTRIES) != 0) { mPerPacketRingBuffer = buffer; } } } else { Log.e(TAG, "no ring buffers found"); } return mRingBuffers != null; } private boolean startLoggingAllExceptPerPacketBuffers() { if (mRingBuffers == null) { if (DBG) Log.d(TAG, "No ring buffers to log anything!"); return false; } for (WifiNative.RingBufferStatus buffer : mRingBuffers){ if ((buffer.flag & RING_BUFFER_FLAG_HAS_PER_PACKET_ENTRIES) != 0) { /* skip per-packet-buffer */ if (DBG) Log.d(TAG, "skipped per packet logging ring " + buffer.name); continue; } startLoggingRingBuffer(buffer); } return true; } private boolean startLoggingRingBuffer(WifiNative.RingBufferStatus buffer) { int minInterval = MinWakeupIntervals[mLogLevel]; int minDataSize = MinBufferSizes[mLogLevel]; if (WifiNative.startLoggingRingBuffer( mLogLevel, 0, minInterval, minDataSize, buffer.name) == false) { if (DBG) Log.e(TAG, "Could not start logging ring " + buffer.name); return false; } return true; } private boolean stopLoggingRingBuffer(WifiNative.RingBufferStatus buffer) { if (WifiNative.startLoggingRingBuffer(0, 0, 0, 0, buffer.name) == false) { if (DBG) Log.e(TAG, "Could not stop logging ring " + buffer.name); } return true; } private boolean stopLoggingAllBuffers() { if (mRingBuffers != null) { for (WifiNative.RingBufferStatus buffer : mRingBuffers) { stopLoggingRingBuffer(buffer); } } return true; } private boolean getAllRingBufferData() { if (mRingBuffers == null) { Log.e(TAG, "Not ring buffers available to collect data!"); return false; } for (WifiNative.RingBufferStatus element : mRingBuffers){ boolean result = WifiNative.getRingBufferData(element.name); if (!result) { Log.e(TAG, "Fail to get ring buffer data of: " + element.name); return false; } } Log.d(TAG, "getAllRingBufferData Successfully!"); return true; } private BugReport captureBugreport(int errorCode, boolean captureFWDump) { BugReport report = new BugReport(); report.errorCode = errorCode; report.systemTimeMs = System.currentTimeMillis(); report.kernelTimeNanos = System.nanoTime(); if (mRingBuffers != null) { for (WifiNative.RingBufferStatus buffer : mRingBuffers) { /* this will push data in mRingBuffers */ WifiNative.getRingBufferData(buffer.name); LimitedCircularArray<byte[]> data = mRingBufferData.get(buffer.name); byte[][] buffers = new byte[data.size()][]; for (int i = 0; i < data.size(); i++) { buffers[i] = data.get(i).clone(); } report.ringBuffers.put(buffer.name, buffers); } } if (captureFWDump) { report.fwMemoryDump = WifiNative.getFwMemoryDump(); } return report; } private static String compressToBase64(byte[] input) { String result; //compress Deflater compressor = new Deflater(); compressor.setLevel(Deflater.BEST_COMPRESSION); compressor.setInput(input); compressor.finish(); ByteArrayOutputStream bos = new ByteArrayOutputStream(input.length); final byte[] buf = new byte[1024]; while (!compressor.finished()) { int count = compressor.deflate(buf); bos.write(buf, 0, count); } try { compressor.end(); bos.close(); } catch (IOException e) { Log.e(TAG, "ByteArrayOutputStream close error"); result = android.util.Base64.encodeToString(input, Base64.DEFAULT); return result; } byte[] compressed = bos.toByteArray(); if (DBG) { Log.d(TAG," length is:" + (compressed == null? "0" : compressed.length)); } //encode result = android.util.Base64.encodeToString( compressed.length < input.length ? compressed : input , Base64.DEFAULT); if (DBG) { Log.d(TAG, "FwMemoryDump length is :" + result.length()); } return result; } }