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.Log;
25 import android.util.StatsEvent;
26 
27 import com.android.car.CarStatsLog;
28 import com.android.car.stats.VmsClientLogger.ConnectionState;
29 import com.android.internal.annotations.GuardedBy;
30 import com.android.internal.util.ConcurrentUtils;
31 
32 import java.io.FileDescriptor;
33 import java.io.PrintWriter;
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 = "CarStatsService";
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                             Log.d(TAG, "Created VmsClientLog: " + packageName);
121                         }
122                         return new VmsClientLogger(uid, packageName);
123                     });
124         }
125     }
126 
dump(FileDescriptor fd, PrintWriter writer, String[] args)127     public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
128         List<String> flags = Arrays.asList(args);
129         if (args.length == 0 || flags.contains("--vms-client")) {
130             dumpVmsStats(writer);
131         }
132     }
133 
dumpVmsStats(PrintWriter writer)134     private void dumpVmsStats(PrintWriter writer) {
135         synchronized (mVmsClientStats) {
136             writer.println(VMS_CONNECTION_STATS_DUMPSYS_HEADER);
137             mVmsClientStats.values().stream()
138                     // Unknown UID will not have connection stats
139                     .filter(entry -> entry.getUid() > 0)
140                     // Sort stats by UID
141                     .sorted(Comparator.comparingInt(VmsClientLogger::getUid))
142                     .forEachOrdered(entry -> writer.println(
143                             VMS_CONNECTION_STATS_DUMPSYS_FORMAT.apply(entry)));
144             writer.println();
145 
146             writer.println(VMS_CLIENT_STATS_DUMPSYS_HEADER);
147             dumpVmsClientStats(entry -> writer.println(
148                     VMS_CLIENT_STATS_DUMPSYS_FORMAT.apply(entry)));
149         }
150     }
151 
pullVmsClientStats(int atomTag, List<StatsEvent> pulledData)152     private int pullVmsClientStats(int atomTag, List<StatsEvent> pulledData) {
153         if (atomTag != CarStatsLog.VMS_CLIENT_STATS) {
154             Log.w(TAG, "Unexpected atom tag: " + atomTag);
155             return StatsManager.PULL_SKIP;
156         }
157 
158         dumpVmsClientStats((entry) -> {
159             StatsEvent e = StatsEvent.newBuilder()
160                     .setAtomId(atomTag)
161                     .writeInt(entry.getUid())
162                     .addBooleanAnnotation(CarStatsLog.ANNOTATION_ID_IS_UID, true)
163 
164                     .writeInt(entry.getLayerType())
165                     .writeInt(entry.getLayerChannel())
166                     .writeInt(entry.getLayerVersion())
167 
168                     .writeLong(entry.getTxBytes())
169                     .writeLong(entry.getTxPackets())
170                     .writeLong(entry.getRxBytes())
171                     .writeLong(entry.getRxPackets())
172                     .writeLong(entry.getDroppedBytes())
173                     .writeLong(entry.getDroppedPackets())
174                     .build();
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