blob: 8add77970328865c2d8d4fbe5543b129486b7191 [file] [log] [blame]
/*
* 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;
}
}