aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLalit Maganti <lalitm@google.com>2023-06-21 10:31:36 +0100
committerLalit Maganti <lalitm@google.com>2023-06-21 10:35:22 +0100
commitdf958f6b3a78e535ab0f6e5bc829c4fe0f063541 (patch)
treef1350957bf7e92e0cd019f03291257bd7865a4d2
parent589fc100e38f1ae9f07b7a19d177e47b931b9bd8 (diff)
downloadperfetto-android14-dev.tar.gz
protozero: implement string filtering as part of message filteringandroid14-dev
This CL introduces support for filtering strings in traces (i.e by redacting contents) using regexes. It also adds faster filtering primitives for atrace which is expected to be where this code is used the most. (cherry-picked from aosp/2629209) Bug: 283246642 Change-Id: Ie3624e4066bbd3d59fbde3225e5a368c18b46b18 Merged-In: Ie3624e4066bbd3d59fbde3225e5a368c18b46b18
-rw-r--r--Android.bp17
-rw-r--r--BUILD12
-rw-r--r--protos/perfetto/common/trace_stats.proto1
-rw-r--r--protos/perfetto/config/perfetto_config.proto67
-rw-r--r--protos/perfetto/config/trace_config.proto67
-rw-r--r--protos/perfetto/trace/perfetto_trace.proto68
-rw-r--r--src/protozero/filtering/BUILD.gn21
-rw-r--r--src/protozero/filtering/message_filter.cc5
-rw-r--r--src/protozero/filtering/message_filter.h5
-rw-r--r--src/protozero/filtering/string_filter.cc143
-rw-r--r--src/protozero/filtering/string_filter.h71
-rw-r--r--src/protozero/filtering/string_filter_benchmark.cc130
-rw-r--r--src/protozero/filtering/string_filter_unittest.cc230
-rw-r--r--src/tracing/core/BUILD.gn1
-rw-r--r--src/tracing/core/tracing_service_impl.cc46
-rw-r--r--src/tracing/core/tracing_service_impl.h1
-rw-r--r--src/tracing/core/tracing_service_impl_unittest.cc68
-rw-r--r--test/data/example_android_trace_30s_atrace_strings.txt.sha2561
18 files changed, 951 insertions, 3 deletions
diff --git a/Android.bp b/Android.bp
index 632a25ea5..c067d8c64 100644
--- a/Android.bp
+++ b/Android.bp
@@ -567,6 +567,7 @@ cc_library_shared {
":perfetto_src_protozero_filtering_bytecode_common",
":perfetto_src_protozero_filtering_bytecode_parser",
":perfetto_src_protozero_filtering_message_filter",
+ ":perfetto_src_protozero_filtering_string_filter",
":perfetto_src_protozero_proto_ring_buffer",
":perfetto_src_protozero_protozero",
":perfetto_src_traced_probes_android_game_intervention_list_android_game_intervention_list",
@@ -801,6 +802,7 @@ cc_library_static {
":perfetto_src_protozero_filtering_bytecode_common",
":perfetto_src_protozero_filtering_bytecode_parser",
":perfetto_src_protozero_filtering_message_filter",
+ ":perfetto_src_protozero_filtering_string_filter",
":perfetto_src_protozero_protozero",
":perfetto_src_tracing_client_api_without_backends",
":perfetto_src_tracing_common",
@@ -1208,6 +1210,7 @@ cc_library_static {
":perfetto_src_protozero_filtering_bytecode_generator",
":perfetto_src_protozero_filtering_bytecode_parser",
":perfetto_src_protozero_filtering_message_filter",
+ ":perfetto_src_protozero_filtering_string_filter",
":perfetto_src_protozero_proto_ring_buffer",
":perfetto_src_protozero_protozero",
":perfetto_src_traced_probes_android_game_intervention_list_android_game_intervention_list",
@@ -1498,6 +1501,7 @@ cc_library_static {
":perfetto_src_protozero_filtering_bytecode_common",
":perfetto_src_protozero_filtering_bytecode_parser",
":perfetto_src_protozero_filtering_message_filter",
+ ":perfetto_src_protozero_filtering_string_filter",
":perfetto_src_protozero_proto_ring_buffer",
":perfetto_src_protozero_protozero",
":perfetto_src_traced_probes_android_game_intervention_list_android_game_intervention_list",
@@ -2038,6 +2042,7 @@ cc_test {
":perfetto_src_protozero_filtering_bytecode_generator",
":perfetto_src_protozero_filtering_bytecode_parser",
":perfetto_src_protozero_filtering_message_filter",
+ ":perfetto_src_protozero_filtering_string_filter",
":perfetto_src_protozero_proto_ring_buffer",
":perfetto_src_protozero_protozero",
":perfetto_src_shared_lib_shared_lib",
@@ -9096,6 +9101,14 @@ filegroup {
],
}
+// GN: //src/protozero/filtering:string_filter
+filegroup {
+ name: "perfetto_src_protozero_filtering_string_filter",
+ srcs: [
+ "src/protozero/filtering/string_filter.cc",
+ ],
+}
+
// GN: //src/protozero/filtering:unittests
filegroup {
name: "perfetto_src_protozero_filtering_unittests",
@@ -9105,6 +9118,7 @@ filegroup {
"src/protozero/filtering/filter_util_unittest.cc",
"src/protozero/filtering/message_filter_unittest.cc",
"src/protozero/filtering/message_tokenizer_unittest.cc",
+ "src/protozero/filtering/string_filter_unittest.cc",
],
}
@@ -12021,6 +12035,7 @@ cc_test {
":perfetto_src_protozero_filtering_bytecode_parser",
":perfetto_src_protozero_filtering_filter_util",
":perfetto_src_protozero_filtering_message_filter",
+ ":perfetto_src_protozero_filtering_string_filter",
":perfetto_src_protozero_filtering_unittests",
":perfetto_src_protozero_proto_ring_buffer",
":perfetto_src_protozero_protozero",
@@ -12457,6 +12472,7 @@ cc_library_static {
":perfetto_src_protozero_filtering_bytecode_common",
":perfetto_src_protozero_filtering_bytecode_parser",
":perfetto_src_protozero_filtering_message_filter",
+ ":perfetto_src_protozero_filtering_string_filter",
":perfetto_src_protozero_proto_ring_buffer",
":perfetto_src_protozero_protozero",
":perfetto_src_traced_probes_android_game_intervention_list_android_game_intervention_list",
@@ -13198,6 +13214,7 @@ cc_binary {
":perfetto_src_protozero_filtering_bytecode_common",
":perfetto_src_protozero_filtering_bytecode_parser",
":perfetto_src_protozero_filtering_message_filter",
+ ":perfetto_src_protozero_filtering_string_filter",
":perfetto_src_protozero_protozero",
":perfetto_src_traced_probes_ftrace_ftrace_procfs",
":perfetto_src_traced_probes_packages_list_packages_list_parser",
diff --git a/BUILD b/BUILD
index 195292f90..0e9b6ff72 100644
--- a/BUILD
+++ b/BUILD
@@ -306,6 +306,7 @@ perfetto_cc_binary(
":src_protozero_filtering_bytecode_parser",
":src_protozero_filtering_filter_util",
":src_protozero_filtering_message_filter",
+ ":src_protozero_filtering_string_filter",
"src/tools/proto_filter/proto_filter.cc",
],
deps = [
@@ -400,6 +401,7 @@ perfetto_cc_library(
":src_protozero_filtering_bytecode_common",
":src_protozero_filtering_bytecode_parser",
":src_protozero_filtering_message_filter",
+ ":src_protozero_filtering_string_filter",
":src_protozero_proto_ring_buffer",
":src_traced_probes_android_game_intervention_list_android_game_intervention_list",
":src_traced_probes_android_log_android_log",
@@ -1233,6 +1235,15 @@ perfetto_filegroup(
],
)
+# GN target: //src/protozero/filtering:string_filter
+perfetto_filegroup(
+ name = "src_protozero_filtering_string_filter",
+ srcs = [
+ "src/protozero/filtering/string_filter.cc",
+ "src/protozero/filtering/string_filter.h",
+ ],
+)
+
# GN target: //src/protozero:proto_ring_buffer
perfetto_filegroup(
name = "src_protozero_proto_ring_buffer",
@@ -4806,6 +4817,7 @@ perfetto_cc_library(
":src_protozero_filtering_bytecode_common",
":src_protozero_filtering_bytecode_parser",
":src_protozero_filtering_message_filter",
+ ":src_protozero_filtering_string_filter",
":src_tracing_client_api_without_backends",
":src_tracing_common",
":src_tracing_core_core",
diff --git a/protos/perfetto/common/trace_stats.proto b/protos/perfetto/common/trace_stats.proto
index 4e6d4555a..6e3788593 100644
--- a/protos/perfetto/common/trace_stats.proto
+++ b/protos/perfetto/common/trace_stats.proto
@@ -191,6 +191,7 @@ message TraceStats {
optional uint64 input_bytes = 2;
optional uint64 output_bytes = 3;
optional uint64 errors = 4;
+ optional uint64 time_taken_ns = 5;
}
optional FilterStats filter_stats = 11;
diff --git a/protos/perfetto/config/perfetto_config.proto b/protos/perfetto/config/perfetto_config.proto
index 14dac1010..9ac7402a9 100644
--- a/protos/perfetto/config/perfetto_config.proto
+++ b/protos/perfetto/config/perfetto_config.proto
@@ -3183,6 +3183,73 @@ message TraceConfig {
// The bytecode as implemented in Android U. Adds support for string
// filtering.
optional bytes bytecode_v2 = 2;
+
+ // =========================
+ // String filtering
+ // =========================
+
+ // The principles and terminology of string filtering is heavily inspired by
+ // iptables. A "rule" decide how strings should be filtered. Each rule
+ // contains a "policy" which indicates the algorithm to use for filtering.
+ // A "chain" is a list of rules which will be sequentially checked against
+ // each string.
+ //
+ // The first rule which applies to the string terminates filtering for that
+ // string. If no rules apply, the string is left unchanged.
+
+ // A policy specifies which algorithm should be used for filtering the
+ // string.
+ enum StringFilterPolicy {
+ SFP_UNSPECIFIED = 0;
+
+ // Tries to match the string field against |regex_pattern|. If it
+ // matches, all matching groups are "redacted" (i.e. replaced with a
+ // constant string) and filtering is terminated (i.e. no further rules are
+ // checked). If it doesn't match, the string is left unchanged and the
+ // next rule in chain is considered.
+ SFP_MATCH_REDACT_GROUPS = 1;
+
+ // Like |SFP_MATCH_REDACT_GROUPS| but tries to do some pre-work before
+ // checking the regex. Specifically, it tries to parse the string field as
+ // an atrace tracepoint and checks if the post-tgid field starts with
+ // |atrace_post_tgid_starts_with|. The regex matching is only performed if
+ // this check succeeds.
+ SFP_ATRACE_MATCH_REDACT_GROUPS = 2;
+
+ // Tries to match the string field against |regex_pattern|. If it
+ // matches, filtering is terminated (i.e. no further rules are checked).
+ // If it doesn't match, the string is left unchanged and the next rule in
+ // chain is considered.
+ SFP_MATCH_BREAK = 3;
+
+ // Like |SFP_MATCH_BREAK| but tries to do some pre-work before checking
+ // the regex. Specifically, it tries to parse the string field as an
+ // atrace tracepoint and checks if the post-tgid field starts with
+ // |atrace_post_tgid_starts_with|. The regex matching is only performed if
+ // this check succeeds.
+ SFP_ATRACE_MATCH_BREAK = 4;
+ }
+
+ // A rule specifies how strings should be filtered.
+ message StringFilterRule {
+ // The policy (i.e. algorithm) dictating how strings matching this rule
+ // should be handled.
+ optional StringFilterPolicy policy = 1;
+
+ // The regex pattern used to match against each string.
+ optional string regex_pattern = 2;
+
+ // The string which should appear after the tgid in atrace tracepoint
+ // strings.
+ optional string atrace_payload_starts_with = 3;
+ }
+
+ // A chain is a list of rules which string will be sequentially checked
+ // against.
+ message StringFilterChain {
+ repeated StringFilterRule rules = 1;
+ }
+ optional StringFilterChain string_filter_chain = 3;
}
// old field number for trace_filter
reserved 32;
diff --git a/protos/perfetto/config/trace_config.proto b/protos/perfetto/config/trace_config.proto
index 05d4520c9..d62b0c794 100644
--- a/protos/perfetto/config/trace_config.proto
+++ b/protos/perfetto/config/trace_config.proto
@@ -504,6 +504,73 @@ message TraceConfig {
// The bytecode as implemented in Android U. Adds support for string
// filtering.
optional bytes bytecode_v2 = 2;
+
+ // =========================
+ // String filtering
+ // =========================
+
+ // The principles and terminology of string filtering is heavily inspired by
+ // iptables. A "rule" decide how strings should be filtered. Each rule
+ // contains a "policy" which indicates the algorithm to use for filtering.
+ // A "chain" is a list of rules which will be sequentially checked against
+ // each string.
+ //
+ // The first rule which applies to the string terminates filtering for that
+ // string. If no rules apply, the string is left unchanged.
+
+ // A policy specifies which algorithm should be used for filtering the
+ // string.
+ enum StringFilterPolicy {
+ SFP_UNSPECIFIED = 0;
+
+ // Tries to match the string field against |regex_pattern|. If it
+ // matches, all matching groups are "redacted" (i.e. replaced with a
+ // constant string) and filtering is terminated (i.e. no further rules are
+ // checked). If it doesn't match, the string is left unchanged and the
+ // next rule in chain is considered.
+ SFP_MATCH_REDACT_GROUPS = 1;
+
+ // Like |SFP_MATCH_REDACT_GROUPS| but tries to do some pre-work before
+ // checking the regex. Specifically, it tries to parse the string field as
+ // an atrace tracepoint and checks if the post-tgid field starts with
+ // |atrace_post_tgid_starts_with|. The regex matching is only performed if
+ // this check succeeds.
+ SFP_ATRACE_MATCH_REDACT_GROUPS = 2;
+
+ // Tries to match the string field against |regex_pattern|. If it
+ // matches, filtering is terminated (i.e. no further rules are checked).
+ // If it doesn't match, the string is left unchanged and the next rule in
+ // chain is considered.
+ SFP_MATCH_BREAK = 3;
+
+ // Like |SFP_MATCH_BREAK| but tries to do some pre-work before checking
+ // the regex. Specifically, it tries to parse the string field as an
+ // atrace tracepoint and checks if the post-tgid field starts with
+ // |atrace_post_tgid_starts_with|. The regex matching is only performed if
+ // this check succeeds.
+ SFP_ATRACE_MATCH_BREAK = 4;
+ }
+
+ // A rule specifies how strings should be filtered.
+ message StringFilterRule {
+ // The policy (i.e. algorithm) dictating how strings matching this rule
+ // should be handled.
+ optional StringFilterPolicy policy = 1;
+
+ // The regex pattern used to match against each string.
+ optional string regex_pattern = 2;
+
+ // The string which should appear after the tgid in atrace tracepoint
+ // strings.
+ optional string atrace_payload_starts_with = 3;
+ }
+
+ // A chain is a list of rules which string will be sequentially checked
+ // against.
+ message StringFilterChain {
+ repeated StringFilterRule rules = 1;
+ }
+ optional StringFilterChain string_filter_chain = 3;
}
// old field number for trace_filter
reserved 32;
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index 4ed8d5a6c..c953e6aa8 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -3183,6 +3183,73 @@ message TraceConfig {
// The bytecode as implemented in Android U. Adds support for string
// filtering.
optional bytes bytecode_v2 = 2;
+
+ // =========================
+ // String filtering
+ // =========================
+
+ // The principles and terminology of string filtering is heavily inspired by
+ // iptables. A "rule" decide how strings should be filtered. Each rule
+ // contains a "policy" which indicates the algorithm to use for filtering.
+ // A "chain" is a list of rules which will be sequentially checked against
+ // each string.
+ //
+ // The first rule which applies to the string terminates filtering for that
+ // string. If no rules apply, the string is left unchanged.
+
+ // A policy specifies which algorithm should be used for filtering the
+ // string.
+ enum StringFilterPolicy {
+ SFP_UNSPECIFIED = 0;
+
+ // Tries to match the string field against |regex_pattern|. If it
+ // matches, all matching groups are "redacted" (i.e. replaced with a
+ // constant string) and filtering is terminated (i.e. no further rules are
+ // checked). If it doesn't match, the string is left unchanged and the
+ // next rule in chain is considered.
+ SFP_MATCH_REDACT_GROUPS = 1;
+
+ // Like |SFP_MATCH_REDACT_GROUPS| but tries to do some pre-work before
+ // checking the regex. Specifically, it tries to parse the string field as
+ // an atrace tracepoint and checks if the post-tgid field starts with
+ // |atrace_post_tgid_starts_with|. The regex matching is only performed if
+ // this check succeeds.
+ SFP_ATRACE_MATCH_REDACT_GROUPS = 2;
+
+ // Tries to match the string field against |regex_pattern|. If it
+ // matches, filtering is terminated (i.e. no further rules are checked).
+ // If it doesn't match, the string is left unchanged and the next rule in
+ // chain is considered.
+ SFP_MATCH_BREAK = 3;
+
+ // Like |SFP_MATCH_BREAK| but tries to do some pre-work before checking
+ // the regex. Specifically, it tries to parse the string field as an
+ // atrace tracepoint and checks if the post-tgid field starts with
+ // |atrace_post_tgid_starts_with|. The regex matching is only performed if
+ // this check succeeds.
+ SFP_ATRACE_MATCH_BREAK = 4;
+ }
+
+ // A rule specifies how strings should be filtered.
+ message StringFilterRule {
+ // The policy (i.e. algorithm) dictating how strings matching this rule
+ // should be handled.
+ optional StringFilterPolicy policy = 1;
+
+ // The regex pattern used to match against each string.
+ optional string regex_pattern = 2;
+
+ // The string which should appear after the tgid in atrace tracepoint
+ // strings.
+ optional string atrace_payload_starts_with = 3;
+ }
+
+ // A chain is a list of rules which string will be sequentially checked
+ // against.
+ message StringFilterChain {
+ repeated StringFilterRule rules = 1;
+ }
+ optional StringFilterChain string_filter_chain = 3;
}
// old field number for trace_filter
reserved 32;
@@ -3426,6 +3493,7 @@ message TraceStats {
optional uint64 input_bytes = 2;
optional uint64 output_bytes = 3;
optional uint64 errors = 4;
+ optional uint64 time_taken_ns = 5;
}
optional FilterStats filter_stats = 11;
diff --git a/src/protozero/filtering/BUILD.gn b/src/protozero/filtering/BUILD.gn
index 1732890af..3464635cc 100644
--- a/src/protozero/filtering/BUILD.gn
+++ b/src/protozero/filtering/BUILD.gn
@@ -25,6 +25,7 @@ source_set("message_filter") {
]
deps = [
":bytecode_parser",
+ ":string_filter",
"..:protozero",
"../../../gn:default_deps",
"../../base",
@@ -62,6 +63,18 @@ source_set("bytecode_generator") {
]
}
+source_set("string_filter") {
+ sources = [
+ "string_filter.cc",
+ "string_filter.h",
+ ]
+ deps = [
+ "..:protozero",
+ "../../../gn:default_deps",
+ "../../base",
+ ]
+}
+
source_set("filter_util") {
testonly = true
sources = [
@@ -85,6 +98,7 @@ perfetto_unittest_source_set("unittests") {
":bytecode_generator",
":bytecode_parser",
":message_filter",
+ ":string_filter",
"..:protozero",
"../../../gn:default_deps",
"../../../gn:gtest_and_gmock",
@@ -96,6 +110,7 @@ perfetto_unittest_source_set("unittests") {
"filter_bytecode_generator_unittest.cc",
"filter_bytecode_parser_unittest.cc",
"message_tokenizer_unittest.cc",
+ "string_filter_unittest.cc",
]
# On chromium component build we cannot have a test target depening boh on
@@ -115,12 +130,16 @@ if (enable_perfetto_benchmarks) {
testonly = true
deps = [
":message_filter",
+ ":string_filter",
"../../../gn:benchmark",
"../../../gn:default_deps",
"../../base",
"../../base:test_support",
]
- sources = [ "message_filter_benchmark.cc" ]
+ sources = [
+ "message_filter_benchmark.cc",
+ "string_filter_benchmark.cc",
+ ]
}
}
diff --git a/src/protozero/filtering/message_filter.cc b/src/protozero/filtering/message_filter.cc
index ff283f869..9808b0cf8 100644
--- a/src/protozero/filtering/message_filter.cc
+++ b/src/protozero/filtering/message_filter.cc
@@ -18,6 +18,7 @@
#include "perfetto/base/logging.h"
#include "perfetto/protozero/proto_utils.h"
+#include "src/protozero/filtering/string_filter.h"
namespace protozero {
@@ -162,7 +163,9 @@ void MessageFilter::FilterOneByte(uint8_t octet) {
} else if (state->action == StackState::kFilterString) {
*(out_++) = octet;
if (state->eat_next_bytes == 0) {
- // TODO(lalitm): do the filtering using |filter_string_ptr|.
+ string_filter_.MaybeFilter(
+ reinterpret_cast<char*>(state->filter_string_ptr),
+ static_cast<size_t>(out_ - state->filter_string_ptr));
}
}
} else {
diff --git a/src/protozero/filtering/message_filter.h b/src/protozero/filtering/message_filter.h
index e67467b6b..8a3c05670 100644
--- a/src/protozero/filtering/message_filter.h
+++ b/src/protozero/filtering/message_filter.h
@@ -25,6 +25,7 @@
#include "src/protozero/filtering/filter_bytecode_parser.h"
#include "src/protozero/filtering/message_tokenizer.h"
+#include "src/protozero/filtering/string_filter.h"
namespace protozero {
@@ -117,6 +118,9 @@ class MessageFilter {
// Exposed only for DCHECKS in TracingServiceImpl.
uint32_t root_msg_index() { return root_msg_index_; }
+ // Retuns the helper class used to perform string filtering.
+ StringFilter& string_filter() { return string_filter_; }
+
private:
// This is called by FilterMessageFragments().
// Inlining allows the compiler turn the per-byte call/return into a for loop,
@@ -206,6 +210,7 @@ class MessageFilter {
FilterBytecodeParser filter_;
MessageTokenizer tokenizer_;
+ StringFilter string_filter_;
std::vector<StackState> stack_;
bool error_ = false;
diff --git a/src/protozero/filtering/string_filter.cc b/src/protozero/filtering/string_filter.cc
new file mode 100644
index 000000000..32542076d
--- /dev/null
+++ b/src/protozero/filtering/string_filter.cc
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2023 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 "src/protozero/filtering/string_filter.h"
+
+#include <cstring>
+#include <regex>
+#include <string_view>
+
+#include "perfetto/base/compiler.h"
+#include "perfetto/base/logging.h"
+#include "perfetto/ext/base/string_view.h"
+#include "perfetto/public/compiler.h"
+
+namespace protozero {
+namespace {
+
+using Matches = std::match_results<char*>;
+
+static constexpr std::string_view kRedacted = "P60REDACTED";
+static constexpr char kRedactedDash = '-';
+
+// Returns a pointer to the first character after the tgid pipe character in
+// the atrace string given by [ptr, end). Returns null if no such character
+// exists.
+//
+// Examples:
+// E|1024 -> nullptr
+// foobarbaz -> nullptr
+// B|1024|x -> pointer to x
+const char* FindAtracePayloadPtr(const char* ptr, const char* end) {
+ // Don't even bother checking any strings which are so short that they could
+ // not contain a post-tgid section. This filters out strings like "E|" which
+ // emitted by Bionic.
+ //
+ // Also filter out any other strings starting with "E" as they never contain
+ // anything past the tgid: this removes >half of the strings for ~zero cost.
+ static constexpr size_t kEarliestSecondPipeIndex = 2;
+ const char* search_start = ptr + kEarliestSecondPipeIndex;
+ if (search_start >= end || *ptr == 'E') {
+ return nullptr;
+ }
+
+ // We skipped past the first '|' character by starting at the character at
+ // index 2. Just find the next pipe character (i.e. the one after tgid) using
+ // memchr.
+ const char* pipe = static_cast<const char*>(
+ memchr(search_start, '|', size_t(end - search_start)));
+ return pipe ? pipe + 1 : nullptr;
+}
+
+bool StartsWith(const char* ptr,
+ const char* end,
+ const std::string& starts_with) {
+ // Verify that the atrace string has enough characters to match against all
+ // the characters in the "starts with" string. If it does, memcmp to check if
+ // all the characters match and return true if they do.
+ return ptr + starts_with.size() <= end &&
+ memcmp(ptr, starts_with.data(), starts_with.size()) == 0;
+}
+
+void RedactMatches(const Matches& matches) {
+ // Go through every group in the matches.
+ for (size_t i = 1; i < matches.size(); ++i) {
+ const auto& match = matches[i];
+ PERFETTO_CHECK(match.second >= match.first);
+
+ // Overwrite the match with characters from |kRedacted|. If match is
+ // smaller, we will not use all of |kRedacted| but that's fine (i.e. we
+ // will overwrite with a truncated |kRedacted|).
+ size_t match_len = static_cast<size_t>(match.second - match.first);
+ size_t redacted_len = std::min(match_len, kRedacted.size());
+ memcpy(match.first, kRedacted.data(), redacted_len);
+
+ // Overwrite any characters after |kRedacted| with |kRedactedDash|.
+ memset(match.first + redacted_len, kRedactedDash, match_len - redacted_len);
+ }
+}
+
+} // namespace
+
+void StringFilter::AddRule(Policy policy,
+ std::string_view pattern_str,
+ std::string atrace_payload_starts_with) {
+ rules_.emplace_back(StringFilter::Rule{
+ policy,
+ std::regex(pattern_str.begin(), pattern_str.end(),
+ std::regex::ECMAScript | std::regex_constants::optimize),
+ std::move(atrace_payload_starts_with)});
+}
+
+bool StringFilter::MaybeFilterInternal(char* ptr, size_t len) {
+ std::match_results<char*> matches;
+ bool atrace_find_tried = false;
+ const char* atrace_payload_ptr = nullptr;
+ for (const Rule& rule : rules_) {
+ switch (rule.policy) {
+ case Policy::kMatchRedactGroups:
+ case Policy::kMatchBreak:
+ if (std::regex_match(ptr, ptr + len, matches, rule.pattern)) {
+ if (rule.policy == Policy::kMatchBreak) {
+ return false;
+ }
+ RedactMatches(matches);
+ return true;
+ }
+ break;
+ case Policy::kAtraceMatchRedactGroups:
+ case Policy::kAtraceMatchBreak:
+ atrace_payload_ptr = atrace_find_tried
+ ? atrace_payload_ptr
+ : FindAtracePayloadPtr(ptr, ptr + len);
+ atrace_find_tried = true;
+ if (atrace_payload_ptr &&
+ StartsWith(atrace_payload_ptr, ptr + len,
+ rule.atrace_payload_starts_with) &&
+ std::regex_match(ptr, ptr + len, matches, rule.pattern)) {
+ if (rule.policy == Policy::kAtraceMatchBreak) {
+ return false;
+ }
+ RedactMatches(matches);
+ return true;
+ }
+ break;
+ }
+ }
+ return false;
+}
+
+} // namespace protozero
diff --git a/src/protozero/filtering/string_filter.h b/src/protozero/filtering/string_filter.h
new file mode 100644
index 000000000..eb167bdf8
--- /dev/null
+++ b/src/protozero/filtering/string_filter.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#ifndef SRC_PROTOZERO_FILTERING_STRING_FILTER_H_
+#define SRC_PROTOZERO_FILTERING_STRING_FILTER_H_
+
+#include <regex>
+
+namespace protozero {
+
+// Performs filtering of strings in an "iptables" style. See the comments in
+// |TraceConfig.TraceFilter| for information on how this class works.
+class StringFilter {
+ public:
+ enum class Policy {
+ kMatchRedactGroups = 1,
+ kAtraceMatchRedactGroups = 2,
+ kMatchBreak = 3,
+ kAtraceMatchBreak = 4,
+ };
+
+ StringFilter() = default;
+
+ // Adds a new rule for filtering strings.
+ void AddRule(Policy policy,
+ std::string_view pattern,
+ std::string atrace_payload_starts_with);
+
+ // Tries to filter the given string. Returns true if the string was modified
+ // in any way, false otherwise.
+ bool MaybeFilter(char* ptr, size_t len) {
+ if (len == 0 || rules_.empty()) {
+ return false;
+ }
+ return MaybeFilterInternal(ptr, len);
+ }
+
+ private:
+ struct Rule {
+ Policy policy;
+ std::regex pattern;
+ std::string atrace_payload_starts_with;
+ };
+
+ StringFilter(StringFilter&&) noexcept = delete;
+ StringFilter& operator=(StringFilter&&) noexcept = delete;
+
+ bool MaybeFilterInternal(char* ptr, size_t len);
+
+ StringFilter(const StringFilter&) = delete;
+ StringFilter& operator=(const StringFilter&) = delete;
+
+ std::vector<Rule> rules_;
+};
+
+} // namespace protozero
+
+#endif // SRC_PROTOZERO_FILTERING_STRING_FILTER_H_
diff --git a/src/protozero/filtering/string_filter_benchmark.cc b/src/protozero/filtering/string_filter_benchmark.cc
new file mode 100644
index 000000000..867f69574
--- /dev/null
+++ b/src/protozero/filtering/string_filter_benchmark.cc
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2023 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 <benchmark/benchmark.h>
+#include <cstdint>
+#include <vector>
+
+#include "perfetto/base/logging.h"
+#include "perfetto/ext/base/hash.h"
+#include "perfetto/ext/base/scoped_file.h"
+#include "src/base/test/utils.h"
+#include "src/protozero/filtering/string_filter.h"
+
+namespace {
+
+using Policy = protozero::StringFilter::Policy;
+
+std::vector<std::pair<size_t, size_t>> LoadTraceStrings(
+ benchmark::State& state,
+ std::vector<char>& storage) {
+ storage.clear();
+
+ std::vector<std::pair<size_t, size_t>> strs;
+ std::string path = perfetto::base::GetTestDataPath(
+ "test/data/example_android_trace_30s_atrace_strings.txt");
+ perfetto::base::ScopedFstream f(fopen(path.c_str(), "re"));
+ if (!f) {
+ state.SkipWithError("Strings does not exist");
+ return {};
+ }
+ char line[4096];
+ while (fgets(line, sizeof(line), *f)) {
+ size_t len = strlen(line);
+ size_t pos = storage.size();
+ storage.insert(storage.end(), line, line + len);
+ strs.push_back(std::make_pair(pos, len));
+ }
+ return strs;
+}
+
+void Benchmark(benchmark::State& state,
+ Policy policy,
+ const char* regex,
+ const char* atrace) {
+ protozero::StringFilter rewriter;
+ for (int64_t i = 0; i < state.range(0); ++i) {
+ rewriter.AddRule(policy, regex, atrace);
+ }
+
+ std::vector<char> storage;
+ auto strs = LoadTraceStrings(state, storage);
+ for (auto _ : state) {
+ uint32_t match = 0;
+ for (auto& str : strs) {
+ match += rewriter.MaybeFilter(storage.data() + str.first, str.second);
+ }
+ benchmark::DoNotOptimize(match);
+ }
+ state.counters["time/string"] =
+ benchmark::Counter(static_cast<double>(strs.size()),
+ benchmark::Counter::kIsIterationInvariantRate |
+ benchmark::Counter::kInvert);
+}
+
+} // namespace
+
+static void BM_ProtozeroStringRewriterRedactMissing(benchmark::State& state) {
+ Benchmark(state, Policy::kMatchRedactGroups,
+ R"(S\|[^|]+\|\*job\*\/.*\/.*\/(.*)\n)", "");
+}
+BENCHMARK(BM_ProtozeroStringRewriterRedactMissing)
+ ->Unit(benchmark::kMillisecond)
+ ->Arg(10);
+
+static void BM_ProtozeroStringRewriterAtraceRedactMissing(
+ benchmark::State& state) {
+ Benchmark(state, Policy::kAtraceMatchRedactGroups,
+ R"(S\|[^|]+\|\*job\*\/.*\/.*\/(.*)\n)", "*job*");
+}
+BENCHMARK(BM_ProtozeroStringRewriterAtraceRedactMissing)
+ ->Unit(benchmark::kMillisecond)
+ ->Arg(10);
+
+static void BM_ProtozeroStringRewriterRedactRare(benchmark::State& state) {
+ Benchmark(state, Policy::kMatchRedactGroups,
+ R"(B\|[^|]+\|VerifyClass (.*)\n)", "");
+}
+BENCHMARK(BM_ProtozeroStringRewriterRedactRare)
+ ->Unit(benchmark::kMillisecond)
+ ->Arg(10);
+
+static void BM_ProtozeroStringRewriterAtraceRedactRare(
+ benchmark::State& state) {
+ Benchmark(state, Policy::kAtraceMatchRedactGroups,
+ R"(B\|[^|]+\|VerifyClass (.*)\n)", "VerifyClass");
+}
+BENCHMARK(BM_ProtozeroStringRewriterAtraceRedactRare)
+ ->Unit(benchmark::kMillisecond)
+ ->Arg(10);
+
+static void BM_ProtozeroStringRewriterRedactCommon(benchmark::State& state) {
+ Benchmark(state, Policy::kMatchRedactGroups,
+ R"(B\|[^|]+\|Lock contention on a monitor lock (.*)\n)", "");
+}
+BENCHMARK(BM_ProtozeroStringRewriterRedactCommon)
+ ->Unit(benchmark::kMillisecond)
+ ->Arg(10);
+
+static void BM_ProtozeroStringRewriterAtraceRedactCommon(
+ benchmark::State& state) {
+ Benchmark(state, Policy::kAtraceMatchRedactGroups,
+ R"(B\|[^|]+\|Lock contention on a monitor lock (.*)\n)",
+ "Lock contention on a monitor lock");
+}
+BENCHMARK(BM_ProtozeroStringRewriterAtraceRedactCommon)
+ ->Unit(benchmark::kMillisecond)
+ ->Arg(10);
diff --git a/src/protozero/filtering/string_filter_unittest.cc b/src/protozero/filtering/string_filter_unittest.cc
new file mode 100644
index 000000000..4e42c0f63
--- /dev/null
+++ b/src/protozero/filtering/string_filter_unittest.cc
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2021 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 "src/protozero/filtering/string_filter.h"
+
+#include "protos/perfetto/trace/trace_packet.pb.h"
+#include "test/gtest_and_gmock.h"
+
+namespace protozero {
+namespace {
+
+TEST(StringFilterTest, RegexRedaction) {
+ StringFilter filter;
+ filter.AddRule(StringFilter::Policy::kMatchRedactGroups,
+ R"(B\|\d+\|foo (.*))", "");
+
+ std::string res = "B|1234|foo 1234 bar baz";
+ ASSERT_TRUE(filter.MaybeFilter(res.data(), res.size()));
+ ASSERT_EQ(res, "B|1234|foo P60REDACTED-");
+}
+
+TEST(StringFilterTest, RegexRedactionShort) {
+ StringFilter filter;
+ filter.AddRule(StringFilter::Policy::kMatchRedactGroups,
+ R"(B\|\d+\|foo (.*))", "");
+
+ std::string res = "B|1234|foo 1234";
+ ASSERT_TRUE(filter.MaybeFilter(res.data(), res.size()));
+ ASSERT_EQ(res, "B|1234|foo P60R");
+}
+
+TEST(StringFilterTest, RegexRedactionMismatch) {
+ StringFilter filter;
+ filter.AddRule(StringFilter::Policy::kMatchRedactGroups,
+ R"(B\|\d+\|foo (.*))", "");
+
+ std::string res = "B|1234|fooo";
+ ASSERT_FALSE(filter.MaybeFilter(res.data(), res.size()));
+ ASSERT_EQ(res, "B|1234|fooo");
+}
+
+TEST(StringFilterTest, AtraceRegexRedaction) {
+ StringFilter filter;
+ filter.AddRule(StringFilter::Policy::kAtraceMatchRedactGroups,
+ R"(B\|\d+\|foo (.*))", "foo");
+
+ std::string res = "B|1234|foo 1234 bar baz";
+ ASSERT_TRUE(filter.MaybeFilter(res.data(), res.size()));
+ ASSERT_EQ(res, "B|1234|foo P60REDACTED-");
+}
+
+TEST(StringFilterTest, AtraceRegexRedactionZero) {
+ StringFilter filter;
+ filter.AddRule(StringFilter::Policy::kAtraceMatchRedactGroups,
+ R"(B\|\d+\|(.*))", "");
+
+ std::string res = "B|1234|";
+ ASSERT_TRUE(filter.MaybeFilter(res.data(), res.size()));
+ ASSERT_EQ(res, "B|1234|");
+}
+
+TEST(StringFilterTest, AtraceRegexRedactionExact) {
+ StringFilter filter;
+ filter.AddRule(StringFilter::Policy::kAtraceMatchRedactGroups,
+ R"(B\|\d+\|foo (.*))", "foo 1234 bar baz");
+
+ std::string res = "B|1234|foo 1234 bar baz";
+ ASSERT_TRUE(filter.MaybeFilter(res.data(), res.size()));
+ ASSERT_EQ(res, "B|1234|foo P60REDACTED-");
+}
+
+TEST(StringFilterTest, AtraceRegexRedactionEmpty) {
+ StringFilter filter;
+ filter.AddRule(StringFilter::Policy::kAtraceMatchRedactGroups,
+ R"(B\|\d+\|foo (.*))", "");
+
+ std::string res = "B|1234|foo 1234";
+ ASSERT_TRUE(filter.MaybeFilter(res.data(), res.size()));
+ ASSERT_EQ(res, "B|1234|foo P60R");
+}
+
+TEST(StringFilterTest, AtraceRegexRedactionTooLong) {
+ StringFilter filter;
+ filter.AddRule(StringFilter::Policy::kAtraceMatchRedactGroups,
+ R"(B\|\d+\|foo (.*))", "foo 1234 bar baz ");
+
+ std::string res = "B|1234|foo 1234 bar baz";
+ ASSERT_FALSE(filter.MaybeFilter(res.data(), res.size()));
+ ASSERT_EQ(res, "B|1234|foo 1234 bar baz");
+}
+
+TEST(StringFilterTest, AtraceRegexRedactionMismatch) {
+ StringFilter filter;
+ filter.AddRule(StringFilter::Policy::kAtraceMatchRedactGroups,
+ R"(B\|\d+\|foo (.*))", "foo 2");
+
+ std::string res = "B|1234|foo 1234 bar baz";
+ ASSERT_FALSE(filter.MaybeFilter(res.data(), res.size()));
+ ASSERT_EQ(res, "B|1234|foo 1234 bar baz");
+}
+
+TEST(StringFilterTest, AtraceRegexRedactionEnd) {
+ StringFilter filter;
+ filter.AddRule(StringFilter::Policy::kAtraceMatchRedactGroups, R"(E\|\d+)",
+ "");
+
+ std::string res = "E|1234";
+ ASSERT_FALSE(filter.MaybeFilter(res.data(), res.size()));
+ ASSERT_EQ(res, "E|1234");
+}
+
+TEST(StringFilterTest, AtraceRegexRedactionNotAtrace) {
+ StringFilter filter;
+ filter.AddRule(StringFilter::Policy::kAtraceMatchRedactGroups, R"(B\|\d+)",
+ "");
+
+ std::string res = "B|1";
+ ASSERT_FALSE(filter.MaybeFilter(res.data(), res.size()));
+ ASSERT_EQ(res, "B|1");
+}
+
+TEST(StringFilterTest, AtraceRegexRedactionMultiple) {
+ StringFilter filter;
+ filter.AddRule(StringFilter::Policy::kAtraceMatchRedactGroups,
+ R"(B\|\d+\|foo (.*))", "foo");
+ filter.AddRule(StringFilter::Policy::kAtraceMatchRedactGroups,
+ R"(B\|\d+\|(.*))", "bar");
+ filter.AddRule(StringFilter::Policy::kAtraceMatchRedactGroups,
+ R"(B\|\d+\|R(.*))", "R");
+
+ std::string res = "B|1|bar 1234567";
+ ASSERT_TRUE(filter.MaybeFilter(res.data(), res.size()));
+ ASSERT_EQ(res, "B|1|P60REDACTED");
+}
+
+TEST(StringFilterTest, Mixed) {
+ StringFilter filter;
+ filter.AddRule(StringFilter::Policy::kAtraceMatchRedactGroups,
+ R"(B\|\d+\|foo (.*))", "foo");
+ filter.AddRule(StringFilter::Policy::kMatchRedactGroups, R"(B\|\d+\|(.*))",
+ "");
+
+ std::string res = "B|1234|foo";
+ ASSERT_TRUE(filter.MaybeFilter(res.data(), res.size()));
+ ASSERT_EQ(res, "B|1234|P60");
+}
+
+TEST(StringFilterTest, Break) {
+ StringFilter filter;
+ filter.AddRule(StringFilter::Policy::kMatchBreak, R"(B\|\d+)", "");
+ filter.AddRule(StringFilter::Policy::kMatchRedactGroups, R"(B\|(\d+))", "");
+
+ std::string res = "B|1234";
+ ASSERT_FALSE(filter.MaybeFilter(res.data(), res.size()));
+ ASSERT_EQ(res, "B|1234");
+}
+
+TEST(StringFilterTest, AtraceBreak) {
+ StringFilter filter;
+ filter.AddRule(StringFilter::Policy::kAtraceMatchBreak, R"(B\|\d+|foo .*)",
+ "foo");
+ filter.AddRule(StringFilter::Policy::kAtraceMatchRedactGroups,
+ R"(B\|(\d+)|foo (.*))", "foo");
+
+ std::string res = "B|1234|foo 1234";
+ ASSERT_FALSE(filter.MaybeFilter(res.data(), res.size()));
+ ASSERT_EQ(res, "B|1234|foo 1234");
+}
+
+TEST(StringFilterTest, RegexRedactionNonUtf) {
+ StringFilter filter;
+ filter.AddRule(StringFilter::Policy::kMatchRedactGroups,
+ R"(B\|\d+\|foo (.*))", "");
+
+ std::string bad = std::string(1, char(0xff));
+ std::string bad_copy = bad;
+
+ ASSERT_FALSE(filter.MaybeFilter(bad.data(), bad.size()));
+ ASSERT_EQ(bad, bad_copy);
+
+ perfetto::protos::TracePacket packet;
+ packet.mutable_perfetto_metatrace()->set_counter_id(0);
+ *packet.mutable_perfetto_metatrace()->mutable_counter_name() = "foo";
+ packet.mutable_perfetto_metatrace()->set_counter_value(100);
+
+ std::string metatrace = packet.SerializeAsString();
+ std::string metatrace_copy = metatrace;
+
+ ASSERT_FALSE(filter.MaybeFilter(metatrace.data(), metatrace.size()));
+ ASSERT_EQ(metatrace, metatrace_copy);
+}
+
+TEST(StringFilterTest, AtraceRedactionNonUtf) {
+ StringFilter filter;
+ filter.AddRule(StringFilter::Policy::kAtraceMatchRedactGroups,
+ R"(B\|\d+\|foo (.*))", "");
+
+ std::string bad = std::string(1, char(0xff));
+ std::string bad_copy = bad;
+
+ ASSERT_FALSE(filter.MaybeFilter(bad.data(), bad.size()));
+ ASSERT_EQ(bad, bad_copy);
+
+ perfetto::protos::TracePacket packet;
+ packet.mutable_perfetto_metatrace()->set_counter_id(0);
+ *packet.mutable_perfetto_metatrace()->mutable_counter_name() = "foo";
+ packet.mutable_perfetto_metatrace()->set_counter_value(100);
+
+ std::string metatrace = packet.SerializeAsString();
+ std::string metatrace_copy = metatrace;
+
+ ASSERT_FALSE(filter.MaybeFilter(metatrace.data(), metatrace.size()));
+ ASSERT_EQ(metatrace, metatrace_copy);
+}
+
+} // namespace
+} // namespace protozero
diff --git a/src/tracing/core/BUILD.gn b/src/tracing/core/BUILD.gn
index 3fe90eae1..bdd4f9371 100644
--- a/src/tracing/core/BUILD.gn
+++ b/src/tracing/core/BUILD.gn
@@ -64,6 +64,7 @@ source_set("service") {
"../../base",
"../../base:version",
"../../protozero/filtering:message_filter",
+ "../../protozero/filtering:string_filter",
]
sources = [
"metatrace_writer.cc",
diff --git a/src/tracing/core/tracing_service_impl.cc b/src/tracing/core/tracing_service_impl.cc
index ecb9db781..ca6837b56 100644
--- a/src/tracing/core/tracing_service_impl.cc
+++ b/src/tracing/core/tracing_service_impl.cc
@@ -21,8 +21,10 @@
#include <string.h>
#include <cinttypes>
+#include <optional>
#include <regex>
#include <unordered_set>
+#include "perfetto/base/time.h"
#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) && \
!PERFETTO_BUILDFLAG(PERFETTO_OS_NACL)
@@ -73,6 +75,7 @@
#include "perfetto/tracing/core/tracing_service_state.h"
#include "src/android_stats/statsd_logging_helper.h"
#include "src/protozero/filtering/message_filter.h"
+#include "src/protozero/filtering/string_filter.h"
#include "src/tracing/core/packet_stream_validator.h"
#include "src/tracing/core/shared_memory_arbiter_impl.h"
#include "src/tracing/core/trace_buffer.h"
@@ -296,6 +299,24 @@ void AppendOwnedSlicesToPacket(std::unique_ptr<uint8_t[]> data,
}
}
+using TraceFilter = protos::gen::TraceConfig::TraceFilter;
+std::optional<protozero::StringFilter::Policy> ConvertPolicy(
+ TraceFilter::StringFilterPolicy policy) {
+ switch (policy) {
+ case TraceFilter::SFP_UNSPECIFIED:
+ return std::nullopt;
+ case TraceFilter::SFP_MATCH_REDACT_GROUPS:
+ return protozero::StringFilter::Policy::kMatchRedactGroups;
+ case TraceFilter::SFP_ATRACE_MATCH_REDACT_GROUPS:
+ return protozero::StringFilter::Policy::kAtraceMatchRedactGroups;
+ case TraceFilter::SFP_MATCH_BREAK:
+ return protozero::StringFilter::Policy::kMatchBreak;
+ case TraceFilter::SFP_ATRACE_MATCH_BREAK:
+ return protozero::StringFilter::Policy::kAtraceMatchBreak;
+ }
+ return std::nullopt;
+}
+
} // namespace
// static
@@ -789,13 +810,31 @@ base::Status TracingServiceImpl::EnableTracing(ConsumerEndpointImpl* consumer,
std::unique_ptr<protozero::MessageFilter> trace_filter;
if (cfg.has_trace_filter()) {
const auto& filt = cfg.trace_filter();
- const std::string& bytecode = filt.bytecode();
trace_filter.reset(new protozero::MessageFilter());
+
+ protozero::StringFilter& string_filter = trace_filter->string_filter();
+ for (const auto& rule : filt.string_filter_chain().rules()) {
+ auto opt_policy = ConvertPolicy(rule.policy());
+ if (!opt_policy.has_value()) {
+ MaybeLogUploadEvent(
+ cfg, uuid, PerfettoStatsdAtom::kTracedEnableTracingInvalidFilter);
+ return PERFETTO_SVC_ERR(
+ "Trace filter has invalid string filtering rules, aborting");
+ }
+ string_filter.AddRule(*opt_policy, rule.regex_pattern(),
+ rule.atrace_payload_starts_with());
+ }
+
+ const std::string& bytecode_v1 = filt.bytecode();
+ const std::string& bytecode_v2 = filt.bytecode_v2();
+ const std::string& bytecode =
+ bytecode_v2.empty() ? bytecode_v1 : bytecode_v2;
if (!trace_filter->LoadFilterBytecode(bytecode.data(), bytecode.size())) {
MaybeLogUploadEvent(
cfg, uuid, PerfettoStatsdAtom::kTracedEnableTracingInvalidFilter);
return PERFETTO_SVC_ERR("Trace filter bytecode invalid, aborting");
}
+
// The filter is created using perfetto.protos.Trace as root message
// (because that makes it possible to play around with the `proto_filter`
// tool on actual traces). Here in the service, however, we deal with
@@ -2366,6 +2405,7 @@ void TracingServiceImpl::MaybeFilterPackets(TracingSession* tracing_session,
// by the earlier call to SetFilterRoot() in EnableTracing().
PERFETTO_DCHECK(trace_filter.root_msg_index() != 0);
std::vector<protozero::MessageFilter::InputSlice> filter_input;
+ auto start = base::GetWallTimeNs();
for (TracePacket& packet : *packets) {
const auto& packet_slices = packet.slices();
filter_input.clear();
@@ -2390,6 +2430,9 @@ void TracingServiceImpl::MaybeFilterPackets(TracingSession* tracing_session,
filtered_packet.size, kMaxTracePacketSliceSize,
&packet);
}
+ auto end = base::GetWallTimeNs();
+ tracing_session->filter_time_taken_ns +=
+ static_cast<uint64_t>((end - start).count());
}
void TracingServiceImpl::MaybeCompressPackets(
@@ -3310,6 +3353,7 @@ TraceStats TracingServiceImpl::GetTraceStats(TracingSession* tracing_session) {
filt_stats->set_input_bytes(tracing_session->filter_input_bytes);
filt_stats->set_output_bytes(tracing_session->filter_output_bytes);
filt_stats->set_errors(tracing_session->filter_errors);
+ filt_stats->set_time_taken_ns(tracing_session->filter_time_taken_ns);
}
for (BufferID buf_id : tracing_session->buffers_index) {
diff --git a/src/tracing/core/tracing_service_impl.h b/src/tracing/core/tracing_service_impl.h
index 8d067fd78..1a146c943 100644
--- a/src/tracing/core/tracing_service_impl.h
+++ b/src/tracing/core/tracing_service_impl.h
@@ -653,6 +653,7 @@ class TracingServiceImpl : public TracingService {
uint64_t filter_input_bytes = 0;
uint64_t filter_output_bytes = 0;
uint64_t filter_errors = 0;
+ uint64_t filter_time_taken_ns = 0;
// A randomly generated trace identifier. Note that this does NOT always
// match the requested TraceConfig.trace_uuid_msb/lsb. Spcifically, it does
diff --git a/src/tracing/core/tracing_service_impl_unittest.cc b/src/tracing/core/tracing_service_impl_unittest.cc
index ff2ad31fc..3a528c48f 100644
--- a/src/tracing/core/tracing_service_impl_unittest.cc
+++ b/src/tracing/core/tracing_service_impl_unittest.cc
@@ -4613,4 +4613,72 @@ TEST_F(TracingServiceImplTest, InvalidBufferSizes) {
EXPECT_THAT(error, HasSubstr("Invalid buffer sizes"));
}
+TEST_F(TracingServiceImplTest, StringFiltering) {
+ std::unique_ptr<MockConsumer> consumer = CreateMockConsumer();
+ consumer->Connect(svc.get());
+
+ std::unique_ptr<MockProducer> producer = CreateMockProducer();
+ producer->Connect(svc.get(), "mock_producer");
+
+ producer->RegisterDataSource("ds_1");
+
+ TraceConfig trace_config;
+ trace_config.add_buffers()->set_size_kb(32); // Buf 0.
+ auto* ds_cfg = trace_config.add_data_sources()->mutable_config();
+ ds_cfg->set_name("ds_1");
+ ds_cfg->set_target_buffer(0);
+
+ protozero::FilterBytecodeGenerator filt;
+ // Message 0: root Trace proto.
+ filt.AddNestedField(1 /* root trace.packet*/, 1);
+ filt.EndMessage();
+ // Message 1: TracePacket proto. Allow only the `for_testing` sub-field.
+ filt.AddNestedField(protos::pbzero::TracePacket::kForTestingFieldNumber, 2);
+ filt.EndMessage();
+ // Message 2: TestEvent proto. Allow only the `str` sub-field as a striong.
+ filt.AddFilterStringField(protos::pbzero::TestEvent::kStrFieldNumber);
+ filt.EndMessage();
+ trace_config.mutable_trace_filter()->set_bytecode_v2(filt.Serialize());
+
+ auto* chain =
+ trace_config.mutable_trace_filter()->mutable_string_filter_chain();
+ auto* rule = chain->add_rules();
+ rule->set_policy(
+ protos::gen::TraceConfig::TraceFilter::SFP_ATRACE_MATCH_REDACT_GROUPS);
+ rule->set_atrace_payload_starts_with("payload1");
+ rule->set_regex_pattern(R"(B\|\d+\|pay(lo)ad1(\d*))");
+
+ consumer->EnableTracing(trace_config);
+ producer->WaitForTracingSetup();
+
+ producer->WaitForDataSourceSetup("ds_1");
+ producer->WaitForDataSourceStart("ds_1");
+
+ std::unique_ptr<TraceWriter> writer = producer->CreateTraceWriter("ds_1");
+ static constexpr size_t kNumTestPackets = 20;
+ for (size_t i = 0; i < kNumTestPackets; i++) {
+ auto tp = writer->NewTracePacket();
+ std::string payload("B|1023|payload" + std::to_string(i));
+ tp->set_for_testing()->set_str(payload.c_str(), payload.size());
+ }
+
+ auto flush_request = consumer->Flush();
+ producer->WaitForFlush(writer.get());
+ ASSERT_TRUE(flush_request.WaitForReply());
+
+ const DataSourceInstanceID id1 = producer->GetDataSourceInstanceId("ds_1");
+ EXPECT_CALL(*producer, StopDataSource(id1));
+
+ consumer->DisableTracing();
+ consumer->WaitForTracingDisabled();
+
+ auto packets = consumer->ReadBuffers();
+ EXPECT_THAT(packets, Contains(Property(&protos::gen::TracePacket::for_testing,
+ Property(&protos::gen::TestEvent::str,
+ Eq("B|1023|payP6ad1")))));
+ EXPECT_THAT(packets, Contains(Property(&protos::gen::TracePacket::for_testing,
+ Property(&protos::gen::TestEvent::str,
+ Eq("B|1023|payP6ad1P")))));
+}
+
} // namespace perfetto
diff --git a/test/data/example_android_trace_30s_atrace_strings.txt.sha256 b/test/data/example_android_trace_30s_atrace_strings.txt.sha256
new file mode 100644
index 000000000..59474a318
--- /dev/null
+++ b/test/data/example_android_trace_30s_atrace_strings.txt.sha256
@@ -0,0 +1 @@
+4a7f0fa961d2272241e21666d3e6cdec0476eba5be961e338fb7b7c132c56a16 \ No newline at end of file