1/*
2 * Copyright (C) 2017 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.os.FileUtils;
20
21import com.android.internal.annotations.VisibleForTesting;
22
23import libcore.io.IoUtils;
24
25import java.io.FileInputStream;
26import java.io.IOException;
27import java.io.PrintWriter;
28
29/**
30 * Provides a facility for capturing kernel trace events related to Wifi control and data paths.
31 */
32public class LastMileLogger {
33    public LastMileLogger(WifiInjector injector) {
34        this(injector, WIFI_EVENT_BUFFER_PATH, WIFI_EVENT_ENABLE_PATH, WIFI_EVENT_RELEASE_PATH);
35    }
36
37    @VisibleForTesting
38    public LastMileLogger(WifiInjector injector, String bufferPath, String enablePath,
39                          String releasePath) {
40        mLog = injector.makeLog(TAG);
41        mEventBufferPath = bufferPath;
42        mEventEnablePath = enablePath;
43        mEventReleasePath = releasePath;
44    }
45
46    /**
47     * Informs LastMileLogger that a connection event has occurred.
48     * @param connectionId A non-negative connection identifier, or -1 to indicate unknown
49     * @param event an event defined in BaseWifiDiagnostics
50     */
51    public void reportConnectionEvent(long connectionId, byte event) {
52        if (connectionId < 0) {
53            mLog.warn("Ignoring negative connection id: %").c(connectionId);
54            return;
55        }
56
57        switch (event) {
58            case BaseWifiDiagnostics.CONNECTION_EVENT_STARTED:
59                mPendingConnectionId = connectionId;
60                enableTracing();
61                return;
62            case BaseWifiDiagnostics.CONNECTION_EVENT_SUCCEEDED:
63                mPendingConnectionId = -1;
64                disableTracing();
65                return;
66            case BaseWifiDiagnostics.CONNECTION_EVENT_FAILED:
67                if (connectionId >= mPendingConnectionId) {
68                    mPendingConnectionId = -1;
69                    disableTracing();
70                    mLastMileLogForLastFailure = readTrace();
71                    return;
72                } else {
73                    // Spurious failure message. Here's one scenario where this might happen:
74                    // t=00sec      start first connection attempt
75                    // t=30sec      start second connection attempt
76                    // t=60sec      timeout first connection attempt
77                    // We should not stop tracing in this case, since the second connection attempt
78                    // is still in progress.
79                    return;
80                }
81        }
82    }
83
84    /**
85     * Dumps the contents of the log.
86     * @param pw the PrintWriter that will receive the dump
87     */
88    public void dump(PrintWriter pw) {
89        dumpInternal(pw, "Last failed last-mile log", mLastMileLogForLastFailure);
90        dumpInternal(pw, "Latest last-mile log", readTrace());
91        mLastMileLogForLastFailure = null;
92    }
93
94    private static final String TAG = "LastMileLogger";
95    private static final String WIFI_EVENT_BUFFER_PATH =
96            "/sys/kernel/debug/tracing/instances/wifi/trace";
97    private static final String WIFI_EVENT_ENABLE_PATH =
98            "/sys/kernel/debug/tracing/instances/wifi/tracing_on";
99    private static final String WIFI_EVENT_RELEASE_PATH =
100            "/sys/kernel/debug/tracing/instances/wifi/free_buffer";
101
102    private final String mEventBufferPath;
103    private final String mEventEnablePath;
104    private final String mEventReleasePath;
105    private WifiLog mLog;
106    private byte[] mLastMileLogForLastFailure;
107    private FileInputStream mLastMileTraceHandle;
108    private long mPendingConnectionId = -1;
109
110    private void enableTracing() {
111        if (!ensureFailSafeIsArmed()) {
112            mLog.wC("Failed to arm fail-safe.");
113            return;
114        }
115
116        try {
117            FileUtils.stringToFile(mEventEnablePath, "1");
118        } catch (IOException e) {
119            mLog.warn("Failed to start event tracing: %").r(e.getMessage()).flush();
120        }
121    }
122
123    private void disableTracing() {
124        try {
125            FileUtils.stringToFile(mEventEnablePath, "0");
126        } catch (IOException e) {
127            mLog.warn("Failed to stop event tracing: %").r(e.getMessage()).flush();
128        }
129    }
130
131    private byte[] readTrace() {
132        try {
133            return IoUtils.readFileAsByteArray(mEventBufferPath);
134        } catch (IOException e) {
135            mLog.warn("Failed to read event trace: %").r(e.getMessage()).flush();
136            return new byte[0];
137        }
138    }
139
140    private boolean ensureFailSafeIsArmed() {
141        if (mLastMileTraceHandle != null) {
142            return true;
143        }
144
145        try {
146            // This file provides fail-safe behavior for Last-Mile logging. Given that we:
147            // 1. Set the disable_on_free option in the trace_options pseudo-file
148            //    (see wifi-events.rc), and
149            // 2. Hold the WIFI_EVENT_RELEASE_PATH open,
150            //
151            // Then, when this process dies, the kernel will automatically disable any
152            // tracing in the wifi trace instance.
153            //
154            // Note that, despite Studio's suggestion that |mLastMileTraceHandle| could be demoted
155            // to a local variable, we need to stick with a field. Otherwise, the handle could be
156            // garbage collected.
157            mLastMileTraceHandle = new FileInputStream(mEventReleasePath);
158            return true;
159        } catch (IOException e) {
160            mLog.warn("Failed to open free_buffer pseudo-file: %").r(e.getMessage()).flush();
161            return false;
162        }
163    }
164
165    private static void dumpInternal(PrintWriter pw, String description, byte[] lastMileLog) {
166        if (lastMileLog == null || lastMileLog.length < 1) {
167            pw.format("No last mile log for \"%s\"\n", description);
168            return;
169        }
170
171        pw.format("-------------------------- %s ---------------------------\n", description);
172        pw.print(new String(lastMileLog));
173        pw.println("--------------------------------------------------------------------");
174    }
175}
176