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.Manifest;
20 import android.content.Context;
21 import android.content.pm.PackageManager;
22 import android.os.StatsLogEventWrapper;
23 import android.os.SystemClock;
24 import android.util.ArrayMap;
25 import android.util.Log;
26 import android.util.StatsLog;
27 
28 import com.android.car.stats.VmsClientLogger.ConnectionState;
29 import com.android.internal.annotations.GuardedBy;
30 import com.android.internal.car.ICarStatsService;
31 
32 import java.io.FileDescriptor;
33 import java.io.PrintWriter;
34 import java.util.ArrayList;
35 import java.util.Arrays;
36 import java.util.Comparator;
37 import java.util.List;
38 import java.util.Locale;
39 import java.util.Map;
40 import java.util.function.Consumer;
41 import java.util.function.Function;
42 
43 /**
44  * Implementation of {@link ICarStatsService}, for reporting pulled atoms via statsd.
45  *
46  * Also implements collection and dumpsys reporting of atoms in CSV format.
47  */
48 public class CarStatsService extends ICarStatsService.Stub {
49     private static final boolean DEBUG = false;
50     private static final String TAG = "CarStatsService";
51     private static final String VMS_CONNECTION_STATS_DUMPSYS_HEADER =
52             "uid,packageName,attempts,connected,disconnected,terminated,errors";
53 
54     private static final Function<VmsClientLogger, String> VMS_CONNECTION_STATS_DUMPSYS_FORMAT =
55             entry -> String.format(Locale.US,
56                     "%d,%s,%d,%d,%d,%d,%d",
57                     entry.getUid(), entry.getPackageName(),
58                     entry.getConnectionStateCount(ConnectionState.CONNECTING),
59                     entry.getConnectionStateCount(ConnectionState.CONNECTED),
60                     entry.getConnectionStateCount(ConnectionState.DISCONNECTED),
61                     entry.getConnectionStateCount(ConnectionState.TERMINATED),
62                     entry.getConnectionStateCount(ConnectionState.CONNECTION_ERROR));
63 
64     private static final String VMS_CLIENT_STATS_DUMPSYS_HEADER =
65             "uid,layerType,layerChannel,layerVersion,"
66                     + "txBytes,txPackets,rxBytes,rxPackets,droppedBytes,droppedPackets";
67 
68     private static final Function<VmsClientStats, String> VMS_CLIENT_STATS_DUMPSYS_FORMAT =
69             entry -> String.format(
70                     "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",
71                     entry.getUid(),
72                     entry.getLayerType(), entry.getLayerChannel(), entry.getLayerVersion(),
73                     entry.getTxBytes(), entry.getTxPackets(),
74                     entry.getRxBytes(), entry.getRxPackets(),
75                     entry.getDroppedBytes(), entry.getDroppedPackets());
76 
77     private static final Comparator<VmsClientStats> VMS_CLIENT_STATS_ORDER =
78             Comparator.comparingInt(VmsClientStats::getUid)
79                     .thenComparingInt(VmsClientStats::getLayerType)
80                     .thenComparingInt(VmsClientStats::getLayerChannel)
81                     .thenComparingInt(VmsClientStats::getLayerVersion);
82 
83     private final Context mContext;
84     private final PackageManager mPackageManager;
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     }
93 
94     /**
95      * Gets a logger for the VMS client with a given UID.
96      */
getVmsClientLogger(int clientUid)97     public VmsClientLogger getVmsClientLogger(int clientUid) {
98         synchronized (mVmsClientStats) {
99             return mVmsClientStats.computeIfAbsent(
100                     clientUid,
101                     uid -> {
102                         String packageName = mPackageManager.getNameForUid(uid);
103                         if (DEBUG) {
104                             Log.d(TAG, "Created VmsClientLog: " + packageName);
105                         }
106                         return new VmsClientLogger(uid, packageName);
107                     });
108         }
109     }
110 
111     @Override
dump(FileDescriptor fd, PrintWriter writer, String[] args)112     public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
113         List<String> flags = Arrays.asList(args);
114         if (args.length == 0 || flags.contains("--vms-client")) {
115             dumpVmsStats(writer);
116         }
117     }
118 
119     @Override
pullData(int tagId)120     public StatsLogEventWrapper[] pullData(int tagId) {
121         mContext.enforceCallingPermission(Manifest.permission.DUMP, null);
122         if (tagId != StatsLog.VMS_CLIENT_STATS) {
123             Log.w(TAG, "Unexpected tagId: " + tagId);
124             return null;
125         }
126 
127         List<StatsLogEventWrapper> ret = new ArrayList<>();
128         long elapsedNanos = SystemClock.elapsedRealtimeNanos();
129         long wallClockNanos = SystemClock.currentTimeMicro() * 1000L;
130         pullVmsClientStats(tagId, elapsedNanos, wallClockNanos, ret);
131         return ret.toArray(new StatsLogEventWrapper[0]);
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 tagId, long elapsedNanos, long wallClockNanos, List<StatsLogEventWrapper> pulledData)152     private void pullVmsClientStats(int tagId, long elapsedNanos, long wallClockNanos,
153             List<StatsLogEventWrapper> pulledData) {
154         dumpVmsClientStats((entry) -> {
155             StatsLogEventWrapper e =
156                     new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
157             e.writeInt(entry.getUid());
158 
159             e.writeInt(entry.getLayerType());
160             e.writeInt(entry.getLayerChannel());
161             e.writeInt(entry.getLayerVersion());
162 
163             e.writeLong(entry.getTxBytes());
164             e.writeLong(entry.getTxPackets());
165             e.writeLong(entry.getRxBytes());
166             e.writeLong(entry.getRxPackets());
167             e.writeLong(entry.getDroppedBytes());
168             e.writeLong(entry.getDroppedPackets());
169             pulledData.add(e);
170         });
171     }
172 
dumpVmsClientStats(Consumer<VmsClientStats> dumpFn)173     private void dumpVmsClientStats(Consumer<VmsClientStats> dumpFn) {
174         synchronized (mVmsClientStats) {
175             mVmsClientStats.values().stream()
176                     .flatMap(log -> log.getLayerEntries().stream())
177                     .sorted(VMS_CLIENT_STATS_ORDER)
178                     .forEachOrdered(dumpFn);
179         }
180     }
181 }
182