SurfaceFlinger TimeStats Metrics
Add timestats metrics for SurfaceFlinger. Keep track of global metrics
like total frames, missed frames, frames fellback to client
compositions, etc, as well as layer timing metrics like the delta
combination of postTime, desiredPresentTime, acqureTime, latchTime,
presentTime, etc. This metric is aimed at GMScore.
Test: dumpsys SurfaceFlinger --timestats [go/sf-timestats for more args]
Bug: b/70388650
Change-Id: I6e4545aef62f7893020533a4e7521541ea453ecd
Merged-In: I6e4545aef62f7893020533a4e7521541ea453ecd
diff --git a/services/surfaceflinger/TimeStats/timestatsproto/Android.bp b/services/surfaceflinger/TimeStats/timestatsproto/Android.bp
new file mode 100644
index 0000000..66aa719
--- /dev/null
+++ b/services/surfaceflinger/TimeStats/timestatsproto/Android.bp
@@ -0,0 +1,55 @@
+cc_library_shared {
+ name: "libtimestats_proto",
+ vendor_available: true,
+ export_include_dirs: ["include"],
+
+ srcs: [
+ "TimeStatsHelper.cpp",
+ "timestats.proto",
+ ],
+
+ shared_libs: [
+ "android.hardware.graphics.common@1.1",
+ "libui",
+ "libprotobuf-cpp-lite",
+ "libbase",
+ "liblog",
+ ],
+
+ proto: {
+ export_proto_headers: true,
+ },
+
+ cppflags: [
+ "-Werror",
+ "-Wno-unused-parameter",
+ "-Wno-format",
+ "-Wno-c++98-compat-pedantic",
+ "-Wno-float-conversion",
+ "-Wno-disabled-macro-expansion",
+ "-Wno-float-equal",
+ "-Wno-sign-conversion",
+ "-Wno-padded",
+ "-Wno-old-style-cast",
+ "-Wno-undef",
+ ],
+
+}
+
+java_library_static {
+ name: "timestatsprotosnano",
+ host_supported: true,
+ proto: {
+ type: "nano",
+ },
+ srcs: ["*.proto"],
+ no_framework_libs: true,
+ target: {
+ android: {
+ jarjar_rules: "jarjar-rules.txt",
+ },
+ host: {
+ static_libs: ["libprotobuf-java-nano"],
+ },
+ },
+}
diff --git a/services/surfaceflinger/TimeStats/timestatsproto/TimeStatsHelper.cpp b/services/surfaceflinger/TimeStats/timestatsproto/TimeStatsHelper.cpp
new file mode 100644
index 0000000..ec0570d
--- /dev/null
+++ b/services/surfaceflinger/TimeStats/timestatsproto/TimeStatsHelper.cpp
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include <android-base/stringprintf.h>
+#include <timestatsproto/TimeStatsHelper.h>
+
+#include <array>
+#include <regex>
+
+#define HISTOGRAM_SIZE 85
+
+using android::base::StringAppendF;
+using android::base::StringPrintf;
+
+namespace android {
+namespace surfaceflinger {
+
+// Time buckets for histogram, the calculated time deltas will be lower bounded
+// to the buckets in this array.
+static const std::array<int32_t, HISTOGRAM_SIZE> histogramConfig =
+ {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
+ 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33,
+ 34, 36, 38, 40, 42, 44, 46, 48, 50, 54, 58, 62, 66, 70, 74, 78, 82,
+ 86, 90, 94, 98, 102, 106, 110, 114, 118, 122, 126, 130, 134, 138, 142, 146, 150,
+ 200, 250, 300, 350, 400, 450, 500, 550, 600, 650, 700, 750, 800, 850, 900, 950, 1000};
+
+void TimeStatsHelper::Histogram::insert(int32_t delta) {
+ if (delta < 0) return;
+ // std::lower_bound won't work on out of range values
+ if (delta > histogramConfig[HISTOGRAM_SIZE - 1]) {
+ hist[histogramConfig[HISTOGRAM_SIZE - 1]]++;
+ return;
+ }
+ auto iter = std::lower_bound(histogramConfig.begin(), histogramConfig.end(), delta);
+ hist[*iter]++;
+}
+
+float TimeStatsHelper::Histogram::averageTime() {
+ int64_t ret = 0;
+ int64_t count = 0;
+ for (auto ele : hist) {
+ count += ele.second;
+ ret += ele.first * ele.second;
+ }
+ return static_cast<float>(ret) / count;
+}
+
+std::string TimeStatsHelper::Histogram::toString() {
+ std::string result;
+ for (int32_t i = 0; i < HISTOGRAM_SIZE; ++i) {
+ int32_t bucket = histogramConfig[i];
+ int32_t count = (hist.count(bucket) == 0) ? 0 : hist[bucket];
+ StringAppendF(&result, "%dms=%d ", bucket, count);
+ }
+ result.back() = '\n';
+ return result;
+}
+
+static std::string getPackageName(const std::string& layerName) {
+ // This regular expression captures the following for instance:
+ // StatusBar in StatusBar#0
+ // com.appname in com.appname/com.appname.activity#0
+ // com.appname in SurfaceView - com.appname/com.appname.activity#0
+ const std::regex re("(?:SurfaceView[-\\s\\t]+)?([^/]+).*#\\d+");
+ std::smatch match;
+ if (std::regex_match(layerName.begin(), layerName.end(), match, re)) {
+ // There must be a match for group 1 otherwise the whole string is not
+ // matched and the above will return false
+ return match[1];
+ }
+ return "";
+}
+
+std::string TimeStatsHelper::TimeStatsLayer::toString() {
+ std::string result = "";
+ StringAppendF(&result, "layerName = %s\n", layerName.c_str());
+ packageName = getPackageName(layerName);
+ StringAppendF(&result, "packageName = %s\n", packageName.c_str());
+ StringAppendF(&result, "statsStart = %lld\n", static_cast<long long int>(statsStart));
+ StringAppendF(&result, "statsEnd = %lld\n", static_cast<long long int>(statsEnd));
+ StringAppendF(&result, "totalFrames= %d\n", totalFrames);
+ if (deltas.find("present2present") != deltas.end()) {
+ StringAppendF(&result, "averageFPS = %.3f\n",
+ 1000.0 / deltas["present2present"].averageTime());
+ }
+ for (auto ele : deltas) {
+ StringAppendF(&result, "%s histogram is as below:\n", ele.first.c_str());
+ StringAppendF(&result, "%s", ele.second.toString().c_str());
+ }
+
+ return result;
+}
+
+std::string TimeStatsHelper::TimeStatsGlobal::toString() {
+ std::string result = "SurfaceFlinger TimeStats:\n";
+ StringAppendF(&result, "statsStart = %lld\n", static_cast<long long int>(statsStart));
+ StringAppendF(&result, "statsEnd = %lld\n", static_cast<long long int>(statsEnd));
+ StringAppendF(&result, "totalFrames= %d\n", totalFrames);
+ StringAppendF(&result, "missedFrames= %d\n", missedFrames);
+ StringAppendF(&result, "clientCompositionFrames= %d\n", clientCompositionFrames);
+ StringAppendF(&result, "TimeStats for each layer is as below:\n");
+ for (auto ele : dumpStats) {
+ StringAppendF(&result, "%s", ele->toString().c_str());
+ }
+
+ return result;
+}
+
+SFTimeStatsLayerProto TimeStatsHelper::TimeStatsLayer::toProto() {
+ SFTimeStatsLayerProto layerProto;
+ layerProto.set_layer_name(layerName);
+ packageName = getPackageName(layerName);
+ layerProto.set_package_name(packageName);
+ layerProto.set_stats_start(statsStart);
+ layerProto.set_stats_end(statsEnd);
+ layerProto.set_total_frames(totalFrames);
+ for (auto ele : deltas) {
+ SFTimeStatsDeltaProto* deltaProto = layerProto.add_deltas();
+ deltaProto->set_delta_name(ele.first);
+ SFTimeStatsHistogramBucketProto* histProto = deltaProto->add_histograms();
+ for (auto histEle : ele.second.hist) {
+ histProto->set_render_millis(histEle.first);
+ histProto->set_frame_count(histEle.second);
+ }
+ }
+ return layerProto;
+}
+
+SFTimeStatsGlobalProto TimeStatsHelper::TimeStatsGlobal::toProto() {
+ SFTimeStatsGlobalProto globalProto;
+ globalProto.set_stats_start(statsStart);
+ globalProto.set_stats_end(statsEnd);
+ globalProto.set_total_frames(totalFrames);
+ globalProto.set_missed_frames(missedFrames);
+ globalProto.set_client_composition_frames(clientCompositionFrames);
+ for (auto ele : dumpStats) {
+ SFTimeStatsLayerProto* layerProto = globalProto.add_stats();
+ layerProto->CopyFrom(ele->toProto());
+ }
+ return globalProto;
+}
+
+} // namespace surfaceflinger
+} // namespace android
diff --git a/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h b/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h
new file mode 100644
index 0000000..c876f21
--- /dev/null
+++ b/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h
@@ -0,0 +1,72 @@
+/*
+ * 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.
+ */
+#pragma once
+
+#include <timestatsproto/TimeStatsProtoHeader.h>
+
+#include <math/vec4.h>
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+namespace android {
+namespace surfaceflinger {
+
+class TimeStatsHelper {
+public:
+ class Histogram {
+ public:
+ // Key is the delta time between timestamps
+ // Value is the number of appearances of that delta
+ std::unordered_map<int32_t, int32_t> hist;
+
+ void insert(int32_t delta);
+ float averageTime();
+ std::string toString();
+ };
+
+ class TimeStatsLayer {
+ public:
+ std::string layerName;
+ std::string packageName;
+ int64_t statsStart = 0;
+ int64_t statsEnd = 0;
+ int32_t totalFrames = 0;
+ std::unordered_map<std::string, Histogram> deltas;
+
+ std::string toString();
+ SFTimeStatsLayerProto toProto();
+ };
+
+ class TimeStatsGlobal {
+ public:
+ int64_t statsStart = 0;
+ int64_t statsEnd = 0;
+ int32_t totalFrames = 0;
+ int32_t missedFrames = 0;
+ int32_t clientCompositionFrames = 0;
+ std::unordered_map<std::string, TimeStatsLayer> stats;
+ std::vector<TimeStatsLayer*> dumpStats;
+
+ std::string toString();
+ SFTimeStatsGlobalProto toProto();
+ };
+};
+
+} // namespace surfaceflinger
+} // namespace android
diff --git a/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsProtoHeader.h b/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsProtoHeader.h
new file mode 100644
index 0000000..fe0d150
--- /dev/null
+++ b/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsProtoHeader.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Projectlayerproto/LayerProtoHeader.h
+ *
+ * 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.
+ */
+
+// pragma is used here to disable the warnings emitted from the protobuf
+// headers. By adding #pragma before including layer.pb.h, it supresses
+// protobuf warnings, but allows the rest of the files to continuing using
+// the current flags.
+// This file should be included instead of directly including layer.b.h
+#pragma GCC system_header
+#include <timestats.pb.h>
diff --git a/services/surfaceflinger/TimeStats/timestatsproto/jarjar-rules.txt b/services/surfaceflinger/TimeStats/timestatsproto/jarjar-rules.txt
new file mode 100644
index 0000000..40043a8
--- /dev/null
+++ b/services/surfaceflinger/TimeStats/timestatsproto/jarjar-rules.txt
@@ -0,0 +1 @@
+rule com.google.protobuf.nano.** com.android.framework.protobuf.nano.@1
diff --git a/services/surfaceflinger/TimeStats/timestatsproto/timestats.proto b/services/surfaceflinger/TimeStats/timestatsproto/timestats.proto
new file mode 100644
index 0000000..a8f6fa8
--- /dev/null
+++ b/services/surfaceflinger/TimeStats/timestatsproto/timestats.proto
@@ -0,0 +1,69 @@
+/*
+ * Copyright 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.
+ */
+
+syntax = "proto2";
+
+package android.surfaceflinger;
+
+option optimize_for = LITE_RUNTIME;
+
+// frameworks/base/core/proto/android/service/sftimestats.proto is based on
+// this proto. Please only make valid protobuf changes to these messages, and
+// keep the other file in sync with this one.
+
+message SFTimeStatsGlobalProto {
+ // The start & end timestamps in UTC as
+ // milliseconds since January 1, 1970
+ optional int64 stats_start = 1;
+ optional int64 stats_end = 2;
+ // Total frames
+ optional int32 total_frames = 3;
+ // Total missed frames of SurfaceFlinger.
+ optional int32 missed_frames = 4;
+ // Total frames fallback to client composition.
+ optional int32 client_composition_frames = 5;
+
+ repeated SFTimeStatsLayerProto stats = 6;
+}
+
+message SFTimeStatsLayerProto {
+ // The layer name
+ optional string layer_name = 1;
+ // The package name
+ optional string package_name = 2;
+ // The start & end timestamps in UTC as
+ // milliseconds since January 1, 1970
+ optional int64 stats_start = 3;
+ optional int64 stats_end = 4;
+ // Distinct frame count.
+ optional int32 total_frames = 5;
+
+ repeated SFTimeStatsDeltaProto deltas = 6;
+}
+
+message SFTimeStatsDeltaProto {
+ // Name of the time interval
+ optional string delta_name = 1;
+ // Histogram of the delta time
+ repeated SFTimeStatsHistogramBucketProto histograms = 2;
+}
+
+message SFTimeStatsHistogramBucketProto {
+ // Lower bound of render time in milliseconds.
+ optional int32 render_millis = 1;
+ // Number of frames in the bucket.
+ optional int32 frame_count = 2;
+}