Etienne Pierre-doray | aec3d57 | 2024-10-29 15:13:33 | [diff] [blame] | 1 | // Copyright 2024 The Chromium Authors |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | #include "content/browser/tracing/background_tracing_rule.h" |
| 6 | |
| 7 | #include <memory> |
| 8 | |
| 9 | #include "base/base_paths.h" |
| 10 | #include "base/metrics/histogram_macros_local.h" |
| 11 | #include "base/path_service.h" |
| 12 | #include "base/run_loop.h" |
| 13 | #include "base/system/sys_info.h" |
| 14 | #include "base/test/bind.h" |
| 15 | #include "base/test/test_proto_loader.h" |
| 16 | #include "base/test/test_timeouts.h" |
| 17 | #include "base/time/time.h" |
| 18 | #include "base/trace_event/named_trigger.h" |
| 19 | #include "build/build_config.h" |
| 20 | #include "content/public/browser/background_tracing_manager.h" |
| 21 | #include "content/public/test/browser_task_environment.h" |
| 22 | #include "testing/gtest/include/gtest/gtest.h" |
| 23 | #include "third_party/perfetto/protos/perfetto/config/chrome/scenario_config.gen.h" |
| 24 | |
| 25 | namespace content { |
| 26 | |
| 27 | namespace { |
| 28 | |
| 29 | base::FilePath GetTestDataRoot() { |
| 30 | return base::PathService::CheckedGet(base::DIR_GEN_TEST_DATA_ROOT); |
| 31 | } |
| 32 | |
| 33 | void CreateRuleConfig(const std::string& proto_text, |
| 34 | perfetto::protos::gen::TriggerRule& destination) { |
| 35 | base::TestProtoLoader loader(GetTestDataRoot().Append(FILE_PATH_LITERAL( |
| 36 | "third_party/perfetto/protos/perfetto/" |
| 37 | "config/chrome/scenario_config.descriptor")), |
| 38 | "perfetto.protos.TriggerRule"); |
| 39 | std::string serialized_message; |
| 40 | loader.ParseFromText(proto_text, serialized_message); |
| 41 | ASSERT_TRUE(destination.ParseFromString(serialized_message)); |
| 42 | } |
| 43 | |
| 44 | } // namespace |
| 45 | |
| 46 | class BackgroundTracingRuleTest : public testing::Test { |
| 47 | public: |
| 48 | BackgroundTracingRuleTest() = default; |
| 49 | |
| 50 | protected: |
| 51 | BrowserTaskEnvironment task_environment_{ |
| 52 | base::test::TaskEnvironment::TimeSource::MOCK_TIME}; |
| 53 | |
Etienne Pierre-doray | 3dc2c7dc | 2025-06-12 12:06:33 | [diff] [blame] | 54 | content::TracingDelegate tracing_delegate; |
Etienne Pierre-doray | aec3d57 | 2024-10-29 15:13:33 | [diff] [blame] | 55 | std::unique_ptr<content::BackgroundTracingManager> |
| 56 | background_tracing_manager = |
Etienne Pierre-doray | 3dc2c7dc | 2025-06-12 12:06:33 | [diff] [blame] | 57 | content::BackgroundTracingManager::CreateInstance(&tracing_delegate); |
Etienne Pierre-doray | aec3d57 | 2024-10-29 15:13:33 | [diff] [blame] | 58 | }; |
| 59 | |
| 60 | TEST_F(BackgroundTracingRuleTest, HistogramRuleFromValidProto) { |
| 61 | perfetto::protos::gen::TriggerRule config; |
| 62 | CreateRuleConfig( |
| 63 | R"pb( |
| 64 | name: "test_rule" |
| 65 | trigger_chance: 0.5 |
| 66 | delay_ms: 500 |
| 67 | histogram: { histogram_name: "foo" min_value: 1 max_value: 2 } |
| 68 | )pb", |
| 69 | config); |
| 70 | auto rule = BackgroundTracingRule::Create(config); |
| 71 | auto result = rule->ToProtoForTesting(); |
| 72 | EXPECT_EQ("test_rule", result.name()); |
| 73 | EXPECT_EQ(0.5, result.trigger_chance()); |
| 74 | EXPECT_EQ(500U, result.delay_ms()); |
| 75 | EXPECT_TRUE(result.has_histogram()); |
| 76 | EXPECT_EQ("foo", result.histogram().histogram_name()); |
| 77 | EXPECT_EQ(1, result.histogram().min_value()); |
| 78 | EXPECT_EQ(2, result.histogram().max_value()); |
| 79 | } |
| 80 | |
| 81 | TEST_F(BackgroundTracingRuleTest, HistogramRuleSucceedsOnLowerReferenceValue) { |
| 82 | perfetto::protos::gen::TriggerRule config; |
| 83 | CreateRuleConfig( |
| 84 | R"pb( |
| 85 | name: "test_rule" |
| 86 | histogram: { histogram_name: "foo" min_value: 1 max_value: 2 } |
| 87 | )pb", |
| 88 | config); |
| 89 | auto rule = BackgroundTracingRule::Create(config); |
| 90 | base::RunLoop run_loop; |
| 91 | rule->Install(base::BindLambdaForTesting([&](const BackgroundTracingRule*) { |
| 92 | run_loop.Quit(); |
| 93 | return true; |
| 94 | })); |
| 95 | LOCAL_HISTOGRAM_COUNTS("foo", 1); |
| 96 | run_loop.Run(); |
| 97 | rule->Uninstall(); |
| 98 | } |
| 99 | |
| 100 | TEST_F(BackgroundTracingRuleTest, HistogramRuleSucceedsOnUpperReferenceValue) { |
| 101 | perfetto::protos::gen::TriggerRule config; |
| 102 | CreateRuleConfig( |
| 103 | R"pb( |
| 104 | name: "test_rule" |
| 105 | histogram: { histogram_name: "foo" min_value: 1 max_value: 2 } |
| 106 | )pb", |
| 107 | config); |
| 108 | auto rule = BackgroundTracingRule::Create(config); |
| 109 | base::RunLoop run_loop; |
| 110 | rule->Install(base::BindLambdaForTesting([&](const BackgroundTracingRule*) { |
| 111 | run_loop.Quit(); |
| 112 | return true; |
| 113 | })); |
| 114 | LOCAL_HISTOGRAM_COUNTS("foo", 2); |
| 115 | run_loop.Run(); |
| 116 | rule->Uninstall(); |
| 117 | } |
| 118 | |
| 119 | TEST_F(BackgroundTracingRuleTest, HistogramRuleSucceedsOnSingleEnumValue) { |
| 120 | perfetto::protos::gen::TriggerRule config; |
| 121 | CreateRuleConfig( |
| 122 | R"pb( |
| 123 | name: "test_rule" |
| 124 | histogram: { histogram_name: "foo" min_value: 1 max_value: 1 } |
| 125 | )pb", |
| 126 | config); |
| 127 | auto rule = BackgroundTracingRule::Create(config); |
| 128 | base::RunLoop run_loop; |
| 129 | rule->Install(base::BindLambdaForTesting([&](const BackgroundTracingRule*) { |
| 130 | run_loop.Quit(); |
| 131 | return true; |
| 132 | })); |
| 133 | LOCAL_HISTOGRAM_COUNTS("foo", 1); |
| 134 | run_loop.Run(); |
| 135 | rule->Uninstall(); |
| 136 | } |
| 137 | |
| 138 | TEST_F(BackgroundTracingRuleTest, HistogramRuleFailsOnLowerHistogramSample) { |
| 139 | perfetto::protos::gen::TriggerRule config; |
| 140 | CreateRuleConfig( |
| 141 | R"pb( |
| 142 | name: "test_rule" |
| 143 | histogram: { histogram_name: "foo" min_value: 1 max_value: 2 } |
| 144 | )pb", |
| 145 | config); |
| 146 | auto rule = BackgroundTracingRule::Create(config); |
| 147 | rule->Install( |
| 148 | base::BindLambdaForTesting([](const BackgroundTracingRule*) -> bool { |
| 149 | ADD_FAILURE(); |
| 150 | return true; |
| 151 | })); |
| 152 | LOCAL_HISTOGRAM_COUNTS("foo", 0); |
| 153 | task_environment_.FastForwardBy(TestTimeouts::tiny_timeout()); |
| 154 | rule->Uninstall(); |
| 155 | } |
| 156 | |
| 157 | TEST_F(BackgroundTracingRuleTest, HistogramRuleFailsOnHigherHistogramSample) { |
| 158 | perfetto::protos::gen::TriggerRule config; |
| 159 | CreateRuleConfig( |
| 160 | R"pb( |
| 161 | name: "test_rule" |
| 162 | histogram: { histogram_name: "foo" min_value: 1 max_value: 2 } |
| 163 | )pb", |
| 164 | config); |
| 165 | auto rule = BackgroundTracingRule::Create(config); |
| 166 | rule->Install( |
| 167 | base::BindLambdaForTesting([](const BackgroundTracingRule*) -> bool { |
| 168 | ADD_FAILURE(); |
| 169 | return true; |
| 170 | })); |
| 171 | LOCAL_HISTOGRAM_COUNTS("foo", 3); |
| 172 | task_environment_.FastForwardBy(TestTimeouts::tiny_timeout()); |
| 173 | rule->Uninstall(); |
| 174 | } |
| 175 | |
| 176 | TEST_F(BackgroundTracingRuleTest, NamedRuleFromValidProto) { |
| 177 | perfetto::protos::gen::TriggerRule config; |
| 178 | CreateRuleConfig(R"pb( |
| 179 | name: "test_rule" |
| 180 | trigger_chance: 0.5 |
| 181 | delay_ms: 500 |
| 182 | manual_trigger_name: "test_trigger" |
| 183 | )pb", |
| 184 | config); |
| 185 | auto rule = BackgroundTracingRule::Create(config); |
| 186 | auto result = rule->ToProtoForTesting(); |
| 187 | EXPECT_EQ("test_rule", result.name()); |
| 188 | EXPECT_EQ(0.5, result.trigger_chance()); |
| 189 | EXPECT_EQ(500U, result.delay_ms()); |
| 190 | EXPECT_EQ("test_trigger", result.manual_trigger_name()); |
| 191 | } |
| 192 | |
| 193 | TEST_F(BackgroundTracingRuleTest, RuleFromEmptyProto) { |
| 194 | perfetto::protos::gen::TriggerRule config; |
| 195 | CreateRuleConfig(R"pb( |
| 196 | name: "test_rule" |
| 197 | )pb", |
| 198 | config); |
| 199 | auto rule = BackgroundTracingRule::Create(config); |
| 200 | EXPECT_EQ(nullptr, rule); |
| 201 | } |
| 202 | |
| 203 | TEST_F(BackgroundTracingRuleTest, TimerRuleFromValidProto) { |
| 204 | perfetto::protos::gen::TriggerRule config; |
| 205 | CreateRuleConfig(R"pb( |
| 206 | name: "test_rule" trigger_chance: 0.5 delay_ms: 500 |
| 207 | )pb", |
| 208 | config); |
| 209 | auto rule = BackgroundTracingRule::Create(config); |
| 210 | auto result = rule->ToProtoForTesting(); |
| 211 | EXPECT_EQ("test_rule", result.name()); |
| 212 | EXPECT_EQ(0.5, result.trigger_chance()); |
| 213 | EXPECT_EQ(500U, result.delay_ms()); |
| 214 | } |
| 215 | |
| 216 | TEST_F(BackgroundTracingRuleTest, TimerRuleTriggersAfterDelay) { |
| 217 | perfetto::protos::gen::TriggerRule config; |
| 218 | CreateRuleConfig(R"pb( |
| 219 | name: "test_rule" delay_ms: 10000 |
| 220 | )pb", |
| 221 | config); |
| 222 | |
| 223 | base::TimeTicks start = base::TimeTicks::Now(); |
| 224 | auto rule = BackgroundTracingRule::Create(config); |
| 225 | base::RunLoop run_loop; |
| 226 | rule->Install(base::BindLambdaForTesting([&](const BackgroundTracingRule*) { |
| 227 | run_loop.Quit(); |
| 228 | return true; |
| 229 | })); |
| 230 | run_loop.Run(); |
| 231 | DCHECK_GE(base::TimeTicks::Now(), start + base::Milliseconds(10000)); |
| 232 | rule->Uninstall(); |
| 233 | } |
| 234 | |
| 235 | TEST_F(BackgroundTracingRuleTest, RuleActivatesAfterDelay) { |
| 236 | perfetto::protos::gen::TriggerRule config; |
| 237 | CreateRuleConfig(R"pb( |
| 238 | name: "test_rule" |
| 239 | manual_trigger_name: "test_rule" |
| 240 | activation_delay_ms: 10000 |
| 241 | )pb", |
| 242 | config); |
| 243 | |
| 244 | auto rule = BackgroundTracingRule::Create(config); |
| 245 | |
| 246 | base::RunLoop run_loop; |
| 247 | rule->Install(base::BindLambdaForTesting([&](const BackgroundTracingRule*) { |
| 248 | run_loop.Quit(); |
| 249 | return true; |
| 250 | })); |
| 251 | |
| 252 | // Rule is not activated yet. |
| 253 | EXPECT_FALSE(base::trace_event::EmitNamedTrigger("test_rule")); |
| 254 | task_environment_.FastForwardBy(base::Seconds(10)); |
| 255 | EXPECT_TRUE(base::trace_event::EmitNamedTrigger("test_rule")); |
| 256 | run_loop.Run(); |
| 257 | rule->Uninstall(); |
| 258 | } |
| 259 | |
| 260 | TEST_F(BackgroundTracingRuleTest, RepeatingIntervalRuleFromValidProto) { |
| 261 | perfetto::protos::gen::TriggerRule config; |
| 262 | CreateRuleConfig( |
| 263 | R"pb( |
| 264 | name: "test_rule" |
| 265 | trigger_chance: 0.5 |
| 266 | delay_ms: 500 |
| 267 | repeating_interval: { period_ms: 1000 randomized: true } |
| 268 | )pb", |
| 269 | config); |
| 270 | auto rule = BackgroundTracingRule::Create(config); |
| 271 | auto result = rule->ToProtoForTesting(); |
| 272 | EXPECT_EQ("test_rule", result.name()); |
| 273 | EXPECT_EQ(0.5, result.trigger_chance()); |
| 274 | EXPECT_EQ(500U, result.delay_ms()); |
| 275 | EXPECT_TRUE(result.has_repeating_interval()); |
| 276 | EXPECT_EQ(1000U, result.repeating_interval().period_ms()); |
| 277 | EXPECT_TRUE(result.repeating_interval().randomized()); |
| 278 | } |
| 279 | |
| 280 | TEST_F(BackgroundTracingRuleTest, RepeatingIntervalRuleTriggersAfterDelay) { |
| 281 | perfetto::protos::gen::TriggerRule config; |
| 282 | CreateRuleConfig(R"pb( |
| 283 | name: "test_rule" |
| 284 | repeating_interval: { period_ms: 2000 } |
| 285 | )pb", |
| 286 | config); |
| 287 | |
| 288 | base::TimeTicks start = base::TimeTicks::Now(); |
| 289 | auto rule = BackgroundTracingRule::Create(config); |
| 290 | std::vector<base::TimeTicks> trigger_times; |
| 291 | auto callback = base::BindLambdaForTesting([&](const BackgroundTracingRule*) { |
| 292 | trigger_times.push_back(base::TimeTicks::Now()); |
| 293 | return true; |
| 294 | }); |
| 295 | rule->Install(callback); |
| 296 | task_environment_.FastForwardBy(base::Seconds(2)); // Triggers at 2s |
| 297 | rule->Uninstall(); |
| 298 | task_environment_.FastForwardBy(base::Seconds(1)); |
| 299 | rule->Install(callback); |
| 300 | task_environment_.FastForwardBy(base::Seconds(2)); // Triggers at 4s |
| 301 | rule->Uninstall(); |
| 302 | task_environment_.FastForwardBy(base::Seconds(2)); // Skips 6s |
| 303 | rule->Install(callback); |
| 304 | task_environment_.FastForwardBy(base::Seconds(1)); // Triggers at 8s |
| 305 | |
| 306 | EXPECT_EQ(std::vector<base::TimeTicks>({ |
| 307 | start + base::Seconds(2), |
| 308 | start + base::Seconds(4), |
| 309 | start + base::Seconds(8), |
| 310 | }), |
| 311 | trigger_times); |
| 312 | rule->Uninstall(); |
| 313 | } |
| 314 | |
| 315 | TEST_F(BackgroundTracingRuleTest, RepeatingIntervalRuleTriggersRandomized) { |
| 316 | perfetto::protos::gen::TriggerRule config; |
| 317 | CreateRuleConfig( |
| 318 | R"pb( |
| 319 | name: "test_rule" |
| 320 | repeating_interval: { period_ms: 2000 randomized: true } |
| 321 | )pb", |
| 322 | config); |
| 323 | |
| 324 | base::TimeTicks start = base::TimeTicks::Now(); |
| 325 | auto rule = BackgroundTracingRule::Create(config); |
| 326 | std::vector<base::TimeTicks> trigger_times; |
| 327 | auto callback = base::BindLambdaForTesting([&](const BackgroundTracingRule*) { |
| 328 | trigger_times.push_back(base::TimeTicks::Now()); |
| 329 | return true; |
| 330 | }); |
| 331 | rule->Install(callback); |
| 332 | task_environment_.FastForwardBy(base::Seconds(4)); // Triggers twice |
| 333 | ASSERT_EQ(2U, trigger_times.size()); |
| 334 | EXPECT_GE(trigger_times[0], start); |
| 335 | EXPECT_LT(trigger_times[0], trigger_times[1]); |
| 336 | EXPECT_GE(trigger_times[1], start + base::Seconds(2)); |
| 337 | rule->Uninstall(); |
| 338 | } |
| 339 | |
| 340 | } // namespace content |