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.car.watchdog;
18 
19 import static com.android.car.watchdog.WatchdogPerfHandler.INTERNAL_APPLICATION_CATEGORY_TYPE_MAPS;
20 import static com.android.car.watchdog.WatchdogPerfHandler.INTERNAL_APPLICATION_CATEGORY_TYPE_MEDIA;
21 
22 import android.automotive.watchdog.PerStateBytes;
23 import android.automotive.watchdog.internal.ApplicationCategoryType;
24 import android.automotive.watchdog.internal.ComponentType;
25 import android.automotive.watchdog.internal.IoOveruseConfiguration;
26 import android.automotive.watchdog.internal.PackageMetadata;
27 import android.automotive.watchdog.internal.PerStateIoOveruseThreshold;
28 import android.automotive.watchdog.internal.ResourceOveruseConfiguration;
29 import android.automotive.watchdog.internal.ResourceSpecificConfiguration;
30 import android.car.builtin.util.Slogf;
31 import android.util.ArrayMap;
32 import android.util.ArraySet;
33 import android.util.SparseArray;
34 import android.util.proto.ProtoOutputStream;
35 
36 import com.android.car.internal.util.IndentingPrintWriter;
37 import com.android.car.watchdog.PerformanceDump.IoThresholdByAppCategory;
38 import com.android.car.watchdog.PerformanceDump.IoThresholdByComponent;
39 import com.android.car.watchdog.PerformanceDump.IoThresholdByPackage;
40 import com.android.car.watchdog.PerformanceDump.OveruseConfigurationCacheDump;
41 import com.android.car.watchdog.PerformanceDump.PackageByAppCategory;
42 import com.android.internal.annotations.GuardedBy;
43 
44 import java.util.ArrayList;
45 import java.util.List;
46 import java.util.Set;
47 import java.util.function.BiFunction;
48 
49 /**
50  * Cache to store overuse configurations in memory.
51  *
52  * <p>It assumes that the error checking and loading/merging initial configs are done prior to
53  * setting the cache.
54  */
55 public final class OveruseConfigurationCache {
56     private static final String TAG = OveruseConfigurationCache.class.getSimpleName();
57     static final PerStateBytes DEFAULT_THRESHOLD =
58             constructPerStateBytes(Long.MAX_VALUE, Long.MAX_VALUE, Long.MAX_VALUE);
59 
60     private final Object mLock = new Object();
61     @GuardedBy("mLock")
62     private final ArraySet<String> mSafeToKillSystemPackages = new ArraySet<>();
63     @GuardedBy("mLock")
64     private final ArraySet<String> mSafeToKillVendorPackages = new ArraySet<>();
65     @GuardedBy("mLock")
66     private final List<String> mVendorPackagePrefixes = new ArrayList<>();
67     @GuardedBy("mLock")
68     private final SparseArray<ArraySet<String>> mPackagesByAppCategoryType = new SparseArray<>();
69     @GuardedBy("mLock")
70     private final SparseArray<PerStateBytes> mGenericIoThresholdsByComponent = new SparseArray<>();
71     @GuardedBy("mLock")
72     private final ArrayMap<String, PerStateBytes> mIoThresholdsBySystemPackages = new ArrayMap<>();
73     @GuardedBy("mLock")
74     private final ArrayMap<String, PerStateBytes> mIoThresholdsByVendorPackages = new ArrayMap<>();
75     @GuardedBy("mLock")
76     private final SparseArray<PerStateBytes> mIoThresholdsByAppCategoryType = new SparseArray<>();
77 
78     /** Dumps the contents of the cache. */
dump(IndentingPrintWriter writer)79     public void dump(IndentingPrintWriter writer) {
80         writer.println("*" + getClass().getSimpleName() + "*");
81         writer.increaseIndent();
82         synchronized (mLock) {
83             writer.println("mSafeToKillSystemPackages: " + mSafeToKillSystemPackages);
84             writer.println("mSafeToKillVendorPackages: " + mSafeToKillVendorPackages);
85             writer.println("mVendorPackagePrefixes: " + mVendorPackagePrefixes);
86             writer.println("mPackagesByAppCategoryType: ");
87             writer.increaseIndent();
88             for (int i = 0; i < mPackagesByAppCategoryType.size(); ++i) {
89                 writer.print("App category: "
90                         + toApplicationCategoryTypeString(mPackagesByAppCategoryType.keyAt(i)));
91                 writer.println(", Packages: " + mPackagesByAppCategoryType.valueAt(i));
92             }
93             writer.decreaseIndent();
94             writer.println("mGenericIoThresholdsByComponent: ");
95             writer.increaseIndent();
96             for (int i = 0; i < mGenericIoThresholdsByComponent.size(); ++i) {
97                 writer.print("Component type: "
98                         + toComponentTypeString(mGenericIoThresholdsByComponent.keyAt(i)));
99                 writer.print(", Threshold: ");
100                 dumpPerStateBytes(mGenericIoThresholdsByComponent.valueAt(i), writer);
101             }
102             writer.decreaseIndent();
103             writer.println("mIoThresholdsBySystemPackages: ");
104             writer.increaseIndent();
105             for (int i = 0; i < mIoThresholdsBySystemPackages.size(); ++i) {
106                 writer.print("Package name: " + mIoThresholdsBySystemPackages.keyAt(i));
107                 writer.print(", Threshold: ");
108                 dumpPerStateBytes(mIoThresholdsBySystemPackages.valueAt(i), writer);
109             }
110             writer.decreaseIndent();
111             writer.println("mIoThresholdsByVendorPackages: ");
112             writer.increaseIndent();
113             for (int i = 0; i < mIoThresholdsByVendorPackages.size(); ++i) {
114                 writer.print("Package name: " + mIoThresholdsByVendorPackages.keyAt(i));
115                 writer.print(", Threshold: ");
116                 dumpPerStateBytes(mIoThresholdsByVendorPackages.valueAt(i), writer);
117             }
118             writer.decreaseIndent();
119             writer.println("mIoThresholdsByAppCategoryType: ");
120             writer.increaseIndent();
121             for (int i = 0; i < mIoThresholdsByAppCategoryType.size(); ++i) {
122                 writer.print("App category: "
123                         + toApplicationCategoryTypeString(mIoThresholdsByAppCategoryType.keyAt(i)));
124                 writer.print(", Threshold: ");
125                 dumpPerStateBytes(mIoThresholdsByAppCategoryType.valueAt(i), writer);
126             }
127             writer.decreaseIndent();
128         }
129         writer.decreaseIndent();
130     }
131 
132     /** Dumps the contents of the cache in proto format. */
dumpProto(ProtoOutputStream proto)133     public void dumpProto(ProtoOutputStream proto) {
134         synchronized (mLock) {
135             long overuseConfigurationCacheDumpToken = proto.start(
136                     PerformanceDump.OVERUSE_CONFIGURATION_CACHE_DUMP);
137             for (int i = 0; i < mSafeToKillSystemPackages.size(); i++) {
138                 proto.write(OveruseConfigurationCacheDump.SAFE_TO_KILL_SYSTEM_PACKAGES,
139                         mSafeToKillSystemPackages.valueAt(i));
140             }
141             for (int i = 0; i < mSafeToKillVendorPackages.size(); i++) {
142                 proto.write(OveruseConfigurationCacheDump.SAFE_TO_KILL_VENDOR_PACKAGES,
143                         mSafeToKillVendorPackages.valueAt(i));
144             }
145             for (int i = 0; i < mVendorPackagePrefixes.size(); i++) {
146                 proto.write(OveruseConfigurationCacheDump.VENDOR_PACKAGE_PREFIXES,
147                         mVendorPackagePrefixes.get(i));
148             }
149 
150             for (int i = 0; i < mPackagesByAppCategoryType.size(); i++) {
151                 long packageByAppCategoryToken = proto.start(
152                         OveruseConfigurationCacheDump.PACKAGES_BY_APP_CATEGORY);
153                 proto.write(PackageByAppCategory.APPLICATION_CATEGORY,
154                         toProtoApplicationCategory(mPackagesByAppCategoryType.keyAt(i)));
155                 ArraySet<String> packages = mPackagesByAppCategoryType.valueAt(i);
156                 for (int j = 0; j < packages.size(); j++) {
157                     proto.write(PackageByAppCategory.PACKAGE_NAME,
158                             packages.valueAt(j));
159                 }
160                 proto.end(packageByAppCategoryToken);
161             }
162 
163             for (int i = 0; i < mGenericIoThresholdsByComponent.size(); i++) {
164                 long ioThresholdByComponentToken = proto.start(
165                         OveruseConfigurationCacheDump.GENERIC_IO_THRESHOLDS_BY_COMPONENT);
166                 proto.write(IoThresholdByComponent.COMPONENT_TYPE,
167                         toProtoComponentType(mGenericIoThresholdsByComponent.keyAt(i)));
168                 long perStateBytesToken = proto.start(IoThresholdByComponent.THRESHOLD);
169                 PerStateBytes perStateBytes = mGenericIoThresholdsByComponent.valueAt(i);
170                 proto.write(PerformanceDump.PerStateBytes.FOREGROUND_BYTES,
171                         perStateBytes.foregroundBytes);
172                 proto.write(PerformanceDump.PerStateBytes.BACKGROUND_BYTES,
173                         perStateBytes.backgroundBytes);
174                 proto.write(PerformanceDump.PerStateBytes.GARAGEMODE_BYTES,
175                         perStateBytes.garageModeBytes);
176                 proto.end(perStateBytesToken);
177                 proto.end(ioThresholdByComponentToken);
178             }
179 
180             for (int i = 0; i < mIoThresholdsBySystemPackages.size(); i++) {
181                 long ioThresholdByPackageToken = proto.start(
182                         OveruseConfigurationCacheDump.IO_THRESHOLDS_BY_PACKAGE);
183                 proto.write(IoThresholdByPackage.PACKAGE_TYPE, PerformanceDump.SYSTEM);
184                 long perStateBytesToken = proto.start(IoThresholdByComponent.THRESHOLD);
185                 PerStateBytes perStateBytes = mIoThresholdsBySystemPackages.valueAt(i);
186                 proto.write(PerformanceDump.PerStateBytes.FOREGROUND_BYTES,
187                         perStateBytes.foregroundBytes);
188                 proto.write(PerformanceDump.PerStateBytes.BACKGROUND_BYTES,
189                         perStateBytes.backgroundBytes);
190                 proto.write(PerformanceDump.PerStateBytes.GARAGEMODE_BYTES,
191                         perStateBytes.garageModeBytes);
192                 proto.end(perStateBytesToken);
193                 proto.write(IoThresholdByPackage.PACKAGE_NAME,
194                         mIoThresholdsBySystemPackages.keyAt(i));
195                 proto.end(ioThresholdByPackageToken);
196             }
197 
198             for (int i = 0; i < mIoThresholdsByVendorPackages.size(); i++) {
199                 long ioThresholdByPackageToken = proto.start(
200                         OveruseConfigurationCacheDump.IO_THRESHOLDS_BY_PACKAGE);
201                 proto.write(IoThresholdByPackage.PACKAGE_TYPE, PerformanceDump.VENDOR);
202                 long perStateBytesToken = proto.start(IoThresholdByComponent.THRESHOLD);
203                 PerStateBytes perStateBytes = mIoThresholdsByVendorPackages.valueAt(i);
204                 proto.write(PerformanceDump.PerStateBytes.FOREGROUND_BYTES,
205                         perStateBytes.foregroundBytes);
206                 proto.write(PerformanceDump.PerStateBytes.BACKGROUND_BYTES,
207                         perStateBytes.backgroundBytes);
208                 proto.write(PerformanceDump.PerStateBytes.GARAGEMODE_BYTES,
209                         perStateBytes.garageModeBytes);
210                 proto.end(perStateBytesToken);
211                 proto.write(IoThresholdByPackage.PACKAGE_NAME,
212                         mIoThresholdsByVendorPackages.keyAt(i));
213                 proto.end(ioThresholdByPackageToken);
214             }
215 
216             for (int i = 0; i < mIoThresholdsByAppCategoryType.size(); i++) {
217                 long ioThresholdsByAppCategoryTypeToken = proto.start(
218                         OveruseConfigurationCacheDump.THRESHOLDS_BY_APP_CATEGORY);
219                 proto.write(IoThresholdByAppCategory.APPLICATION_CATEGORY,
220                         toProtoApplicationCategory(mIoThresholdsByAppCategoryType.keyAt(i)));
221                 long perStateBytesToken = proto.start(IoThresholdByAppCategory.THRESHOLD);
222                 PerStateBytes perStateBytes = mIoThresholdsByAppCategoryType.valueAt(i);
223                 proto.write(PerformanceDump.PerStateBytes.FOREGROUND_BYTES,
224                         perStateBytes.foregroundBytes);
225                 proto.write(PerformanceDump.PerStateBytes.BACKGROUND_BYTES,
226                         perStateBytes.backgroundBytes);
227                 proto.write(PerformanceDump.PerStateBytes.GARAGEMODE_BYTES,
228                         perStateBytes.garageModeBytes);
229                 proto.end(perStateBytesToken);
230                 proto.end(ioThresholdsByAppCategoryTypeToken);
231             }
232 
233             proto.end(overuseConfigurationCacheDumpToken);
234         }
235     }
236 
237     /** Overwrites the configurations in the cache. */
set(List<ResourceOveruseConfiguration> configs)238     public void set(List<ResourceOveruseConfiguration> configs) {
239         synchronized (mLock) {
240             clearLocked();
241             for (int i = 0; i < configs.size(); i++) {
242                 ResourceOveruseConfiguration config = configs.get(i);
243                 switch (config.componentType) {
244                     case ComponentType.SYSTEM:
245                         mSafeToKillSystemPackages.addAll(config.safeToKillPackages);
246                         break;
247                     case ComponentType.VENDOR:
248                         mSafeToKillVendorPackages.addAll(config.safeToKillPackages);
249                         mVendorPackagePrefixes.addAll(config.vendorPackagePrefixes);
250                         for (int j = 0; j < config.packageMetadata.size(); ++j) {
251                             PackageMetadata meta = config.packageMetadata.get(j);
252                             ArraySet<String> packages =
253                                     mPackagesByAppCategoryType.get(meta.appCategoryType);
254                             if (packages == null) {
255                                 packages = new ArraySet<>();
256                             }
257                             packages.add(meta.packageName);
258                             mPackagesByAppCategoryType.append(meta.appCategoryType, packages);
259                         }
260                         break;
261                     default:
262                         // All third-party apps are killable.
263                         break;
264                 }
265                 for (int j = 0; j < config.resourceSpecificConfigurations.size(); ++j) {
266                     if (config.resourceSpecificConfigurations.get(j).getTag()
267                             == ResourceSpecificConfiguration.ioOveruseConfiguration) {
268                         setIoThresholdsLocked(config.componentType,
269                                 config.resourceSpecificConfigurations.get(j)
270                                         .getIoOveruseConfiguration());
271                     }
272                 }
273             }
274         }
275     }
276 
277     /** Returns the threshold for the given package and component type. */
fetchThreshold(String genericPackageName, @ComponentType int componentType)278     public PerStateBytes fetchThreshold(String genericPackageName,
279             @ComponentType int componentType) {
280         synchronized (mLock) {
281             PerStateBytes threshold = null;
282             switch (componentType) {
283                 case ComponentType.SYSTEM:
284                     threshold = mIoThresholdsBySystemPackages.get(genericPackageName);
285                     if (threshold != null) {
286                         return copyPerStateBytes(threshold);
287                     }
288                     break;
289                 case ComponentType.VENDOR:
290                     threshold = mIoThresholdsByVendorPackages.get(genericPackageName);
291                     if (threshold != null) {
292                         return copyPerStateBytes(threshold);
293                     }
294                     break;
295                 default:
296                     // THIRD_PARTY and UNKNOWN components are set with the
297                     // default thresholds.
298                     break;
299             }
300             threshold = fetchAppCategorySpecificThresholdLocked(genericPackageName);
301             if (threshold != null) {
302                 return copyPerStateBytes(threshold);
303             }
304             threshold = mGenericIoThresholdsByComponent.get(componentType);
305             return threshold != null ? copyPerStateBytes(threshold)
306                     : copyPerStateBytes(DEFAULT_THRESHOLD);
307         }
308     }
309 
310     /** Returns whether or not the given package is safe-to-kill on resource overuse. */
isSafeToKill(String genericPackageName, @ComponentType int componentType, List<String> sharedPackages)311     public boolean isSafeToKill(String genericPackageName, @ComponentType int componentType,
312             List<String> sharedPackages) {
313         synchronized (mLock) {
314             BiFunction<List<String>, Set<String>, Boolean> isSafeToKillAnyPackage =
315                     (packages, safeToKillPackages) -> {
316                         if (packages == null) {
317                             return false;
318                         }
319                         for (int i = 0; i < packages.size(); i++) {
320                             if (safeToKillPackages.contains(packages.get(i))) {
321                                 return true;
322                             }
323                         }
324                         return false;
325                     };
326 
327             switch (componentType) {
328                 case ComponentType.SYSTEM:
329                     if (mSafeToKillSystemPackages.contains(genericPackageName)) {
330                         return true;
331                     }
332                     return isSafeToKillAnyPackage.apply(sharedPackages, mSafeToKillSystemPackages);
333                 case ComponentType.VENDOR:
334                     if (mSafeToKillVendorPackages.contains(genericPackageName)) {
335                         return true;
336                     }
337                     /*
338                      * Packages under the vendor shared UID may contain system packages because when
339                      * CarWatchdogService derives the shared component type it attributes system
340                      * packages as vendor packages when there is at least one vendor package.
341                      */
342                     return isSafeToKillAnyPackage.apply(sharedPackages, mSafeToKillSystemPackages)
343                             || isSafeToKillAnyPackage.apply(sharedPackages,
344                             mSafeToKillVendorPackages);
345                 default:
346                     // Third-party apps are always killable
347                     return true;
348             }
349         }
350     }
351 
352     /** Returns the list of vendor package prefixes. */
getVendorPackagePrefixes()353     public List<String> getVendorPackagePrefixes() {
354         synchronized (mLock) {
355             return new ArrayList<>(mVendorPackagePrefixes);
356         }
357     }
358 
359     @GuardedBy("mLock")
clearLocked()360     private void clearLocked() {
361         mSafeToKillSystemPackages.clear();
362         mSafeToKillVendorPackages.clear();
363         mVendorPackagePrefixes.clear();
364         mPackagesByAppCategoryType.clear();
365         mGenericIoThresholdsByComponent.clear();
366         mIoThresholdsBySystemPackages.clear();
367         mIoThresholdsByVendorPackages.clear();
368         mIoThresholdsByAppCategoryType.clear();
369     }
370 
371     @GuardedBy("mLock")
setIoThresholdsLocked(int componentType, IoOveruseConfiguration ioConfig)372     private void setIoThresholdsLocked(int componentType, IoOveruseConfiguration ioConfig) {
373         mGenericIoThresholdsByComponent.append(componentType,
374                 ioConfig.componentLevelThresholds.perStateWriteBytes);
375         switch (componentType) {
376             case ComponentType.SYSTEM:
377                 populateThresholdsByPackagesLocked(
378                         ioConfig.packageSpecificThresholds, mIoThresholdsBySystemPackages);
379                 break;
380             case ComponentType.VENDOR:
381                 populateThresholdsByPackagesLocked(
382                         ioConfig.packageSpecificThresholds, mIoThresholdsByVendorPackages);
383                 setIoThresholdsByAppCategoryTypeLocked(ioConfig.categorySpecificThresholds);
384                 break;
385             default:
386                 Slogf.i(TAG, "Ignoring I/O overuse threshold for invalid component type: %d",
387                         componentType);
388         }
389     }
390 
391     @GuardedBy("mLock")
setIoThresholdsByAppCategoryTypeLocked( List<PerStateIoOveruseThreshold> thresholds)392     private void setIoThresholdsByAppCategoryTypeLocked(
393             List<PerStateIoOveruseThreshold> thresholds) {
394         for (int i = 0; i < thresholds.size(); ++i) {
395             PerStateIoOveruseThreshold threshold = thresholds.get(i);
396             switch(threshold.name) {
397                 case INTERNAL_APPLICATION_CATEGORY_TYPE_MAPS:
398                     mIoThresholdsByAppCategoryType.append(
399                             ApplicationCategoryType.MAPS, threshold.perStateWriteBytes);
400                     break;
401                 case INTERNAL_APPLICATION_CATEGORY_TYPE_MEDIA:
402                     mIoThresholdsByAppCategoryType.append(ApplicationCategoryType.MEDIA,
403                             threshold.perStateWriteBytes);
404                     break;
405                 default:
406                     Slogf.i(TAG,
407                             "Ignoring I/O overuse threshold for invalid application category: %s",
408                             threshold.name);
409             }
410         }
411     }
412 
413     @GuardedBy("mLock")
populateThresholdsByPackagesLocked(List<PerStateIoOveruseThreshold> thresholds, ArrayMap<String, PerStateBytes> thresholdsByPackages)414     private void populateThresholdsByPackagesLocked(List<PerStateIoOveruseThreshold> thresholds,
415             ArrayMap<String, PerStateBytes> thresholdsByPackages) {
416         for (int i = 0; i < thresholds.size(); ++i) {
417             thresholdsByPackages.put(
418                     thresholds.get(i).name, thresholds.get(i).perStateWriteBytes);
419         }
420     }
421 
422     @GuardedBy("mLock")
fetchAppCategorySpecificThresholdLocked(String genericPackageName)423     private PerStateBytes fetchAppCategorySpecificThresholdLocked(String genericPackageName) {
424         for (int i = 0; i < mPackagesByAppCategoryType.size(); ++i) {
425             if (mPackagesByAppCategoryType.valueAt(i).contains(genericPackageName)) {
426                 return mIoThresholdsByAppCategoryType.get(mPackagesByAppCategoryType.keyAt(i));
427             }
428         }
429         return null;
430     }
431 
toApplicationCategoryTypeString(@pplicationCategoryType int type)432     private static String toApplicationCategoryTypeString(@ApplicationCategoryType int type) {
433         switch (type) {
434             case ApplicationCategoryType.MAPS:
435                 return "ApplicationCategoryType.MAPS";
436             case ApplicationCategoryType.MEDIA:
437                 return "ApplicationCategoryType.MEDIA";
438             case ApplicationCategoryType.OTHERS:
439                 return "ApplicationCategoryType.OTHERS";
440             default:
441                 return "Invalid ApplicationCategoryType";
442         }
443     }
444 
toComponentTypeString(@omponentType int type)445     private static String toComponentTypeString(@ComponentType int type) {
446         switch (type) {
447             case ComponentType.SYSTEM:
448                 return "ComponentType.SYSTEM";
449             case ComponentType.VENDOR:
450                 return "ComponentType.VENDOR";
451             case ComponentType.THIRD_PARTY:
452                 return "ComponentType.THIRD_PARTY";
453             default:
454                 return "ComponentType.UNKNOWN";
455         }
456     }
457 
toProtoApplicationCategory(@pplicationCategoryType int type)458     private static int toProtoApplicationCategory(@ApplicationCategoryType int type) {
459         switch (type) {
460             case ApplicationCategoryType.MAPS:
461                 return PerformanceDump.MAPS;
462             case ApplicationCategoryType.MEDIA:
463                 return PerformanceDump.MEDIA;
464             case ApplicationCategoryType.OTHERS:
465                 return PerformanceDump.OTHERS;
466             default:
467                 return PerformanceDump.APPLICATION_CATEGORY_UNSPECIFIED;
468         }
469     }
470 
toProtoComponentType(@omponentType int type)471     private static int toProtoComponentType(@ComponentType int type) {
472         switch (type) {
473             case ComponentType.SYSTEM:
474                 return PerformanceDump.SYSTEM;
475             case ComponentType.VENDOR:
476                 return PerformanceDump.VENDOR;
477             case ComponentType.THIRD_PARTY:
478                 return PerformanceDump.THIRD_PARTY;
479             default:
480                 return PerformanceDump.COMPONENT_TYPE_UNSPECIFIED;
481         }
482     }
483 
dumpPerStateBytes(PerStateBytes perStateBytes, IndentingPrintWriter writer)484     private static void dumpPerStateBytes(PerStateBytes perStateBytes,
485             IndentingPrintWriter writer) {
486         if (perStateBytes == null) {
487             writer.println("{NULL}");
488             return;
489         }
490         writer.println("{Foreground bytes: " + perStateBytes.foregroundBytes
491                 + ", Background bytes: " + perStateBytes.backgroundBytes + ", Garage mode bytes: "
492                 + perStateBytes.garageModeBytes + '}');
493     }
494 
constructPerStateBytes(long fgBytes, long bgBytes, long gmBytes)495     private static PerStateBytes constructPerStateBytes(long fgBytes, long bgBytes, long gmBytes) {
496         return new PerStateBytes() {{
497                 foregroundBytes = fgBytes;
498                 backgroundBytes = bgBytes;
499                 garageModeBytes = gmBytes;
500             }};
501     }
502 
503     private static PerStateBytes copyPerStateBytes(PerStateBytes perStateBytes) {
504         return new PerStateBytes() {{
505                 foregroundBytes = perStateBytes.foregroundBytes;
506                 backgroundBytes = perStateBytes.backgroundBytes;
507                 garageModeBytes = perStateBytes.garageModeBytes;
508             }};
509     }
510 }
511