| /* |
| * 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 "rate_limiter.h" |
| |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| #include "perfetto/base/logging.h" |
| #include "perfetto/base/scoped_file.h" |
| #include "perfetto/base/utils.h" |
| #include "src/perfetto_cmd/perfetto_cmd.h" |
| |
| namespace perfetto { |
| namespace { |
| |
| // 5 mins between traces. |
| const uint64_t kCooldownInSeconds = 60 * 5; |
| |
| // Every 24 hours we reset how much we've uploaded. |
| const uint64_t kMaxUploadResetPeriodInSeconds = 60 * 60 * 24; |
| |
| // Maximum of 10mb every 24h. |
| const uint64_t kMaxUploadInBytes = 1024 * 1024 * 10; |
| |
| } // namespace |
| |
| RateLimiter::RateLimiter() = default; |
| RateLimiter::~RateLimiter() = default; |
| |
| bool RateLimiter::ShouldTrace(const Args& args) { |
| uint64_t now_in_s = args.current_time.count(); |
| |
| // Not uploading? |
| // -> We can just trace. |
| if (!args.is_dropbox) |
| return true; |
| |
| // The state file is gone. |
| // Maybe we're tracing for the first time or maybe something went wrong the |
| // last time we tried to save the state. Either way reinitialize the state |
| // file. |
| if (!StateFileExists()) { |
| // We can't write the empty state file? |
| // -> Give up. |
| if (!ClearState()) { |
| PERFETTO_ELOG("Guardrail: failed to initialize guardrail state."); |
| return false; |
| } |
| } |
| |
| bool loaded_state = LoadState(&state_); |
| |
| // Failed to load the state? |
| // Current time is before either saved times? |
| // Last saved trace time is before first saved trace time? |
| // -> Try to save a clean state but don't trace. |
| if (!loaded_state || now_in_s < state_.first_trace_timestamp() || |
| now_in_s < state_.last_trace_timestamp() || |
| state_.last_trace_timestamp() < state_.first_trace_timestamp()) { |
| ClearState(); |
| PERFETTO_ELOG("Guardrail: state invalid, clearing it."); |
| if (!args.ignore_guardrails) |
| return false; |
| } |
| |
| // If we've uploaded in the last 5mins we shouldn't trace now. |
| if ((now_in_s - state_.last_trace_timestamp()) < kCooldownInSeconds) { |
| PERFETTO_ELOG("Guardrail: Uploaded to DropBox in the last 5mins."); |
| if (!args.ignore_guardrails) |
| return false; |
| } |
| |
| // First trace was more than 24h ago? Reset state. |
| if ((now_in_s - state_.first_trace_timestamp()) > |
| kMaxUploadResetPeriodInSeconds) { |
| state_.set_first_trace_timestamp(0); |
| state_.set_last_trace_timestamp(0); |
| state_.set_total_bytes_uploaded(0); |
| return true; |
| } |
| |
| // If we've uploaded more than 10mb in the last 24 hours we shouldn't trace |
| // now. |
| if (state_.total_bytes_uploaded() > kMaxUploadInBytes) { |
| PERFETTO_ELOG("Guardrail: Uploaded >10mb DropBox in the last 24h."); |
| if (!args.ignore_guardrails) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool RateLimiter::OnTraceDone(const Args& args, bool success, size_t bytes) { |
| uint64_t now_in_s = args.current_time.count(); |
| |
| // Failed to upload? Don't update the state. |
| if (!success) |
| return false; |
| |
| if (!args.is_dropbox) |
| return true; |
| |
| // If the first trace timestamp is 0 (either because this is the |
| // first time or because it was reset for being more than 24h ago). |
| // -> We update it to the time of this trace. |
| if (state_.first_trace_timestamp() == 0) |
| state_.set_first_trace_timestamp(now_in_s); |
| // Always updated the last trace timestamp. |
| state_.set_last_trace_timestamp(now_in_s); |
| // Add the amount we uploaded to the running total. |
| state_.set_total_bytes_uploaded(state_.total_bytes_uploaded() + bytes); |
| |
| if (!SaveState(state_)) { |
| PERFETTO_ELOG("Failed to save state."); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| std::string RateLimiter::GetStateFilePath() const { |
| return std::string(kTempDropBoxTraceDir) + "/.guardraildata"; |
| } |
| |
| bool RateLimiter::StateFileExists() { |
| struct stat out; |
| return stat(GetStateFilePath().c_str(), &out) != -1; |
| } |
| |
| bool RateLimiter::ClearState() { |
| PerfettoCmdState zero{}; |
| zero.set_total_bytes_uploaded(0); |
| zero.set_last_trace_timestamp(0); |
| zero.set_first_trace_timestamp(0); |
| bool success = SaveState(zero); |
| if (!success && StateFileExists()) |
| remove(GetStateFilePath().c_str()); |
| return success; |
| } |
| |
| bool RateLimiter::LoadState(PerfettoCmdState* state) { |
| base::ScopedFile in_fd; |
| in_fd.reset(open(GetStateFilePath().c_str(), O_RDONLY)); |
| |
| if (!in_fd) |
| return false; |
| char buf[1024]; |
| ssize_t bytes = PERFETTO_EINTR(read(in_fd.get(), &buf, sizeof(buf))); |
| if (bytes <= 0) |
| return false; |
| return state->ParseFromArray(&buf, bytes); |
| } |
| |
| bool RateLimiter::SaveState(const PerfettoCmdState& state) { |
| base::ScopedFile out_fd; |
| out_fd.reset( |
| open(GetStateFilePath().c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0600)); |
| if (!out_fd) |
| return false; |
| char buf[1024]; |
| PERFETTO_CHECK(static_cast<size_t>(state.ByteSize()) < sizeof(buf)); |
| size_t size = state.ByteSize(); |
| if (!state.SerializeToArray(&buf, size)) |
| return false; |
| ssize_t written = PERFETTO_EINTR(write(out_fd.get(), &buf, size)); |
| return written >= 0 && static_cast<size_t>(written) == size; |
| } |
| |
| } // namespace perfetto |