1 /* 2 * Copyright (C) 2022 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.car.telemetry.publisher; 18 19 import android.annotation.NonNull; 20 import android.app.ActivityManager; 21 import android.car.builtin.os.TraceHelper; 22 import android.car.builtin.util.Slogf; 23 import android.car.builtin.util.TimingsTraceLog; 24 import android.car.telemetry.TelemetryProto; 25 import android.car.telemetry.TelemetryProto.Publisher.PublisherCase; 26 import android.content.Context; 27 import android.os.Debug; 28 import android.os.Handler; 29 import android.os.PersistableBundle; 30 import android.provider.Settings; 31 32 import com.android.car.CarLog; 33 import com.android.car.telemetry.CarTelemetryService; 34 import com.android.car.telemetry.ResultStore; 35 import com.android.car.telemetry.databroker.DataSubscriber; 36 import com.android.car.telemetry.sessioncontroller.SessionAnnotation; 37 import com.android.car.telemetry.sessioncontroller.SessionController; 38 import com.android.internal.annotations.VisibleForTesting; 39 import com.android.internal.util.Preconditions; 40 41 import java.io.IOException; 42 import java.nio.file.Files; 43 import java.nio.file.Path; 44 import java.nio.file.Paths; 45 import java.util.ArrayList; 46 import java.util.Arrays; 47 import java.util.HashSet; 48 import java.util.List; 49 import java.util.Map; 50 import java.util.Set; 51 52 /** 53 * Publisher implementation for {@link TelemetryProto.MemoryPublisher}. 54 * 55 * <p>It pulls data from /proc/meminfo periodically and sends the file as String to the Lua script. 56 * 57 * <p>This publisher only allows for one Subscriber at a time. It will pull data until the 58 * MetricsConfig is removed or until the maximum snapshot is reached. 59 * 60 * <p>Failure to read from /proc/meminfo will cause a {@link TelemetryProto.TelemetryError} to be 61 * returned to the client. 62 * 63 * <p>The published data format is: 64 * <code> 65 * - mem.timestamp_millis : long 66 * - mem.meminfo : string (device meminfo) 67 * - package_name:uid:process_name : PersistableBundle (parsed from Debug.MemInfo, listed below) 68 * - mem.summary.total-pss : int 69 * - mem.summary.java-heap : int 70 * - mem.summary.native-heap : int 71 * - mem.summary.stack : int 72 * - mem.summary.system : int 73 * - mem.summary.code : int 74 * - mem.summary.graphics : int 75 * - mem.summary.private-other : int 76 * - mem.summary.total-swap : int 77 * - mem.total_shared_clean : int 78 * - mem.total_shared_dirty : int 79 * - mem.total_private_clean : int 80 * - mem.total_private_dirty : int 81 * - mem.total_swappable_pss : int 82 * </code> 83 */ 84 public class MemoryPublisher extends AbstractPublisher { 85 86 private static final int MILLIS_IN_SECOND = 1000; 87 private static final String ACTIVITY_MANAGER_CONSTANTS = "activity_manager_constants"; 88 @VisibleForTesting 89 static final int THROTTLE_MILLIS = 60 * MILLIS_IN_SECOND; 90 @VisibleForTesting 91 static final String BUNDLE_KEY_NUM_SNAPSHOTS_UNTIL_FINISH = "num_snapshots_left"; 92 @VisibleForTesting 93 static final String BUNDLE_KEY_COLLECT_INDEFINITELY = "collect_indefinitely"; 94 95 private final ActivityManager mActivityManager; 96 private final Context mContext; 97 private final Handler mTelemetryHandler; 98 private final Path mMeminfoPath; 99 private final ResultStore mResultStore; 100 private final Runnable mReadMeminfoRunnable = this::readMemInfo; 101 private final TimingsTraceLog mTraceLog; 102 103 private MemorySubscriberWrapper mSubscriber; 104 private PersistableBundle mPublisherState; 105 private SessionAnnotation mSessionAnnotation; 106 MemoryPublisher( @onNull Context context, @NonNull PublisherListener listener, @NonNull Handler telemetryHandler, @NonNull ResultStore resultStore, @NonNull SessionController sessionController)107 MemoryPublisher( 108 @NonNull Context context, 109 @NonNull PublisherListener listener, 110 @NonNull Handler telemetryHandler, 111 @NonNull ResultStore resultStore, 112 @NonNull SessionController sessionController) { 113 this(context, listener, telemetryHandler, resultStore, sessionController, 114 Paths.get("/proc/meminfo")); 115 } 116 117 @VisibleForTesting MemoryPublisher( @onNull Context context, @NonNull PublisherListener listener, @NonNull Handler telemetryHandler, @NonNull ResultStore resultStore, @NonNull SessionController sessionController, @NonNull Path meminfoPath)118 MemoryPublisher( 119 @NonNull Context context, 120 @NonNull PublisherListener listener, 121 @NonNull Handler telemetryHandler, 122 @NonNull ResultStore resultStore, 123 @NonNull SessionController sessionController, 124 @NonNull Path meminfoPath) { 125 super(listener); 126 mContext = context; 127 mTelemetryHandler = telemetryHandler; 128 mResultStore = resultStore; 129 mMeminfoPath = meminfoPath; 130 mActivityManager = mContext.getSystemService(ActivityManager.class); 131 mPublisherState = mResultStore.getPublisherData( 132 MemoryPublisher.class.getSimpleName(), false); 133 mTraceLog = new TimingsTraceLog( 134 CarLog.TAG_TELEMETRY, TraceHelper.TRACE_TAG_CAR_SERVICE); 135 // Subscribes the publisher to driving session updates by SessionController. 136 sessionController.registerCallback(this::handleSessionStateChange); 137 } 138 139 @Override handleSessionStateChange(@onNull SessionAnnotation annotation)140 protected void handleSessionStateChange(@NonNull SessionAnnotation annotation) { 141 mSessionAnnotation = annotation; 142 } 143 144 @Override addDataSubscriber(@onNull DataSubscriber subscriber)145 public void addDataSubscriber(@NonNull DataSubscriber subscriber) { 146 if (mSubscriber != null) { 147 throw new IllegalStateException("Only one subscriber is allowed for MemoryPublisher."); 148 } 149 Preconditions.checkArgument( 150 subscriber.getPublisherParam().getPublisherCase() == PublisherCase.MEMORY, 151 "Only subscribers for memory statistics are supported by this class."); 152 // the minimum allowed read_rate is 1, i.e. one snapshot per second 153 if (subscriber.getPublisherParam().getMemory().getReadIntervalSec() <= 0) { 154 throw new IllegalArgumentException("MemoryPublisher read_rate must be at least 1"); 155 } 156 if (subscriber.getPublisherParam().getMemory().getMaxPendingTasks() <= 0) { 157 throw new IllegalArgumentException("max_pending_tasks in MemoryPublisher must be set" 158 + " as a throttling threshold"); 159 } 160 // if the subscriber is new, i.e. it is added from CarTelemetryManager#addMetricsConifg(), 161 // then the protobuf max_snapshots field is the number of snapshots left 162 int numSnapshotsLeft = subscriber.getPublisherParam().getMemory().getMaxSnapshots(); 163 // if client does not specify max_snapshots, the publisher will publish until the 164 // MetricsConfig's lifecycle ends via Lua callback or when the MetricsConfig is removed 165 boolean collectIndefinitely = numSnapshotsLeft <= 0; 166 // if the subscriber is not new, i.e. it is loaded from disk, then the number of snapshots 167 // left is whatever value from last time, which is stored in the publisher state 168 if (mPublisherState != null) { 169 numSnapshotsLeft = mPublisherState.getInt( 170 BUNDLE_KEY_NUM_SNAPSHOTS_UNTIL_FINISH, numSnapshotsLeft); 171 collectIndefinitely = mPublisherState.getBoolean( 172 BUNDLE_KEY_COLLECT_INDEFINITELY, numSnapshotsLeft <= 0); 173 } 174 mSubscriber = new MemorySubscriberWrapper( 175 subscriber, numSnapshotsLeft, collectIndefinitely); 176 readMemInfo(); 177 } 178 179 @Override removeDataSubscriber(@onNull DataSubscriber subscriber)180 public void removeDataSubscriber(@NonNull DataSubscriber subscriber) { 181 if (mSubscriber == null || !mSubscriber.mDataSubscriber.equals(subscriber)) { 182 return; 183 } 184 resetPublisher(); 185 } 186 187 @Override removeAllDataSubscribers()188 public void removeAllDataSubscribers() { 189 resetPublisher(); 190 } 191 192 @Override hasDataSubscriber(@onNull DataSubscriber subscriber)193 public boolean hasDataSubscriber(@NonNull DataSubscriber subscriber) { 194 return mSubscriber != null && mSubscriber.mDataSubscriber.equals(subscriber); 195 } 196 resetPublisher()197 private void resetPublisher() { 198 mTelemetryHandler.removeCallbacks(mReadMeminfoRunnable); 199 mSubscriber = null; 200 mPublisherState = null; 201 mResultStore.removePublisherData(MemoryPublisher.class.getSimpleName()); 202 } 203 readMemInfo()204 private void readMemInfo() { 205 if (mSubscriber == null) { 206 return; 207 } 208 if (mSubscriber.isDone()) { 209 // terminate the MetricsConfig 210 onConfigFinished(mSubscriber.mMetricsConfig); 211 resetPublisher(); 212 return; 213 } 214 mTraceLog.traceBegin("Reading meminfo and publishing"); 215 // Read timestamp and meminfo and create published data 216 PersistableBundle data = new PersistableBundle(); 217 data.putLong(Constants.MEMORY_BUNDLE_KEY_TIMESTAMP, System.currentTimeMillis()); 218 String meminfo; 219 try { 220 meminfo = new String(Files.readAllBytes(mMeminfoPath)); 221 } catch (IOException e) { 222 // Return failure to client as error 223 onPublisherFailure(Arrays.asList(mSubscriber.mMetricsConfig), e); 224 resetPublisher(); 225 mTraceLog.traceEnd(); 226 return; 227 } 228 data.putString(Constants.MEMORY_BUNDLE_KEY_MEMINFO, meminfo); 229 230 if (!mSubscriber.mPackageNames.isEmpty()) { 231 readProcessMeminfo(data); 232 } 233 234 // add sessions info to published data if available 235 if (mSessionAnnotation != null) { 236 mSessionAnnotation.addAnnotationsToBundle(data); 237 } 238 // publish data, enqueue data for script execution 239 int numPendingTasks = mSubscriber.push(data); 240 241 // update publisher state 242 if (mPublisherState == null) { 243 mPublisherState = new PersistableBundle(); 244 } 245 mPublisherState.putBoolean( 246 BUNDLE_KEY_COLLECT_INDEFINITELY, 247 mSubscriber.mCollectIndefinitely); 248 mPublisherState.putInt( 249 BUNDLE_KEY_NUM_SNAPSHOTS_UNTIL_FINISH, 250 mSubscriber.mNumSnapshotsLeft); 251 mResultStore.putPublisherData(MemoryPublisher.class.getSimpleName(), mPublisherState); 252 253 // if there are too many pending tasks from this publisher, throttle this publisher 254 // by reducing the publishing frequency 255 int delayMillis = numPendingTasks < mSubscriber.mMaxPendingTasks 256 ? mSubscriber.mPublisherProto.getReadIntervalSec() * MILLIS_IN_SECOND 257 : THROTTLE_MILLIS; 258 // schedule the next Runnable to read meminfo 259 mTelemetryHandler.postDelayed(mReadMeminfoRunnable, delayMillis); 260 mTraceLog.traceEnd(); 261 } 262 263 /** 264 * Helper method to get process meminfo statistics from the declared packages in 265 * {@code mSubscriber}. Each process meminfo is a PersistableBundle and it will be put into the 266 * parameter {@code data}; 267 */ 268 private void readProcessMeminfo(PersistableBundle data) { 269 mTraceLog.traceBegin("reading process meminfo"); 270 // update the throttle time for ActivityManager#getProcessMemoryInfo API call 271 String restore = Settings.Global.getString( 272 mContext.getContentResolver(), ACTIVITY_MANAGER_CONSTANTS); 273 Settings.Global.putString( 274 mContext.getContentResolver(), 275 ACTIVITY_MANAGER_CONSTANTS, 276 "memory_info_throttle_time=1"); 277 // find PIDs from package names because the API accept an array of PIDs 278 List<ActivityManager.RunningAppProcessInfo> runningAppProcesses = 279 mActivityManager.getRunningAppProcesses(); 280 // the 2 lists run in parallel, where pidList.get(i) is the pid of the pkgList.get(i) 281 List<Integer> pidList = new ArrayList<>(); 282 List<String> bundleKeys = new ArrayList<>(); 283 // find PIDs from package names specified by the MetricsConfig 284 for (ActivityManager.RunningAppProcessInfo process : runningAppProcesses) { 285 // Default apps use package name as process name, some apps will have the format 286 // package_name:process_name, so we extract package name and process name with split 287 String[] split = process.processName.split(":"); 288 if (mSubscriber.mPackageNames.contains(split[0])) { 289 if (CarTelemetryService.DEBUG) { 290 Slogf.d(CarLog.TAG_TELEMETRY, 291 "MemoryPublisher found matching process " + process.processName); 292 } 293 pidList.add(process.pid); 294 bundleKeys.add(split[0] + ":" + process.uid + ":" + split[split.length - 1]); 295 } 296 } 297 298 // return early if no matching process found 299 if (pidList.size() == 0) { 300 return; 301 } 302 303 // convert ArrayList<Integer> into int[] 304 int[] pids = new int[pidList.size()]; 305 for (int i = 0; i < pids.length; i++) { 306 pids[i] = pidList.get(i); 307 } 308 // get process meminfo and parse it into a PersistableBundle 309 Debug.MemoryInfo[] mis = mActivityManager.getProcessMemoryInfo(pids); 310 for (int i = 0; i < mis.length; i++) { 311 Debug.MemoryInfo mi = mis[i]; 312 PersistableBundle processMeminfoBundle = new PersistableBundle(); 313 processMeminfoBundle.putInt( 314 Constants.MEMORY_BUNDLE_KEY_TOTAL_SWAPPABLE_PSS, 315 Integer.valueOf(mi.getTotalSwappablePss())); 316 processMeminfoBundle.putInt( 317 Constants.MEMORY_BUNDLE_KEY_TOTAL_PRIVATE_DIRTY, 318 Integer.valueOf(mi.getTotalPrivateDirty())); 319 processMeminfoBundle.putInt( 320 Constants.MEMORY_BUNDLE_KEY_TOTAL_SHARED_DIRTY, 321 Integer.valueOf(mi.getTotalSharedDirty())); 322 processMeminfoBundle.putInt( 323 Constants.MEMORY_BUNDLE_KEY_TOTAL_PRIVATE_CLEAN, 324 Integer.valueOf(mi.getTotalPrivateClean())); 325 processMeminfoBundle.putInt( 326 Constants.MEMORY_BUNDLE_KEY_TOTAL_SHARED_CLEAN, 327 Integer.valueOf(mi.getTotalSharedClean())); 328 Map<String, String> map = mi.getMemoryStats(); 329 for (Map.Entry<String, String> entry : map.entrySet()) { 330 processMeminfoBundle.putInt(Constants.MEMORY_BUNDLE_KEY_PREFIX + entry.getKey(), 331 Integer.valueOf(entry.getValue())); 332 } 333 data.putPersistableBundle(bundleKeys.get(i), processMeminfoBundle); 334 } 335 // restore the settings constant to the original state 336 Settings.Global.putString(mContext.getContentResolver(), 337 ACTIVITY_MANAGER_CONSTANTS, restore); 338 mTraceLog.traceEnd(); 339 } 340 341 private static final class MemorySubscriberWrapper { 342 /** 343 * Whether to keep collecting the meminfo snapshots until end of MetricsConfig lifecycle or 344 * MetricsConfig removed. 345 * This flag should be set to true when the max_snapshots field is unspecified in 346 * {@link TelemetryProto.Publisher}. 347 */ 348 private final boolean mCollectIndefinitely; 349 /** 350 * Maximum number of memory-related pending tasks before throttling this publisher 351 */ 352 private final int mMaxPendingTasks; 353 private final DataSubscriber mDataSubscriber; 354 private final TelemetryProto.MetricsConfig mMetricsConfig; 355 private final TelemetryProto.MemoryPublisher mPublisherProto; 356 private final Set<String> mPackageNames = new HashSet<>(); 357 358 /** 359 * Number of snapshots until the publisher stops collecting data. 360 */ 361 private int mNumSnapshotsLeft; 362 363 private MemorySubscriberWrapper( 364 DataSubscriber dataSubscriber, int numSnapshotsLeft, boolean collectIndefinitely) { 365 mDataSubscriber = dataSubscriber; 366 mNumSnapshotsLeft = numSnapshotsLeft; 367 mCollectIndefinitely = collectIndefinitely; 368 mMetricsConfig = dataSubscriber.getMetricsConfig(); 369 mPublisherProto = dataSubscriber.getPublisherParam().getMemory(); 370 mMaxPendingTasks = mPublisherProto.getMaxPendingTasks(); 371 mPackageNames.addAll(mPublisherProto.getPackageNamesList()); 372 } 373 374 /** Publishes data and returns the number of pending tasks by this publisher. */ 375 private int push(PersistableBundle data) { 376 if (mNumSnapshotsLeft > 0) { 377 mNumSnapshotsLeft--; 378 } 379 boolean isLargeData = 380 data.toString().length() >= DataSubscriber.SCRIPT_INPUT_SIZE_THRESHOLD_BYTES 381 ? true : false; 382 return mDataSubscriber.push(data, isLargeData); 383 } 384 385 private boolean isDone() { 386 if (mCollectIndefinitely) { 387 return false; 388 } 389 return mNumSnapshotsLeft == 0; 390 } 391 } 392 } 393