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