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