1 /*
2  * Copyright (C) 2017 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 package com.android.car.storagemonitoring;
17 
18 import android.car.storagemonitoring.IoStatsEntry;
19 import android.car.storagemonitoring.UidIoRecord;
20 import android.util.SparseArray;
21 import com.android.car.SparseArrayStream;
22 import com.android.car.procfsinspector.ProcessInfo;
23 import com.android.car.systeminterface.SystemStateInterface;
24 import java.util.List;
25 import java.util.Optional;
26 
27 public class IoStatsTracker {
28     private abstract class Lazy<T> {
29         protected Optional<T> mLazy = Optional.empty();
30 
supply()31         protected abstract T supply();
32 
get()33         public synchronized T get() {
34             if (!mLazy.isPresent()) {
35                 mLazy = Optional.of(supply());
36             }
37             return mLazy.get();
38         }
39     }
40 
41     private final long mSampleWindowMs;
42     private final SystemStateInterface mSystemStateInterface;
43     private SparseArray<IoStatsEntry> mTotal;
44     private SparseArray<IoStatsEntry> mCurrentSample;
45 
IoStatsTracker(List<IoStatsEntry> initialValue, long sampleWindowMs, SystemStateInterface systemStateInterface)46     public IoStatsTracker(List<IoStatsEntry> initialValue,
47             long sampleWindowMs, SystemStateInterface systemStateInterface) {
48         mTotal = new SparseArray<>(initialValue.size());
49         initialValue.forEach(uidIoStats -> mTotal.append(uidIoStats.uid, uidIoStats));
50         mCurrentSample = mTotal.clone();
51         mSampleWindowMs = sampleWindowMs;
52         mSystemStateInterface = systemStateInterface;
53     }
54 
update(SparseArray<UidIoRecord> newMetrics)55     public synchronized void update(SparseArray<UidIoRecord> newMetrics) {
56         final Lazy<List<ProcessInfo>> processTable = new Lazy<List<ProcessInfo>>() {
57             @Override
58             protected List<ProcessInfo> supply() {
59                 return mSystemStateInterface.getRunningProcesses();
60             }
61         };
62 
63         SparseArray<IoStatsEntry> newSample = new SparseArray<>();
64         SparseArray<IoStatsEntry> newTotal = new SparseArray<>();
65 
66         // prepare the new values
67         SparseArrayStream.valueStream(newMetrics).forEach( newRecord -> {
68             final int uid = newRecord.uid;
69             final IoStatsEntry oldRecord = mTotal.get(uid);
70 
71             IoStatsEntry newStats = null;
72 
73             if (oldRecord == null) {
74                 // this user id has just showed up, so just add it to the current sample
75                 // and its runtime is the size of our sample window
76                 newStats = new IoStatsEntry(newRecord, mSampleWindowMs);
77             } else {
78                 // this user id has already been detected
79 
80                 if (oldRecord.representsSameMetrics(newRecord)) {
81                     // if no new I/O happened, try to figure out if any process on behalf
82                     // of this user has happened, and use that to update the runtime metrics
83                     if (processTable.get().stream().anyMatch(pi -> pi.uid == uid)) {
84                         newStats = new IoStatsEntry(newRecord.delta(oldRecord),
85                                 oldRecord.runtimeMillis + mSampleWindowMs);
86                     }
87                     // if no new I/O happened and no process is running for this user
88                     // then do not prepare a new sample, as nothing has changed
89                 } else {
90                     // but if new I/O happened, assume something was running for the entire
91                     // sample window and compute the delta
92                     newStats = new IoStatsEntry(newRecord.delta(oldRecord),
93                             oldRecord.runtimeMillis + mSampleWindowMs);
94                 }
95             }
96 
97             if (newStats != null) {
98                 newSample.put(uid, newStats);
99                 newTotal.append(uid, new IoStatsEntry(newRecord, newStats.runtimeMillis));
100             } else {
101                 // if oldRecord were null, newStats would be != null and we wouldn't be here
102                 newTotal.append(uid, oldRecord);
103             }
104         });
105 
106         // now update the stored values
107         mCurrentSample = newSample;
108         mTotal = newTotal;
109     }
110 
getTotal()111     public synchronized SparseArray<IoStatsEntry> getTotal() {
112         return mTotal;
113     }
114 
getCurrentSample()115     public synchronized SparseArray<IoStatsEntry> getCurrentSample() {
116         return mCurrentSample;
117     }
118 }
119