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 28namespace perfetto { 29namespace { 30 31// 5 mins between traces. 32const uint64_t kCooldownInSeconds = 60 * 5; 33 34// Every 24 hours we reset how much we've uploaded. 35const uint64_t kMaxUploadResetPeriodInSeconds = 60 * 60 * 24; 36 37// Maximum of 10mb every 24h. 38const uint64_t kMaxUploadInBytes = 1024 * 1024 * 10; 39 40} // namespace 41 42RateLimiter::RateLimiter() = default; 43RateLimiter::~RateLimiter() = default; 44 45bool 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 111bool 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 139std::string RateLimiter::GetStateFilePath() const { 140 return std::string(kTempDropBoxTraceDir) + "/.guardraildata"; 141} 142 143bool RateLimiter::StateFileExists() { 144 struct stat out; 145 return stat(GetStateFilePath().c_str(), &out) != -1; 146} 147 148bool 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 159bool 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 172bool 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