1 /* 2 * Copyright (C) 2021 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.providers.media.metrics; 18 19 import static com.android.providers.media.MediaProviderStatsLog.TRANSCODING_DATA; 20 21 import android.app.StatsManager; 22 import android.util.StatsEvent; 23 24 import com.android.internal.annotations.VisibleForTesting; 25 26 import java.util.ArrayList; 27 import java.util.Collections; 28 import java.util.List; 29 import java.util.Random; 30 31 /** 32 * Stores metrics for transcode sessions to be shared with statsd. 33 */ 34 final class TranscodeMetrics { 35 private static final List<TranscodingStatsData> TRANSCODING_STATS_DATA = new ArrayList<>(); 36 37 // PLEASE update these if there's a change in the proto message, per the limit set in 38 // StatsEvent#MAX_PULL_PAYLOAD_SIZE 39 private static final int STATS_DATA_SAMPLE_LIMIT = 300; 40 private static final int STATS_DATA_COUNT_HARD_LIMIT = 500; // for safety 41 42 // Total data save requests we've received for one statsd pull cycle. 43 // This can be greater than TRANSCODING_STATS_DATA.size() since we might not add all the 44 // incoming data because of the hard limit on the size. 45 private static int sTotalStatsDataCount = 0; 46 pullStatsEvents()47 static List<StatsEvent> pullStatsEvents() { 48 synchronized (TRANSCODING_STATS_DATA) { 49 if (TRANSCODING_STATS_DATA.size() > STATS_DATA_SAMPLE_LIMIT) { 50 doRandomSampling(); 51 } 52 53 List<StatsEvent> result = getStatsEvents(); 54 resetStatsData(); 55 return result; 56 } 57 } 58 getStatsEvents()59 private static List<StatsEvent> getStatsEvents() { 60 synchronized (TRANSCODING_STATS_DATA) { 61 List<StatsEvent> result = new ArrayList<>(); 62 StatsEvent event; 63 int dataCountToFill = Math.min(TRANSCODING_STATS_DATA.size(), STATS_DATA_SAMPLE_LIMIT); 64 for (int i = 0; i < dataCountToFill; ++i) { 65 TranscodingStatsData statsData = TRANSCODING_STATS_DATA.get(i); 66 event = StatsEvent.newBuilder().setAtomId(TRANSCODING_DATA) 67 .writeString(statsData.mRequestorPackage) 68 .writeInt(statsData.mAccessType) 69 .writeLong(statsData.mFileSizeBytes) 70 .writeInt(statsData.mTranscodeResult) 71 .writeLong(statsData.mTranscodeDurationMillis) 72 .writeLong(statsData.mFileDurationMillis) 73 .writeLong(statsData.mFrameRate) 74 .writeInt(statsData.mAccessReason).build(); 75 76 result.add(event); 77 } 78 return result; 79 } 80 } 81 82 /** 83 * The random samples would get collected in the first {@code STATS_DATA_SAMPLE_LIMIT} positions 84 * inside {@code TRANSCODING_STATS_DATA} 85 */ doRandomSampling()86 private static void doRandomSampling() { 87 Random random = new Random(System.currentTimeMillis()); 88 89 synchronized (TRANSCODING_STATS_DATA) { 90 for (int i = 0; i < STATS_DATA_SAMPLE_LIMIT; ++i) { 91 int randomIndex = random.nextInt(TRANSCODING_STATS_DATA.size() - i /* bound */) 92 + i; 93 Collections.swap(TRANSCODING_STATS_DATA, i, randomIndex); 94 } 95 } 96 } 97 98 @VisibleForTesting resetStatsData()99 static void resetStatsData() { 100 synchronized (TRANSCODING_STATS_DATA) { 101 TRANSCODING_STATS_DATA.clear(); 102 sTotalStatsDataCount = 0; 103 } 104 } 105 106 /** Saves the statsd data that'd eventually be shared in the pull callback. */ 107 @VisibleForTesting saveStatsData(TranscodingStatsData transcodingStatsData)108 static void saveStatsData(TranscodingStatsData transcodingStatsData) { 109 checkAndLimitStatsDataSizeAfterAddition(transcodingStatsData); 110 } 111 checkAndLimitStatsDataSizeAfterAddition( TranscodingStatsData transcodingStatsData)112 private static void checkAndLimitStatsDataSizeAfterAddition( 113 TranscodingStatsData transcodingStatsData) { 114 synchronized (TRANSCODING_STATS_DATA) { 115 ++sTotalStatsDataCount; 116 117 if (TRANSCODING_STATS_DATA.size() < STATS_DATA_COUNT_HARD_LIMIT) { 118 TRANSCODING_STATS_DATA.add(transcodingStatsData); 119 return; 120 } 121 122 // Depending on how much transcoding we are doing, we might end up accumulating a lot of 123 // data by the time statsd comes back with the pull callback. 124 // We don't want to just keep growing our memory usage. 125 // So we simply randomly choose an element to remove with equal likeliness. 126 Random random = new Random(System.currentTimeMillis()); 127 int replaceIndex = random.nextInt(sTotalStatsDataCount /* bound */); 128 129 if (replaceIndex < STATS_DATA_COUNT_HARD_LIMIT) { 130 TRANSCODING_STATS_DATA.set(replaceIndex, transcodingStatsData); 131 } 132 } 133 } 134 135 @VisibleForTesting getSavedStatsDataCount()136 static int getSavedStatsDataCount() { 137 return TRANSCODING_STATS_DATA.size(); 138 } 139 140 @VisibleForTesting getTotalStatsDataCount()141 static int getTotalStatsDataCount() { 142 return sTotalStatsDataCount; 143 } 144 145 @VisibleForTesting getStatsDataCountHardLimit()146 static int getStatsDataCountHardLimit() { 147 return STATS_DATA_COUNT_HARD_LIMIT; 148 } 149 150 @VisibleForTesting getStatsDataSampleLimit()151 static int getStatsDataSampleLimit() { 152 return STATS_DATA_SAMPLE_LIMIT; 153 } 154 155 /** This is the data to populate the proto shared with statsd. */ 156 static final class TranscodingStatsData { 157 private final String mRequestorPackage; 158 private final short mAccessType; 159 private final long mFileSizeBytes; 160 private final short mTranscodeResult; 161 private final long mTranscodeDurationMillis; 162 private final long mFileDurationMillis; 163 private final long mFrameRate; 164 private final short mAccessReason; 165 TranscodingStatsData(String requestorPackage, int accessType, long fileSizeBytes, int transcodeResult, long transcodeDurationMillis, long videoDurationMillis, long frameRate, short transcodeReason)166 TranscodingStatsData(String requestorPackage, int accessType, long fileSizeBytes, 167 int transcodeResult, long transcodeDurationMillis, 168 long videoDurationMillis, long frameRate, short transcodeReason) { 169 mRequestorPackage = requestorPackage; 170 mAccessType = (short) accessType; 171 mFileSizeBytes = fileSizeBytes; 172 mTranscodeResult = (short) transcodeResult; 173 mTranscodeDurationMillis = transcodeDurationMillis; 174 mFileDurationMillis = videoDurationMillis; 175 mFrameRate = frameRate; 176 mAccessReason = transcodeReason; 177 } 178 } 179 } 180