/* * 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. */ #include "src/perfetto_cmd/rate_limiter.h" #include #include "perfetto/ext/base/file_utils.h" #include "perfetto/ext/base/scoped_file.h" #include "perfetto/ext/base/temp_file.h" #include "perfetto/ext/base/utils.h" #include "test/gtest_and_gmock.h" using testing::_; using testing::Contains; using testing::Invoke; using testing::NiceMock; using testing::Return; using testing::StrictMock; namespace perfetto { namespace { class MockRateLimiter : public RateLimiter { public: MockRateLimiter() : dir_(base::TempDir::Create()) { ON_CALL(*this, LoadState(_)) .WillByDefault(Invoke(this, &MockRateLimiter::LoadStateConcrete)); ON_CALL(*this, SaveState(_)) .WillByDefault(Invoke(this, &MockRateLimiter::SaveStateConcrete)); } virtual std::string GetStateFilePath() const { return std::string(dir_.path()) + "/.guardraildata"; } virtual ~MockRateLimiter() override { if (StateFileExists()) remove(GetStateFilePath().c_str()); } bool LoadStateConcrete(gen::PerfettoCmdState* state) { return RateLimiter::LoadState(state); } bool SaveStateConcrete(const gen::PerfettoCmdState& state) { return RateLimiter::SaveState(state); } MOCK_METHOD1(LoadState, bool(gen::PerfettoCmdState*)); MOCK_METHOD1(SaveState, bool(const gen::PerfettoCmdState&)); private: base::TempDir dir_; }; void WriteGarbageToFile(const std::string& path) { base::ScopedFile fd(base::OpenFile(path, O_WRONLY | O_CREAT, 0600)); constexpr char data[] = "Some random bytes."; if (base::WriteAll(fd.get(), data, sizeof(data)) != sizeof(data)) ADD_FAILURE() << "Could not write garbage"; } TEST(RateLimiterTest, RoundTripState) { NiceMock limiter; gen::PerfettoCmdState input{}; gen::PerfettoCmdState output{}; input.set_total_bytes_uploaded(42); ASSERT_TRUE(limiter.SaveState(input)); ASSERT_TRUE(limiter.LoadState(&output)); ASSERT_EQ(output.total_bytes_uploaded(), 42u); ASSERT_EQ(output.session_state_size(), 0); } TEST(RateLimiterTest, FileIsSensiblyTruncated) { NiceMock limiter; gen::PerfettoCmdState input{}; gen::PerfettoCmdState output{}; input.set_total_bytes_uploaded(42); input.set_first_trace_timestamp(1); input.set_last_trace_timestamp(2); for (size_t i = 0; i < 100; ++i) { auto* session = input.add_session_state(); session->set_session_name("session_" + std::to_string(i)); session->set_total_bytes_uploaded(i * 100); session->set_last_trace_timestamp(i); } ASSERT_TRUE(limiter.SaveState(input)); ASSERT_TRUE(limiter.LoadState(&output)); ASSERT_EQ(output.total_bytes_uploaded(), 42u); ASSERT_EQ(output.first_trace_timestamp(), 1u); ASSERT_EQ(output.last_trace_timestamp(), 2u); ASSERT_LE(output.session_state_size(), 50); ASSERT_GE(output.session_state_size(), 5); { gen::PerfettoCmdState::PerSessionState session; session.set_session_name("session_99"); session.set_total_bytes_uploaded(99 * 100); session.set_last_trace_timestamp(99); ASSERT_THAT(output.session_state(), Contains(session)); } } TEST(RateLimiterTest, LoadFromEmpty) { NiceMock limiter; gen::PerfettoCmdState input{}; input.set_total_bytes_uploaded(0); input.set_last_trace_timestamp(0); input.set_first_trace_timestamp(0); gen::PerfettoCmdState output{}; ASSERT_TRUE(limiter.SaveState(input)); ASSERT_TRUE(limiter.LoadState(&output)); ASSERT_EQ(output.total_bytes_uploaded(), 0u); } TEST(RateLimiterTest, LoadFromNoFileFails) { NiceMock limiter; gen::PerfettoCmdState output{}; ASSERT_FALSE(limiter.LoadState(&output)); ASSERT_EQ(output.total_bytes_uploaded(), 0u); } TEST(RateLimiterTest, LoadFromGarbageFails) { NiceMock limiter; WriteGarbageToFile(limiter.GetStateFilePath().c_str()); gen::PerfettoCmdState output{}; ASSERT_FALSE(limiter.LoadState(&output)); ASSERT_EQ(output.total_bytes_uploaded(), 0u); } TEST(RateLimiterTest, NotDropBox) { StrictMock limiter; ASSERT_EQ(limiter.ShouldTrace({}), RateLimiter::kOkToTrace); ASSERT_TRUE(limiter.OnTraceDone({}, true, 10000)); ASSERT_FALSE(limiter.StateFileExists()); } TEST(RateLimiterTest, NotDropBox_FailedToTrace) { StrictMock limiter; ASSERT_FALSE(limiter.OnTraceDone({}, false, 0)); ASSERT_FALSE(limiter.StateFileExists()); } TEST(RateLimiterTest, DropBox_IgnoreGuardrails) { StrictMock limiter; RateLimiter::Args args; args.allow_user_build_tracing = true; args.is_uploading = true; args.ignore_guardrails = true; args.current_time = base::TimeSeconds(41); EXPECT_CALL(limiter, SaveState(_)); EXPECT_CALL(limiter, LoadState(_)); ASSERT_EQ(limiter.ShouldTrace(args), RateLimiter::kOkToTrace); EXPECT_CALL(limiter, SaveState(_)); ASSERT_TRUE(limiter.OnTraceDone(args, true, 42u)); gen::PerfettoCmdState output{}; ASSERT_TRUE(limiter.LoadStateConcrete(&output)); ASSERT_EQ(output.first_trace_timestamp(), 41u); ASSERT_EQ(output.last_trace_timestamp(), 41u); ASSERT_EQ(output.total_bytes_uploaded(), 42u); } TEST(RateLimiterTest, DropBox_EmptyState) { StrictMock limiter; RateLimiter::Args args; args.allow_user_build_tracing = true; args.is_uploading = true; args.current_time = base::TimeSeconds(10000); EXPECT_CALL(limiter, SaveState(_)); EXPECT_CALL(limiter, LoadState(_)); ASSERT_EQ(limiter.ShouldTrace(args), RateLimiter::kOkToTrace); EXPECT_CALL(limiter, SaveState(_)); ASSERT_TRUE(limiter.OnTraceDone(args, true, 1024 * 1024)); gen::PerfettoCmdState output{}; ASSERT_TRUE(limiter.LoadStateConcrete(&output)); EXPECT_EQ(output.total_bytes_uploaded(), 1024u * 1024u); EXPECT_EQ(output.first_trace_timestamp(), 10000u); EXPECT_EQ(output.last_trace_timestamp(), 10000u); } TEST(RateLimiterTest, DropBox_NormalUpload) { StrictMock limiter; RateLimiter::Args args; gen::PerfettoCmdState input{}; input.set_first_trace_timestamp(10000); input.set_last_trace_timestamp(10000 + 60 * 10); input.set_total_bytes_uploaded(1024 * 1024 * 2); ASSERT_TRUE(limiter.SaveStateConcrete(input)); args.allow_user_build_tracing = true; args.is_uploading = true; args.current_time = base::TimeSeconds(input.last_trace_timestamp() + 60 * 10); EXPECT_CALL(limiter, LoadState(_)); ASSERT_EQ(limiter.ShouldTrace(args), RateLimiter::kOkToTrace); EXPECT_CALL(limiter, SaveState(_)); ASSERT_TRUE(limiter.OnTraceDone(args, true, 1024 * 1024)); gen::PerfettoCmdState output{}; ASSERT_TRUE(limiter.LoadStateConcrete(&output)); EXPECT_EQ(output.total_bytes_uploaded(), 1024u * 1024u * 3); EXPECT_EQ(output.first_trace_timestamp(), input.first_trace_timestamp()); EXPECT_EQ(output.last_trace_timestamp(), static_cast(args.current_time.count())); } TEST(RateLimiterTest, DropBox_NormalUploadWithSessionName) { StrictMock limiter; RateLimiter::Args args; gen::PerfettoCmdState input{}; input.set_first_trace_timestamp(10000); input.set_last_trace_timestamp(10000 + 60 * 10); input.set_total_bytes_uploaded(1024 * 1024 * 2); ASSERT_TRUE(limiter.SaveStateConcrete(input)); args.allow_user_build_tracing = true; args.is_uploading = true; args.unique_session_name = "foo"; args.current_time = base::TimeSeconds(input.last_trace_timestamp() + 60 * 10); EXPECT_CALL(limiter, LoadState(_)); ASSERT_EQ(limiter.ShouldTrace(args), RateLimiter::kOkToTrace); EXPECT_CALL(limiter, SaveState(_)); ASSERT_TRUE(limiter.OnTraceDone(args, true, 1024 * 1024)); gen::PerfettoCmdState output{}; ASSERT_TRUE(limiter.LoadStateConcrete(&output)); EXPECT_EQ(output.total_bytes_uploaded(), 1024u * 1024u * 2); EXPECT_EQ(output.first_trace_timestamp(), input.first_trace_timestamp()); EXPECT_EQ(output.last_trace_timestamp(), static_cast(args.current_time.count())); ASSERT_GE(output.session_state_size(), 1); { gen::PerfettoCmdState::PerSessionState session; session.set_session_name("foo"); session.set_total_bytes_uploaded(1024 * 1024); session.set_last_trace_timestamp( static_cast(args.current_time.count())); ASSERT_THAT(output.session_state(), Contains(session)); } } TEST(RateLimiterTest, DropBox_FailedToLoadState) { StrictMock limiter; RateLimiter::Args args; args.allow_user_build_tracing = true; args.is_uploading = true; WriteGarbageToFile(limiter.GetStateFilePath().c_str()); EXPECT_CALL(limiter, LoadState(_)); EXPECT_CALL(limiter, SaveState(_)); ASSERT_EQ(limiter.ShouldTrace(args), RateLimiter::kInvalidState); gen::PerfettoCmdState output{}; ASSERT_TRUE(limiter.LoadStateConcrete(&output)); EXPECT_EQ(output.total_bytes_uploaded(), 0u); EXPECT_EQ(output.first_trace_timestamp(), 0u); EXPECT_EQ(output.last_trace_timestamp(), 0u); } TEST(RateLimiterTest, DropBox_NoTimeTravel) { StrictMock limiter; RateLimiter::Args args; gen::PerfettoCmdState input{}; input.set_first_trace_timestamp(100); input.set_last_trace_timestamp(100); ASSERT_TRUE(limiter.SaveStateConcrete(input)); args.allow_user_build_tracing = true; args.is_uploading = true; args.current_time = base::TimeSeconds(99); EXPECT_CALL(limiter, LoadState(_)); EXPECT_CALL(limiter, SaveState(_)); ASSERT_EQ(limiter.ShouldTrace(args), RateLimiter::kInvalidState); gen::PerfettoCmdState output{}; ASSERT_TRUE(limiter.LoadStateConcrete(&output)); EXPECT_EQ(output.total_bytes_uploaded(), 0u); EXPECT_EQ(output.first_trace_timestamp(), 0u); EXPECT_EQ(output.last_trace_timestamp(), 0u); } TEST(RateLimiterTest, DropBox_TooMuch_OtherSession) { StrictMock limiter; RateLimiter::Args args; gen::PerfettoCmdState input{}; auto* session = input.add_session_state(); session->set_session_name("foo"); session->set_total_bytes_uploaded(100 * 1024 * 1024); ASSERT_TRUE(limiter.SaveStateConcrete(input)); args.is_user_build = true; args.allow_user_build_tracing = true; args.is_uploading = true; args.unique_session_name = "bar"; args.current_time = base::TimeSeconds(60 * 60); EXPECT_CALL(limiter, LoadState(_)); ASSERT_EQ(limiter.ShouldTrace(args), RateLimiter::kOkToTrace); } TEST(RateLimiterTest, DropBox_TooMuch_Session) { StrictMock limiter; RateLimiter::Args args; gen::PerfettoCmdState input{}; auto* session = input.add_session_state(); session->set_session_name("foo"); session->set_total_bytes_uploaded(100 * 1024 * 1024); ASSERT_TRUE(limiter.SaveStateConcrete(input)); args.is_user_build = true; args.allow_user_build_tracing = true; args.is_uploading = true; args.unique_session_name = "foo"; args.current_time = base::TimeSeconds(60 * 60); EXPECT_CALL(limiter, LoadState(_)); ASSERT_EQ(limiter.ShouldTrace(args), RateLimiter::kHitUploadLimit); } TEST(RateLimiterTest, DropBox_TooMuch_User) { StrictMock limiter; RateLimiter::Args args; gen::PerfettoCmdState input{}; input.set_total_bytes_uploaded(10 * 1024 * 1024 + 1); ASSERT_TRUE(limiter.SaveStateConcrete(input)); args.is_user_build = true; args.allow_user_build_tracing = true; args.is_uploading = true; args.current_time = base::TimeSeconds(60 * 60); EXPECT_CALL(limiter, LoadState(_)); ASSERT_EQ(limiter.ShouldTrace(args), RateLimiter::kHitUploadLimit); } TEST(RateLimiterTest, DropBox_TooMuch_Override) { StrictMock limiter; RateLimiter::Args args; gen::PerfettoCmdState input{}; auto* session = input.add_session_state(); session->set_session_name("foo"); session->set_total_bytes_uploaded(10 * 1024 * 1024 + 1); ASSERT_TRUE(limiter.SaveStateConcrete(input)); args.allow_user_build_tracing = true; args.is_uploading = true; args.current_time = base::TimeSeconds(60 * 60); args.max_upload_bytes_override = 10 * 1024 * 1024 + 2; args.unique_session_name = "foo"; EXPECT_CALL(limiter, LoadState(_)); ASSERT_EQ(limiter.ShouldTrace(args), RateLimiter::kOkToTrace); } // Override doesn't apply to traces without session name. TEST(RateLimiterTest, DropBox_OverrideOnEmptySesssionName) { StrictMock limiter; RateLimiter::Args args; gen::PerfettoCmdState input{}; input.set_total_bytes_uploaded(10 * 1024 * 1024 + 1); ASSERT_TRUE(limiter.SaveStateConcrete(input)); args.allow_user_build_tracing = true; args.is_uploading = true; args.current_time = base::TimeSeconds(60 * 60); args.max_upload_bytes_override = 10 * 1024 * 1024 + 2; EXPECT_CALL(limiter, LoadState(_)); ASSERT_EQ(limiter.ShouldTrace(args), RateLimiter::kHitUploadLimit); } TEST(RateLimiterTest, DropBox_TooMuchWasUploaded) { StrictMock limiter; RateLimiter::Args args; gen::PerfettoCmdState input{}; input.set_first_trace_timestamp(1); input.set_last_trace_timestamp(1); input.set_total_bytes_uploaded(10 * 1024 * 1024 + 1); ASSERT_TRUE(limiter.SaveStateConcrete(input)); args.is_uploading = true; args.current_time = base::TimeSeconds(60 * 60 * 24 + 2); EXPECT_CALL(limiter, LoadState(_)); ASSERT_EQ(limiter.ShouldTrace(args), RateLimiter::kOkToTrace); EXPECT_CALL(limiter, SaveState(_)); ASSERT_TRUE(limiter.OnTraceDone(args, true, 1024 * 1024)); gen::PerfettoCmdState output{}; ASSERT_TRUE(limiter.LoadStateConcrete(&output)); EXPECT_EQ(output.total_bytes_uploaded(), 1024u * 1024u); EXPECT_EQ(output.first_trace_timestamp(), static_cast(args.current_time.count())); EXPECT_EQ(output.last_trace_timestamp(), static_cast(args.current_time.count())); } TEST(RateLimiterTest, DropBox_FailedToUpload) { StrictMock limiter; RateLimiter::Args args; args.is_uploading = true; args.current_time = base::TimeSeconds(10000); EXPECT_CALL(limiter, SaveState(_)); EXPECT_CALL(limiter, LoadState(_)); ASSERT_EQ(limiter.ShouldTrace(args), RateLimiter::kOkToTrace); ASSERT_FALSE(limiter.OnTraceDone(args, false, 1024 * 1024)); } TEST(RateLimiterTest, DropBox_FailedToSave) { StrictMock limiter; RateLimiter::Args args; args.is_uploading = true; args.current_time = base::TimeSeconds(10000); EXPECT_CALL(limiter, SaveState(_)); EXPECT_CALL(limiter, LoadState(_)); ASSERT_EQ(limiter.ShouldTrace(args), RateLimiter::kOkToTrace); EXPECT_CALL(limiter, SaveState(_)).WillOnce(Return(false)); ASSERT_FALSE(limiter.OnTraceDone(args, true, 1024 * 1024)); } TEST(RateLimiterTest, DropBox_CantTraceOnUser) { StrictMock limiter; RateLimiter::Args args; args.is_user_build = true; args.allow_user_build_tracing = false; args.is_uploading = true; args.current_time = base::TimeSeconds(10000); ASSERT_EQ(limiter.ShouldTrace(args), RateLimiter::kNotAllowedOnUserBuild); } TEST(RateLimiterTest, DropBox_CanTraceOnUser) { StrictMock limiter; RateLimiter::Args args; args.is_user_build = false; args.allow_user_build_tracing = false; args.is_uploading = true; args.current_time = base::TimeSeconds(10000); EXPECT_CALL(limiter, SaveState(_)); EXPECT_CALL(limiter, LoadState(_)); ASSERT_EQ(limiter.ShouldTrace(args), RateLimiter::kOkToTrace); } } // namespace } // namespace perfetto