/* * Copyright (C) 2017 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 android.util.ArrayMap; import com.android.internal.annotations.VisibleForTesting; import com.android.server.wifi.util.FileUtils; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.PrintWriter; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Map; /** * Provides a facility for capturing kernel trace events related to Wifi control and data paths. */ public class LastMileLogger { public LastMileLogger(WifiInjector injector) { File tracefsEnablePath = new File(WIFI_EVENT_ENABLE_PATH); if (tracefsEnablePath.exists()) { initLastMileLogger(injector, WIFI_EVENT_BUFFER_PATH, WIFI_EVENT_ENABLE_PATH, WIFI_EVENT_RELEASE_PATH); } else { initLastMileLogger(injector, WIFI_EVENT_BUFFER_PATH_DEBUGFS, WIFI_EVENT_ENABLE_PATH_DEBUGFS, WIFI_EVENT_RELEASE_PATH_DEBUGFS); } } @VisibleForTesting public LastMileLogger(WifiInjector injector, String bufferPath, String enablePath, String releasePath) { initLastMileLogger(injector, bufferPath, enablePath, releasePath); } /** * Informs LastMileLogger that a connection event has occurred. * @param event an event defined in WifiDiagnostics */ public void reportConnectionEvent(String ifaceName, byte event) { boolean wasTracingEnabled = anyConnectionInProgress(); mIfaceToConnectionStatus.put(ifaceName, event); boolean shouldTracingBeEnabled = anyConnectionInProgress(); if (!wasTracingEnabled && shouldTracingBeEnabled) { enableTracing(); } else if (wasTracingEnabled && !shouldTracingBeEnabled) { disableTracing(); } if (event == WifiDiagnostics.CONNECTION_EVENT_FAILED || event == WifiDiagnostics.CONNECTION_EVENT_TIMEOUT) { mLastMileLogForLastFailure = readTrace(); } } private boolean anyConnectionInProgress() { for (byte status : mIfaceToConnectionStatus.values()) { if (status == WifiDiagnostics.CONNECTION_EVENT_STARTED) { return true; } } return false; } /** * Dumps the contents of the log. * @param pw the PrintWriter that will receive the dump */ public void dump(PrintWriter pw) { dumpInternal(pw, "Last failed last-mile log", mLastMileLogForLastFailure); dumpInternal(pw, "Latest last-mile log", readTrace()); } private static final String TAG = "LastMileLogger"; private static final String WIFI_EVENT_BUFFER_PATH = "/sys/kernel/tracing/instances/wifi/trace"; private static final String WIFI_EVENT_ENABLE_PATH = "/sys/kernel/tracing/instances/wifi/tracing_on"; private static final String WIFI_EVENT_RELEASE_PATH = "/sys/kernel/tracing/instances/wifi/free_buffer"; private static final String WIFI_EVENT_BUFFER_PATH_DEBUGFS = "/sys/kernel/debug/tracing/instances/wifi/trace"; private static final String WIFI_EVENT_ENABLE_PATH_DEBUGFS = "/sys/kernel/debug/tracing/instances/wifi/tracing_on"; private static final String WIFI_EVENT_RELEASE_PATH_DEBUGFS = "/sys/kernel/debug/tracing/instances/wifi/free_buffer"; private String mEventBufferPath; private String mEventEnablePath; private String mEventReleasePath; private WifiLog mLog; private byte[] mLastMileLogForLastFailure; private FileInputStream mLastMileTraceHandle; /** * String key: iface name * byte value: Connection status, one of WifiDiagnostics.CONNECTION_EVENT_* */ private final Map mIfaceToConnectionStatus = new ArrayMap<>(); private void initLastMileLogger(WifiInjector injector, String bufferPath, String enablePath, String releasePath) { mLog = injector.makeLog(TAG); mEventBufferPath = bufferPath; mEventEnablePath = enablePath; mEventReleasePath = releasePath; } private void enableTracing() { if (!ensureFailSafeIsArmed()) { mLog.wC("Failed to arm fail-safe."); return; } try { FileUtils.stringToFile(mEventEnablePath, "1"); } catch (IOException e) { mLog.warn("Failed to start event tracing: %").r(e.getMessage()).flush(); } } private void disableTracing() { try { FileUtils.stringToFile(mEventEnablePath, "0"); } catch (IOException e) { mLog.warn("Failed to stop event tracing: %").r(e.getMessage()).flush(); } } private byte[] readTrace() { try { return Files.readAllBytes(Paths.get(mEventBufferPath)); } catch (IOException e) { mLog.warn("Failed to read event trace: %").r(e.getMessage()).flush(); return new byte[0]; } } private boolean ensureFailSafeIsArmed() { if (mLastMileTraceHandle != null) { return true; } try { // This file provides fail-safe behavior for Last-Mile logging. Given that we: // 1. Set the disable_on_free option in the trace_options pseudo-file // (see wifi-events.rc), and // 2. Hold the WIFI_EVENT_RELEASE_PATH open, // // Then, when this process dies, the kernel will automatically disable any // tracing in the wifi trace instance. // // Note that, despite Studio's suggestion that |mLastMileTraceHandle| could be demoted // to a local variable, we need to stick with a field. Otherwise, the handle could be // garbage collected. mLastMileTraceHandle = new FileInputStream(mEventReleasePath); return true; } catch (IOException e) { mLog.warn("Failed to open free_buffer pseudo-file: %").r(e.getMessage()).flush(); return false; } } private static void dumpInternal(PrintWriter pw, String description, byte[] lastMileLog) { if (lastMileLog == null || lastMileLog.length < 1) { pw.format("No last mile log for \"%s\"\n", description); return; } pw.format("-------------------------- %s ---------------------------\n", description); pw.print(new String(lastMileLog)); pw.println("--------------------------------------------------------------------"); } }