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