blob: 38b2822c1b218914edf456a04787836c1ff1e6b1 [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.
*/
#include "src/perfetto_cmd/rate_limiter.h"
#include <stdio.h>
#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_METHOD(bool, LoadState, (gen::PerfettoCmdState*), (override));
MOCK_METHOD(bool, SaveState, (const gen::PerfettoCmdState&), (override));
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<MockRateLimiter> 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<MockRateLimiter> 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<MockRateLimiter> 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<MockRateLimiter> limiter;
gen::PerfettoCmdState output{};
ASSERT_FALSE(limiter.LoadState(&output));
ASSERT_EQ(output.total_bytes_uploaded(), 0u);
}
TEST(RateLimiterTest, LoadFromGarbageFails) {
NiceMock<MockRateLimiter> 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<MockRateLimiter> limiter;
ASSERT_EQ(limiter.ShouldTrace({}), RateLimiter::kOkToTrace);
ASSERT_TRUE(limiter.OnTraceDone({}, true, 10000));
ASSERT_FALSE(limiter.StateFileExists());
}
TEST(RateLimiterTest, NotDropBox_FailedToTrace) {
StrictMock<MockRateLimiter> limiter;
ASSERT_FALSE(limiter.OnTraceDone({}, false, 0));
ASSERT_FALSE(limiter.StateFileExists());
}
TEST(RateLimiterTest, DropBox_IgnoreGuardrails) {
StrictMock<MockRateLimiter> 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<MockRateLimiter> 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<MockRateLimiter> 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<uint64_t>(args.current_time.count()));
}
TEST(RateLimiterTest, DropBox_NormalUploadWithSessionName) {
StrictMock<MockRateLimiter> 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<uint64_t>(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<uint64_t>(args.current_time.count()));
ASSERT_THAT(output.session_state(), Contains(session));
}
}
TEST(RateLimiterTest, DropBox_FailedToLoadState) {
StrictMock<MockRateLimiter> 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<MockRateLimiter> 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<MockRateLimiter> 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<MockRateLimiter> 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<MockRateLimiter> 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<MockRateLimiter> 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<MockRateLimiter> 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<MockRateLimiter> 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<uint64_t>(args.current_time.count()));
EXPECT_EQ(output.last_trace_timestamp(),
static_cast<uint64_t>(args.current_time.count()));
}
TEST(RateLimiterTest, DropBox_FailedToUpload) {
StrictMock<MockRateLimiter> 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<MockRateLimiter> 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<MockRateLimiter> 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<MockRateLimiter> 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