1 /* 2 * Copyright (C) 2018 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.helpers; 18 19 import android.util.Log; 20 21 import com.android.os.AtomsProto.AppStartFullyDrawn; 22 import com.android.os.AtomsProto.AppStartOccurred; 23 import com.android.os.AtomsProto.Atom; 24 import com.android.os.AtomsProto.ProcessStartTime; 25 import com.android.os.StatsLog.EventMetricData; 26 27 import java.util.ArrayList; 28 import java.util.HashMap; 29 import java.util.List; 30 import java.util.Map; 31 import java.util.Map.Entry; 32 import java.util.stream.Collectors; 33 34 /** 35 * AppStartupHelper consist of helper methods to set the app 36 * startup configs in statsd to track the app startup related 37 * performance metrics and retrieve the necessary information from 38 * statsd using the config id. 39 */ 40 public class AppStartupHelper implements ICollectorHelper<StringBuilder> { 41 42 private static final String LOG_TAG = AppStartupHelper.class.getSimpleName(); 43 44 private static final String COLD_STARTUP = "cold_startup"; 45 private static final String WARM_STARTUP = "warm_startup"; 46 private static final String HOT_STARTUP = "hot_startup"; 47 private static final String COUNT = "count"; 48 private static final String TOTAL_COUNT = "total_count"; 49 50 private static final String STARTUP_FULLY_DRAWN_UNKNOWN = "startup_fully_drawn_unknown"; 51 private static final String STARTUP_FULLY_DRAWN_WITH_BUNDLE = "startup_fully_drawn_with_bundle"; 52 private static final String STARTUP_FULLY_DRAWN_WITHOUT_BUNDLE = 53 "startup_fully_drawn_without_bundle"; 54 55 private static final String PROCESS_START = "process_start"; 56 private static final String PROCESS_START_DELAY = "process_start_delay"; 57 private static final String TRANSITION_DELAY_MILLIS = "transition_delay_millis"; 58 private boolean isProcStartDetailsDisabled; 59 60 private StatsdHelper mStatsdHelper = new StatsdHelper(); 61 62 /** 63 * Set up the app startup statsd config to track the metrics during the app start occurred. 64 */ 65 @Override startCollecting()66 public boolean startCollecting() { 67 Log.i(LOG_TAG, "Adding app startup configs to statsd."); 68 List<Integer> atomIdList = new ArrayList<>(); 69 atomIdList.add(Atom.APP_START_OCCURRED_FIELD_NUMBER); 70 atomIdList.add(Atom.APP_START_FULLY_DRAWN_FIELD_NUMBER); 71 if (!isProcStartDetailsDisabled) { 72 atomIdList.add(Atom.PROCESS_START_TIME_FIELD_NUMBER); 73 } 74 return mStatsdHelper.addEventConfig(atomIdList); 75 } 76 77 /** 78 * Collect the app startup metrics tracked during the app startup occurred from the statsd. 79 */ 80 @Override getMetrics()81 public Map<String, StringBuilder> getMetrics() { 82 List<EventMetricData> eventMetricData = mStatsdHelper.getEventMetrics(); 83 Map<String, StringBuilder> appStartResultMap = new HashMap<>(); 84 Map<String, Integer> appStartCountMap = new HashMap<>(); 85 Map<String, Integer> tempResultCountMap = new HashMap<>(); 86 for (EventMetricData dataItem : eventMetricData) { 87 Atom atom = dataItem.getAtom(); 88 if (atom.hasAppStartOccurred()) { 89 AppStartOccurred appStartAtom = atom.getAppStartOccurred(); 90 String pkgName = appStartAtom.getPkgName(); 91 String transitionType = appStartAtom.getType().toString(); 92 int windowsDrawnMillis = appStartAtom.getWindowsDrawnDelayMillis(); 93 int transitionDelayMillis = appStartAtom.getTransitionDelayMillis(); 94 Log.i(LOG_TAG, String.format("Pkg Name: %s, Transition Type: %s, " 95 + "WindowDrawnDelayMillis: %s, TransitionDelayMillis: %s", 96 pkgName, transitionType, windowsDrawnMillis, transitionDelayMillis)); 97 98 String metricTypeKey = ""; 99 String metricTransitionKey = ""; 100 // To track number of startups per type per package. 101 String metricCountKey = ""; 102 // To track total number of startups per type. 103 String totalCountKey = ""; 104 String typeKey = ""; 105 switch (appStartAtom.getType()) { 106 case COLD: 107 typeKey = COLD_STARTUP; 108 break; 109 case WARM: 110 typeKey = WARM_STARTUP; 111 break; 112 case HOT: 113 typeKey = HOT_STARTUP; 114 break; 115 case UNKNOWN: 116 break; 117 } 118 if (!typeKey.isEmpty()) { 119 metricTypeKey = MetricUtility.constructKey(typeKey, pkgName); 120 metricCountKey = MetricUtility.constructKey(typeKey, COUNT, pkgName); 121 totalCountKey = MetricUtility.constructKey(typeKey, TOTAL_COUNT); 122 123 // Update the windows drawn delay metrics. 124 MetricUtility.addMetric(metricTypeKey, windowsDrawnMillis, appStartResultMap); 125 MetricUtility.addMetric(metricCountKey, appStartCountMap); 126 MetricUtility.addMetric(totalCountKey, appStartCountMap); 127 128 // Update the transition delay metrics. 129 metricTransitionKey = MetricUtility.constructKey(typeKey, 130 TRANSITION_DELAY_MILLIS, pkgName); 131 MetricUtility.addMetric(metricTransitionKey, transitionDelayMillis, 132 appStartResultMap); 133 } 134 } 135 if (atom.hasAppStartFullyDrawn()) { 136 AppStartFullyDrawn appFullyDrawnAtom = atom.getAppStartFullyDrawn(); 137 String pkgName = appFullyDrawnAtom.getPkgName(); 138 String transitionType = appFullyDrawnAtom.getType().toString(); 139 long startupTimeMillis = appFullyDrawnAtom.getAppStartupTimeMillis(); 140 Log.i(LOG_TAG, String.format("Pkg Name: %s, Transition Type: %s, " 141 + "AppStartupTimeMillis: %d", pkgName, transitionType, startupTimeMillis)); 142 143 String metricKey = ""; 144 switch (appFullyDrawnAtom.getType()) { 145 case UNKNOWN: 146 metricKey = MetricUtility.constructKey( 147 STARTUP_FULLY_DRAWN_UNKNOWN, pkgName); 148 break; 149 case WITH_BUNDLE: 150 metricKey = MetricUtility.constructKey( 151 STARTUP_FULLY_DRAWN_WITH_BUNDLE, pkgName); 152 break; 153 case WITHOUT_BUNDLE: 154 metricKey = MetricUtility.constructKey( 155 STARTUP_FULLY_DRAWN_WITHOUT_BUNDLE, pkgName); 156 break; 157 } 158 if (!metricKey.isEmpty()) { 159 MetricUtility.addMetric(metricKey, startupTimeMillis, appStartResultMap); 160 } 161 } 162 // ProcessStartTime reports startup time for both foreground and background process. 163 if (atom.hasProcessStartTime()) { 164 ProcessStartTime processStartTimeAtom = atom.getProcessStartTime(); 165 String processName = processStartTimeAtom.getProcessName(); 166 // Number of milliseconds it takes to finish start of the process. 167 long processStartDelayMillis = processStartTimeAtom.getProcessStartDelayMillis(); 168 // Treating activity hosting type as foreground and everything else as background. 169 String hostingType = processStartTimeAtom.getHostingType().equals("activity") 170 ? "fg" : "bg"; 171 Log.i(LOG_TAG, String.format("Process Name: %s, Start Type: %s, Hosting Type: %s," 172 + " ProcessStartDelayMillis: %d", processName, 173 processStartTimeAtom.getType().toString(), 174 hostingType, processStartDelayMillis)); 175 176 String metricKey = ""; 177 // To track number of startups per type per package. 178 String metricCountKey = ""; 179 // To track total number of startups per type. 180 String totalCountKey = ""; 181 String typeKey = ""; 182 switch (processStartTimeAtom.getType()) { 183 case COLD: 184 typeKey = COLD_STARTUP; 185 break; 186 case WARM: 187 typeKey = WARM_STARTUP; 188 break; 189 case HOT: 190 typeKey = HOT_STARTUP; 191 break; 192 case UNKNOWN: 193 break; 194 } 195 if (!typeKey.isEmpty()) { 196 metricKey = MetricUtility.constructKey(typeKey, 197 PROCESS_START_DELAY, processName, hostingType); 198 metricCountKey = MetricUtility.constructKey(typeKey, PROCESS_START, 199 COUNT, processName, hostingType); 200 totalCountKey = MetricUtility.constructKey(typeKey, PROCESS_START, 201 TOTAL_COUNT); 202 // Update the metrics 203 if (isProcStartDetailsDisabled) { 204 MetricUtility.addMetric(metricCountKey, tempResultCountMap); 205 } else { 206 MetricUtility.addMetric(metricKey, processStartDelayMillis, 207 appStartResultMap); 208 MetricUtility.addMetric(metricCountKey, appStartCountMap); 209 } 210 211 MetricUtility.addMetric(totalCountKey, appStartCountMap); 212 } 213 } 214 } 215 216 if (isProcStartDetailsDisabled) { 217 for (Entry<String, Integer> entry : tempResultCountMap.entrySet()) { 218 Log.i(LOG_TAG, String.format("Process_delay_key: %s, Count: %d", entry.getKey(), 219 entry.getValue())); 220 } 221 } 222 223 // Cast to StringBuilder as the raw app startup metric could be comma separated values 224 // if there are multiple app launches. 225 Map<String, StringBuilder> finalCountMap = appStartCountMap 226 .entrySet() 227 .stream() 228 .collect( 229 Collectors.toMap(Map.Entry::getKey, 230 e -> new StringBuilder(Integer.toString(e.getValue())))); 231 // Add the count map in the app start result map. 232 appStartResultMap.putAll(finalCountMap); 233 return appStartResultMap; 234 } 235 236 /** 237 * Remove the statsd config used to track the app startup metrics. 238 */ 239 @Override stopCollecting()240 public boolean stopCollecting() { 241 return mStatsdHelper.removeStatsConfig(); 242 } 243 244 /** 245 * Disable process start detailed metrics. 246 */ setDisableProcStartDetails()247 public void setDisableProcStartDetails() { 248 isProcStartDetailsDisabled = true; 249 } 250 } 251