Home | History | Annotate | Download | only in perfetto_cmd
      1 /*
      2  * Copyright (C) 2018 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 #include "src/perfetto_cmd/rate_limiter.h"
     18 
     19 #include <sys/stat.h>
     20 #include <sys/types.h>
     21 #include <unistd.h>
     22 
     23 #include "perfetto/base/logging.h"
     24 #include "perfetto/base/scoped_file.h"
     25 #include "perfetto/base/utils.h"
     26 #include "src/perfetto_cmd/perfetto_cmd.h"
     27 
     28 namespace perfetto {
     29 namespace {
     30 
     31 // 5 mins between traces.
     32 const uint64_t kCooldownInSeconds = 60 * 5;
     33 
     34 // Every 24 hours we reset how much we've uploaded.
     35 const uint64_t kMaxUploadResetPeriodInSeconds = 60 * 60 * 24;
     36 
     37 // Maximum of 10mb every 24h.
     38 const uint64_t kMaxUploadInBytes = 1024 * 1024 * 10;
     39 
     40 }  // namespace
     41 
     42 RateLimiter::RateLimiter() = default;
     43 RateLimiter::~RateLimiter() = default;
     44 
     45 bool RateLimiter::ShouldTrace(const Args& args) {
     46   uint64_t now_in_s = static_cast<uint64_t>(args.current_time.count());
     47 
     48   // Not uploading?
     49   // -> We can just trace.
     50   if (!args.is_dropbox)
     51     return true;
     52 
     53   // The state file is gone.
     54   // Maybe we're tracing for the first time or maybe something went wrong the
     55   // last time we tried to save the state. Either way reinitialize the state
     56   // file.
     57   if (!StateFileExists()) {
     58     // We can't write the empty state file?
     59     // -> Give up.
     60     if (!ClearState()) {
     61       PERFETTO_ELOG("Guardrail: failed to initialize guardrail state.");
     62       return false;
     63     }
     64   }
     65 
     66   bool loaded_state = LoadState(&state_);
     67 
     68   // Failed to load the state?
     69   // Current time is before either saved times?
     70   // Last saved trace time is before first saved trace time?
     71   // -> Try to save a clean state but don't trace.
     72   if (!loaded_state || now_in_s < state_.first_trace_timestamp() ||
     73       now_in_s < state_.last_trace_timestamp() ||
     74       state_.last_trace_timestamp() < state_.first_trace_timestamp()) {
     75     ClearState();
     76     PERFETTO_ELOG("Guardrail: state invalid, clearing it.");
     77     if (!args.ignore_guardrails)
     78       return false;
     79   }
     80 
     81   // If we've uploaded in the last 5mins we shouldn't trace now.
     82   if ((now_in_s - state_.last_trace_timestamp()) < kCooldownInSeconds) {
     83     PERFETTO_ELOG("Guardrail: Uploaded to DropBox in the last 5mins.");
     84     if (!args.ignore_guardrails)
     85       return false;
     86   }
     87 
     88   // First trace was more than 24h ago? Reset state.
     89   if ((now_in_s - state_.first_trace_timestamp()) >
     90       kMaxUploadResetPeriodInSeconds) {
     91     state_.set_first_trace_timestamp(0);
     92     state_.set_last_trace_timestamp(0);
     93     state_.set_total_bytes_uploaded(0);
     94     return true;
     95   }
     96 
     97   // If we've uploaded more than 10mb in the last 24 hours we shouldn't trace
     98   // now.
     99   uint64_t max_upload_guardrail = args.max_upload_bytes_override > 0
    100                                       ? args.max_upload_bytes_override
    101                                       : kMaxUploadInBytes;
    102   if (state_.total_bytes_uploaded() > max_upload_guardrail) {
    103     PERFETTO_ELOG("Guardrail: Uploaded >10mb DropBox in the last 24h.");
    104     if (!args.ignore_guardrails)
    105       return false;
    106   }
    107 
    108   return true;
    109 }
    110 
    111 bool RateLimiter::OnTraceDone(const Args& args, bool success, size_t bytes) {
    112   uint64_t now_in_s = static_cast<uint64_t>(args.current_time.count());
    113 
    114   // Failed to upload? Don't update the state.
    115   if (!success)
    116     return false;
    117 
    118   if (!args.is_dropbox)
    119     return true;
    120 
    121   // If the first trace timestamp is 0 (either because this is the
    122   // first time or because it was reset for being more than 24h ago).
    123   // -> We update it to the time of this trace.
    124   if (state_.first_trace_timestamp() == 0)
    125     state_.set_first_trace_timestamp(now_in_s);
    126   // Always updated the last trace timestamp.
    127   state_.set_last_trace_timestamp(now_in_s);
    128   // Add the amount we uploaded to the running total.
    129   state_.set_total_bytes_uploaded(state_.total_bytes_uploaded() + bytes);
    130 
    131   if (!SaveState(state_)) {
    132     PERFETTO_ELOG("Failed to save state.");
    133     return false;
    134   }
    135 
    136   return true;
    137 }
    138 
    139 std::string RateLimiter::GetStateFilePath() const {
    140   return std::string(kTempDropBoxTraceDir) + "/.guardraildata";
    141 }
    142 
    143 bool RateLimiter::StateFileExists() {
    144   struct stat out;
    145   return stat(GetStateFilePath().c_str(), &out) != -1;
    146 }
    147 
    148 bool RateLimiter::ClearState() {
    149   PerfettoCmdState zero{};
    150   zero.set_total_bytes_uploaded(0);
    151   zero.set_last_trace_timestamp(0);
    152   zero.set_first_trace_timestamp(0);
    153   bool success = SaveState(zero);
    154   if (!success && StateFileExists())
    155     remove(GetStateFilePath().c_str());
    156   return success;
    157 }
    158 
    159 bool RateLimiter::LoadState(PerfettoCmdState* state) {
    160   base::ScopedFile in_fd;
    161   in_fd.reset(open(GetStateFilePath().c_str(), O_RDONLY));
    162 
    163   if (!in_fd)
    164     return false;
    165   char buf[1024];
    166   ssize_t bytes = PERFETTO_EINTR(read(in_fd.get(), &buf, sizeof(buf)));
    167   if (bytes <= 0)
    168     return false;
    169   return state->ParseFromArray(&buf, static_cast<int>(bytes));
    170 }
    171 
    172 bool RateLimiter::SaveState(const PerfettoCmdState& state) {
    173   base::ScopedFile out_fd;
    174   out_fd.reset(
    175       open(GetStateFilePath().c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0600));
    176   if (!out_fd)
    177     return false;
    178   char buf[1024];
    179   size_t size = static_cast<size_t>(state.ByteSize());
    180   PERFETTO_CHECK(size < sizeof(buf));
    181   if (!state.SerializeToArray(&buf, static_cast<int>(size)))
    182     return false;
    183   ssize_t written = PERFETTO_EINTR(write(out_fd.get(), &buf, size));
    184   return written >= 0 && static_cast<size_t>(written) == size;
    185 }
    186 
    187 }  // namespace perfetto
    188