1 /* 2 * Copyright (C) 2019 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.stats; 18 19 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; 20 21 import android.app.StatsManager; 22 import android.app.StatsManager.PullAtomMetadata; 23 import android.car.builtin.util.Slogf; 24 import android.content.Context; 25 import android.content.pm.PackageManager; 26 import android.util.SparseArray; 27 import android.util.StatsEvent; 28 29 import com.android.car.CarLog; 30 import com.android.car.CarStatsLog; 31 import com.android.car.CarSystemService; 32 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 33 import com.android.car.internal.util.ConcurrentUtils; 34 import com.android.car.internal.util.IndentingPrintWriter; 35 import com.android.car.stats.VmsClientLogger.ConnectionState; 36 import com.android.internal.annotations.GuardedBy; 37 38 import java.util.ArrayList; 39 import java.util.Comparator; 40 import java.util.List; 41 import java.util.Locale; 42 import java.util.function.Function; 43 44 /** 45 * Registers pulled atoms with statsd via StatsManager. 46 * 47 * Also implements collection and dumpsys reporting of atoms in CSV format. 48 */ 49 public class CarStatsService implements CarSystemService { 50 private static final boolean DEBUG = false; 51 private static final String TAG = CarLog.tagFor(CarStatsService.class); 52 private static final String VMS_CONNECTION_STATS_DUMPSYS_HEADER = 53 "uid,packageName,attempts,connected,disconnected,terminated,errors"; 54 55 private static final Function<VmsClientLogger, String> VMS_CONNECTION_STATS_DUMPSYS_FORMAT = 56 entry -> String.format(Locale.US, 57 "%d,%s,%d,%d,%d,%d,%d", 58 entry.getUid(), entry.getPackageName(), 59 entry.getConnectionStateCount(ConnectionState.CONNECTING), 60 entry.getConnectionStateCount(ConnectionState.CONNECTED), 61 entry.getConnectionStateCount(ConnectionState.DISCONNECTED), 62 entry.getConnectionStateCount(ConnectionState.TERMINATED), 63 entry.getConnectionStateCount(ConnectionState.CONNECTION_ERROR)); 64 65 private static final String VMS_CLIENT_STATS_DUMPSYS_HEADER = 66 "uid,layerType,layerChannel,layerVersion," 67 + "txBytes,txPackets,rxBytes,rxPackets,droppedBytes,droppedPackets"; 68 69 private static final Function<VmsClientStats, String> VMS_CLIENT_STATS_DUMPSYS_FORMAT = 70 entry -> String.format( 71 "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", 72 entry.getUid(), 73 entry.getLayerType(), entry.getLayerChannel(), entry.getLayerVersion(), 74 entry.getTxBytes(), entry.getTxPackets(), 75 entry.getRxBytes(), entry.getRxPackets(), 76 entry.getDroppedBytes(), entry.getDroppedPackets()); 77 78 private static final Comparator<VmsClientStats> VMS_CLIENT_STATS_ORDER = 79 Comparator.comparingInt(VmsClientStats::getUid) 80 .thenComparingInt(VmsClientStats::getLayerType) 81 .thenComparingInt(VmsClientStats::getLayerChannel) 82 .thenComparingInt(VmsClientStats::getLayerVersion); 83 84 private final Context mContext; 85 private final PackageManager mPackageManager; 86 private final StatsManager mStatsManager; 87 88 @GuardedBy("mVmsClientStats") 89 private final SparseArray<VmsClientLogger> mVmsClientStats = new SparseArray(); 90 CarStatsService(Context context)91 public CarStatsService(Context context) { 92 mContext = context; 93 mPackageManager = context.getPackageManager(); 94 mStatsManager = (StatsManager) mContext.getSystemService(Context.STATS_MANAGER); 95 } 96 97 /** 98 * Registers VmsClientStats puller with StatsManager. 99 */ 100 @Override init()101 public void init() { 102 PullAtomMetadata metadata = new PullAtomMetadata.Builder() 103 .setAdditiveFields(new int[] {5, 6, 7, 8, 9, 10}) 104 .build(); 105 mStatsManager.setPullAtomCallback( 106 CarStatsLog.VMS_CLIENT_STATS, 107 metadata, 108 ConcurrentUtils.DIRECT_EXECUTOR, 109 (atomTag, data) -> pullVmsClientStats(atomTag, data) 110 ); 111 } 112 113 @Override release()114 public void release() { 115 mStatsManager.clearPullAtomCallback(CarStatsLog.VMS_CLIENT_STATS); 116 } 117 118 /** 119 * Gets a logger for the VMS client with a given UID. 120 */ getVmsClientLogger(int clientUid)121 public VmsClientLogger getVmsClientLogger(int clientUid) { 122 synchronized (mVmsClientStats) { 123 if (!mVmsClientStats.contains(clientUid)) { 124 String packageName = mPackageManager.getNameForUid(clientUid); 125 if (DEBUG) { 126 Slogf.d(TAG, "Created VmsClientLog: " + packageName); 127 } 128 mVmsClientStats.put(clientUid, new VmsClientLogger(clientUid, packageName)); 129 } 130 return mVmsClientStats.get(clientUid); 131 } 132 } 133 134 interface DumpVmsClientStats { dump(VmsClientStats vmsClientStats)135 void dump(VmsClientStats vmsClientStats); 136 } 137 138 @Override 139 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dump(IndentingPrintWriter writer)140 public void dump(IndentingPrintWriter writer) { 141 synchronized (mVmsClientStats) { 142 writer.println(VMS_CONNECTION_STATS_DUMPSYS_HEADER); 143 List<VmsClientLogger> loggers = new ArrayList<>(); 144 for (int index = 0; index < mVmsClientStats.size(); index++) { 145 // Unknown UID will not have connection stats 146 if (mVmsClientStats.valueAt(index).getUid() > 0) { 147 loggers.add(mVmsClientStats.valueAt(index)); 148 } 149 } 150 loggers.sort(Comparator.comparingInt(VmsClientLogger::getUid)); 151 for (int index = 0; index < loggers.size(); index++) { 152 writer.println(VMS_CONNECTION_STATS_DUMPSYS_FORMAT.apply(loggers.get(index))); 153 } 154 writer.println(); 155 156 writer.println(VMS_CLIENT_STATS_DUMPSYS_HEADER); 157 dumpVmsClientStats(entry -> writer.println( 158 VMS_CLIENT_STATS_DUMPSYS_FORMAT.apply(entry))); 159 } 160 } 161 pullVmsClientStats(int atomTag, List<StatsEvent> pulledData)162 private int pullVmsClientStats(int atomTag, List<StatsEvent> pulledData) { 163 if (atomTag != CarStatsLog.VMS_CLIENT_STATS) { 164 Slogf.w(TAG, "Unexpected atom tag: " + atomTag); 165 return StatsManager.PULL_SKIP; 166 } 167 168 dumpVmsClientStats((entry) -> { 169 StatsEvent e = CarStatsLog.buildStatsEvent( 170 atomTag, 171 entry.getUid(), 172 entry.getLayerType(), 173 entry.getLayerChannel(), 174 entry.getLayerVersion(), 175 entry.getTxBytes(), 176 entry.getTxPackets(), 177 entry.getRxBytes(), 178 entry.getRxPackets(), 179 entry.getDroppedBytes(), 180 entry.getDroppedPackets() 181 ); 182 pulledData.add(e); 183 }); 184 return StatsManager.PULL_SUCCESS; 185 } 186 dumpVmsClientStats(DumpVmsClientStats dumpFn)187 private void dumpVmsClientStats(DumpVmsClientStats dumpFn) { 188 synchronized (mVmsClientStats) { 189 List<VmsClientStats> loggers = new ArrayList<>(); 190 for (int index = 0; index < mVmsClientStats.size(); index++) { 191 loggers.addAll(mVmsClientStats.valueAt(index).getLayerEntries()); 192 } 193 loggers.sort(VMS_CLIENT_STATS_ORDER); 194 for (int index = 0; index < loggers.size(); index++) { 195 dumpFn.dump(loggers.get(index)); 196 } 197 } 198 } 199 } 200