1 /*
2  * Copyright (C) 2020 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.tools.metalava
18 
19 import java.io.PrintWriter
20 import java.time.LocalDateTime
21 import java.time.format.DateTimeFormatter
22 import kotlin.math.max
23 
24 class ProgressTracker(
25     private val verbose: Boolean = false,
26     private val stdout: PrintWriter = PrintWriter(System.out),
27 ) {
28     private val progressTimeFormatter = DateTimeFormatter.ofPattern("HH:mm:ss.SSS")
29     private var beginningOfLine = true
30     private var firstProgress = true
31 
32     /** Print a progress message with a timestamp when --verbose is enabled. */
progressnull33     fun progress(message: String) {
34         if (!verbose) {
35             return
36         }
37         if (!beginningOfLine) {
38             stdout.println()
39         }
40         val now = LocalDateTime.now().format(progressTimeFormatter)
41 
42         if (!firstProgress) {
43             stdout.print(now)
44             stdout.print("   CPU: ")
45             stdout.println(getCpuStats())
46 
47             stdout.print(now)
48             stdout.print("   MEM: ")
49             stdout.println(getMemoryStats())
50         }
51         firstProgress = false
52 
53         stdout.print(now)
54         stdout.print(" ")
55         stdout.print(message)
56         stdout.flush()
57         beginningOfLine = message.endsWith('\n')
58     }
59 
60     private var lastMillis: Long = -1L
61     private var lastUserMillis: Long = -1L
62     private var lastCpuMillis: Long = -1L
63 
getCpuStatsnull64     private fun getCpuStats(): String {
65         val nowMillis = System.currentTimeMillis()
66         val userMillis = threadMXBean.currentThreadUserTime / 1000_000
67         val cpuMillis = threadMXBean.currentThreadCpuTime / 1000_000
68 
69         if (lastMillis == -1L) {
70             lastMillis = nowMillis
71         }
72         if (lastUserMillis == -1L) {
73             lastUserMillis = userMillis
74         }
75         if (lastCpuMillis == -1L) {
76             lastCpuMillis = cpuMillis
77         }
78 
79         val realDeltaMs = nowMillis - lastMillis
80         val userDeltaMillis = userMillis - lastUserMillis
81         // Sometimes we'd get "-0.0" without the max.
82         val sysDeltaMillis = max(0, cpuMillis - lastCpuMillis - userDeltaMillis)
83 
84         lastMillis = nowMillis
85         lastUserMillis = userMillis
86         lastCpuMillis = cpuMillis
87 
88         return String.format(
89             "+%.1freal +%.1fusr +%.1fsys",
90             realDeltaMs / 1_000.0,
91             userDeltaMillis / 1_000.0,
92             sysDeltaMillis / 1_000.0
93         )
94     }
95 
getMemoryStatsnull96     private fun getMemoryStats(): String {
97         val mu = memoryMXBean.heapMemoryUsage
98 
99         return String.format(
100             "%dmi %dmu %dmc %dmx",
101             mu.init / 1024 / 1024,
102             mu.used / 1024 / 1024,
103             mu.committed / 1024 / 1024,
104             mu.max / 1024 / 1024
105         )
106     }
107 }
108