blob: 1dac136cb048b7c17afad89f1f2b7a52e9c9f3ec [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.util;
import android.os.SystemClock;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
import com.android.server.StatLoggerProto;
import com.android.server.StatLoggerProto.Event;
import java.io.PrintWriter;
/**
* Simple class to keep track of the number of times certain events happened and their durations for
* benchmarking.
*
* @hide
*/
public class StatLogger {
private static final String TAG = "StatLogger";
private final Object mLock = new Object();
private final int SIZE;
@GuardedBy("mLock")
private final int[] mCountStats;
@GuardedBy("mLock")
private final long[] mDurationStats;
@GuardedBy("mLock")
private final int[] mCallsPerSecond;
@GuardedBy("mLock")
private final long[] mDurationPerSecond;
@GuardedBy("mLock")
private final int[] mMaxCallsPerSecond;
@GuardedBy("mLock")
private final long[] mMaxDurationPerSecond;
@GuardedBy("mLock")
private final long[] mMaxDurationStats;
@GuardedBy("mLock")
private long mNextTickTime = SystemClock.elapsedRealtime() + 1000;
private final String[] mLabels;
public StatLogger(String[] eventLabels) {
SIZE = eventLabels.length;
mCountStats = new int[SIZE];
mDurationStats = new long[SIZE];
mCallsPerSecond = new int[SIZE];
mMaxCallsPerSecond = new int[SIZE];
mDurationPerSecond = new long[SIZE];
mMaxDurationPerSecond = new long[SIZE];
mMaxDurationStats = new long[SIZE];
mLabels = eventLabels;
}
/**
* Return the current time in the internal time unit.
* Call it before an event happens, and
* give it back to the {@link #logDurationStat(int, long)}} after the event.
*/
public long getTime() {
return SystemClock.elapsedRealtimeNanos() / 1000;
}
/**
* @see {@link #getTime()}
*
* @return the duration in microseconds.
*/
public long logDurationStat(int eventId, long start) {
synchronized (mLock) {
final long duration = getTime() - start;
if (eventId >= 0 && eventId < SIZE) {
mCountStats[eventId]++;
mDurationStats[eventId] += duration;
} else {
Slog.wtf(TAG, "Invalid event ID: " + eventId);
return duration;
}
if (mMaxDurationStats[eventId] < duration) {
mMaxDurationStats[eventId] = duration;
}
// Keep track of the per-second max.
final long nowRealtime = SystemClock.elapsedRealtime();
if (nowRealtime > mNextTickTime) {
if (mMaxCallsPerSecond[eventId] < mCallsPerSecond[eventId]) {
mMaxCallsPerSecond[eventId] = mCallsPerSecond[eventId];
}
if (mMaxDurationPerSecond[eventId] < mDurationPerSecond[eventId]) {
mMaxDurationPerSecond[eventId] = mDurationPerSecond[eventId];
}
mCallsPerSecond[eventId] = 0;
mDurationPerSecond[eventId] = 0;
mNextTickTime = nowRealtime + 1000;
}
mCallsPerSecond[eventId]++;
mDurationPerSecond[eventId] += duration;
return duration;
}
}
public void dump(PrintWriter pw, String prefix) {
dump(new IndentingPrintWriter(pw, " ").setIndent(prefix));
}
public void dump(IndentingPrintWriter pw) {
synchronized (mLock) {
pw.println("Stats:");
pw.increaseIndent();
for (int i = 0; i < SIZE; i++) {
final int count = mCountStats[i];
final double durationMs = mDurationStats[i] / 1000.0;
pw.println(String.format(
"%s: count=%d, total=%.1fms, avg=%.3fms, max calls/s=%d max dur/s=%.1fms"
+ " max time=%.1fms",
mLabels[i], count, durationMs,
(count == 0 ? 0 : durationMs / count),
mMaxCallsPerSecond[i], mMaxDurationPerSecond[i] / 1000.0,
mMaxDurationStats[i] / 1000.0));
}
pw.decreaseIndent();
}
}
public void dumpProto(ProtoOutputStream proto, long fieldId) {
synchronized (mLock) {
final long outer = proto.start(fieldId);
for (int i = 0; i < mLabels.length; i++) {
final long inner = proto.start(StatLoggerProto.EVENTS);
proto.write(Event.EVENT_ID, i);
proto.write(Event.LABEL, mLabels[i]);
proto.write(Event.COUNT, mCountStats[i]);
proto.write(Event.TOTAL_DURATION_MICROS, mDurationStats[i]);
proto.end(inner);
}
proto.end(outer);
}
}
}