1 /* 2 * Copyright (C) 2024 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 17 package com.android.server.connectivity; 18 19 import static com.android.server.ConnectivityStatsLog.NETWORK_REQUEST_STATE_CHANGED; 20 21 import android.annotation.NonNull; 22 import android.net.NetworkRequest; 23 import android.os.Handler; 24 import android.os.HandlerThread; 25 import android.os.Looper; 26 import android.os.Message; 27 import android.os.SystemClock; 28 import android.util.Log; 29 import android.util.SparseArray; 30 31 import com.android.internal.annotations.VisibleForTesting; 32 import com.android.server.ConnectivityStatsLog; 33 34 import java.util.ArrayDeque; 35 36 /** 37 * A Connectivity Service helper class to push atoms capturing network requests have been received 38 * and removed and its metadata. 39 * 40 * Atom events are logged in the ConnectivityStatsLog. Network request id: network request metadata 41 * hashmap is stored to calculate network request duration when it is removed. 42 * 43 * Note that this class is not thread-safe. The instance of the class needs to be 44 * synchronized in the callers when being used in multiple threads. 45 */ 46 public class NetworkRequestStateStatsMetrics { 47 48 private static final String TAG = "NetworkRequestStateStatsMetrics"; 49 private static final int CMD_SEND_PENDING_NETWORK_REQUEST_STATE_METRIC = 0; 50 private static final int CMD_SEND_MAYBE_ENQUEUE_NETWORK_REQUEST_STATE_METRIC = 1; 51 52 @VisibleForTesting 53 static final int MAX_QUEUED_REQUESTS = 20; 54 55 // Stats logging frequency is limited to 10 ms at least, 500ms are taken as a safely margin 56 // for cases of longer periods of frequent network requests. 57 private static final int ATOM_INTERVAL_MS = 500; 58 private final StatsLoggingHandler mStatsLoggingHandler; 59 60 private final Dependencies mDependencies; 61 62 private final NetworkRequestStateInfo.Dependencies mNRStateInfoDeps; 63 private final SparseArray<NetworkRequestStateInfo> mNetworkRequestsActive; 64 NetworkRequestStateStatsMetrics()65 public NetworkRequestStateStatsMetrics() { 66 this(new Dependencies(), new NetworkRequestStateInfo.Dependencies()); 67 } 68 69 @VisibleForTesting NetworkRequestStateStatsMetrics(Dependencies deps, NetworkRequestStateInfo.Dependencies nrStateInfoDeps)70 NetworkRequestStateStatsMetrics(Dependencies deps, 71 NetworkRequestStateInfo.Dependencies nrStateInfoDeps) { 72 mNetworkRequestsActive = new SparseArray<>(); 73 mDependencies = deps; 74 mNRStateInfoDeps = nrStateInfoDeps; 75 HandlerThread handlerThread = mDependencies.makeHandlerThread(TAG); 76 handlerThread.start(); 77 mStatsLoggingHandler = new StatsLoggingHandler(handlerThread.getLooper()); 78 } 79 80 /** 81 * Register network request receive event, push RECEIVE atom 82 * 83 * @param networkRequest network request received 84 */ onNetworkRequestReceived(NetworkRequest networkRequest)85 public void onNetworkRequestReceived(NetworkRequest networkRequest) { 86 if (mNetworkRequestsActive.contains(networkRequest.requestId)) { 87 Log.w(TAG, "Received already registered network request, id = " 88 + networkRequest.requestId); 89 } else { 90 Log.d(TAG, "Registered nr with ID = " + networkRequest.requestId 91 + ", package_uid = " + networkRequest.networkCapabilities.getRequestorUid()); 92 NetworkRequestStateInfo networkRequestStateInfo = new NetworkRequestStateInfo( 93 networkRequest, mNRStateInfoDeps); 94 mNetworkRequestsActive.put(networkRequest.requestId, networkRequestStateInfo); 95 mStatsLoggingHandler.sendMessage(Message.obtain( 96 mStatsLoggingHandler, 97 CMD_SEND_MAYBE_ENQUEUE_NETWORK_REQUEST_STATE_METRIC, 98 networkRequestStateInfo)); 99 } 100 } 101 102 /** 103 * Register network request remove event, push REMOVE atom 104 * 105 * @param networkRequest network request removed 106 */ onNetworkRequestRemoved(NetworkRequest networkRequest)107 public void onNetworkRequestRemoved(NetworkRequest networkRequest) { 108 NetworkRequestStateInfo networkRequestStateInfo = mNetworkRequestsActive.get( 109 networkRequest.requestId); 110 if (networkRequestStateInfo == null) { 111 Log.w(TAG, "This NR hasn't been registered. NR id = " + networkRequest.requestId); 112 } else { 113 Log.d(TAG, "Removed nr with ID = " + networkRequest.requestId); 114 mNetworkRequestsActive.remove(networkRequest.requestId); 115 networkRequestStateInfo = new NetworkRequestStateInfo(networkRequestStateInfo); 116 networkRequestStateInfo.setNetworkRequestRemoved(); 117 mStatsLoggingHandler.sendMessage(Message.obtain( 118 mStatsLoggingHandler, 119 CMD_SEND_MAYBE_ENQUEUE_NETWORK_REQUEST_STATE_METRIC, 120 networkRequestStateInfo)); 121 } 122 } 123 124 /** Dependency class */ 125 public static class Dependencies { 126 /** 127 * Creates a thread with provided tag. 128 * 129 * @param tag for the thread. 130 */ makeHandlerThread(@onNull final String tag)131 public HandlerThread makeHandlerThread(@NonNull final String tag) { 132 return new HandlerThread(tag); 133 } 134 135 /** 136 * @see Handler#sendMessageDelayed(Message, long) 137 */ sendMessageDelayed(@onNull Handler handler, int what, long delayMillis)138 public void sendMessageDelayed(@NonNull Handler handler, int what, long delayMillis) { 139 handler.sendMessageDelayed(Message.obtain(handler, what), delayMillis); 140 } 141 142 /** 143 * Gets number of millis since event. 144 * 145 * @param eventTimeMillis long timestamp in millis when the event occurred. 146 */ getMillisSinceEvent(long eventTimeMillis)147 public long getMillisSinceEvent(long eventTimeMillis) { 148 return SystemClock.elapsedRealtime() - eventTimeMillis; 149 } 150 151 /** 152 * Writes a NETWORK_REQUEST_STATE_CHANGED event to ConnectivityStatsLog. 153 * 154 * @param networkRequestStateInfo NetworkRequestStateInfo containing network request info. 155 */ writeStats(NetworkRequestStateInfo networkRequestStateInfo)156 public void writeStats(NetworkRequestStateInfo networkRequestStateInfo) { 157 ConnectivityStatsLog.write( 158 NETWORK_REQUEST_STATE_CHANGED, 159 networkRequestStateInfo.getPackageUid(), 160 networkRequestStateInfo.getTransportTypes(), 161 networkRequestStateInfo.getNetCapabilityNotMetered(), 162 networkRequestStateInfo.getNetCapabilityInternet(), 163 networkRequestStateInfo.getNetworkRequestStateStatsType(), 164 networkRequestStateInfo.getNetworkRequestDurationMillis()); 165 } 166 } 167 168 private class StatsLoggingHandler extends Handler { 169 private static final String TAG = "NetworkRequestsStateStatsLoggingHandler"; 170 171 private final ArrayDeque<NetworkRequestStateInfo> mPendingState = new ArrayDeque<>(); 172 173 private long mLastLogTime = 0; 174 StatsLoggingHandler(Looper looper)175 StatsLoggingHandler(Looper looper) { 176 super(looper); 177 } 178 maybeEnqueueStatsMessage(NetworkRequestStateInfo networkRequestStateInfo)179 private void maybeEnqueueStatsMessage(NetworkRequestStateInfo networkRequestStateInfo) { 180 if (mPendingState.size() < MAX_QUEUED_REQUESTS) { 181 mPendingState.add(networkRequestStateInfo); 182 } else { 183 Log.w(TAG, "Too many network requests received within last " + ATOM_INTERVAL_MS 184 + " ms, dropping the last network request (id = " 185 + networkRequestStateInfo.getRequestId() + ") event"); 186 return; 187 } 188 if (hasMessages(CMD_SEND_PENDING_NETWORK_REQUEST_STATE_METRIC)) { 189 return; 190 } 191 long millisSinceLastLog = mDependencies.getMillisSinceEvent(mLastLogTime); 192 193 if (millisSinceLastLog >= ATOM_INTERVAL_MS) { 194 sendMessage( 195 Message.obtain(this, CMD_SEND_PENDING_NETWORK_REQUEST_STATE_METRIC)); 196 } else { 197 mDependencies.sendMessageDelayed( 198 this, 199 CMD_SEND_PENDING_NETWORK_REQUEST_STATE_METRIC, 200 ATOM_INTERVAL_MS - millisSinceLastLog); 201 } 202 } 203 204 @Override handleMessage(Message msg)205 public void handleMessage(Message msg) { 206 NetworkRequestStateInfo loggingInfo; 207 switch (msg.what) { 208 case CMD_SEND_MAYBE_ENQUEUE_NETWORK_REQUEST_STATE_METRIC: 209 maybeEnqueueStatsMessage((NetworkRequestStateInfo) msg.obj); 210 break; 211 case CMD_SEND_PENDING_NETWORK_REQUEST_STATE_METRIC: 212 mLastLogTime = SystemClock.elapsedRealtime(); 213 if (!mPendingState.isEmpty()) { 214 loggingInfo = mPendingState.remove(); 215 mDependencies.writeStats(loggingInfo); 216 if (!mPendingState.isEmpty()) { 217 mDependencies.sendMessageDelayed( 218 this, 219 CMD_SEND_PENDING_NETWORK_REQUEST_STATE_METRIC, 220 ATOM_INTERVAL_MS); 221 } 222 } 223 break; 224 default: // fall out 225 } 226 } 227 } 228 } 229