• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 <fcntl.h>
20 #include <inttypes.h>
21 #include <sys/stat.h>
22 #include <sys/types.h>
23 
24 #include <algorithm>
25 
26 #include "perfetto/base/logging.h"
27 #include "perfetto/ext/base/file_utils.h"
28 #include "perfetto/ext/base/scoped_file.h"
29 #include "perfetto/ext/base/utils.h"
30 #include "src/perfetto_cmd/perfetto_cmd.h"
31 
32 namespace perfetto {
33 namespace {
34 
35 // Every 24 hours we reset how much we've uploaded.
36 const uint64_t kMaxUploadResetPeriodInSeconds = 60 * 60 * 24;
37 
38 // Maximum of 10mb every 24h.
39 const uint64_t kMaxUploadInBytes = 1024 * 1024 * 10;
40 
41 // Keep track of last 10 observed sessions.
42 const uint64_t kMaxSessionsInHistory = 10;
43 
44 }  // namespace
45 
46 using PerSessionState = gen::PerfettoCmdState::PerSessionState;
47 
48 RateLimiter::RateLimiter() = default;
49 RateLimiter::~RateLimiter() = default;
50 
ShouldTrace(const Args & args)51 RateLimiter::ShouldTraceResponse RateLimiter::ShouldTrace(const Args& args) {
52   uint64_t now_in_s = static_cast<uint64_t>(args.current_time.count());
53 
54   // Not uploading?
55   // -> We can just trace.
56   if (!args.is_uploading)
57     return ShouldTraceResponse::kOkToTrace;
58 
59   // If we're tracing a user build we should only trace if the override in
60   // the config is set:
61   if (args.is_user_build && !args.allow_user_build_tracing) {
62     PERFETTO_ELOG(
63         "Guardrail: allow_user_build_tracing must be set to trace on user "
64         "builds");
65     return ShouldTraceResponse::kNotAllowedOnUserBuild;
66   }
67 
68   // The state file is gone.
69   // Maybe we're tracing for the first time or maybe something went wrong the
70   // last time we tried to save the state. Either way reinitialize the state
71   // file.
72   if (!StateFileExists()) {
73     // We can't write the empty state file?
74     // -> Give up.
75     if (!ClearState()) {
76       PERFETTO_ELOG("Guardrail: failed to initialize guardrail state.");
77       return ShouldTraceResponse::kFailedToInitState;
78     }
79   }
80 
81   bool loaded_state = LoadState(&state_);
82 
83   // Failed to load the state?
84   // Current time is before either saved times?
85   // Last saved trace time is before first saved trace time?
86   // -> Try to save a clean state but don't trace.
87   if (!loaded_state || now_in_s < state_.first_trace_timestamp() ||
88       now_in_s < state_.last_trace_timestamp() ||
89       state_.last_trace_timestamp() < state_.first_trace_timestamp()) {
90     ClearState();
91     PERFETTO_ELOG("Guardrail: state invalid, clearing it.");
92     if (!args.ignore_guardrails)
93       return ShouldTraceResponse::kInvalidState;
94   }
95 
96   // First trace was more than 24h ago? Reset state.
97   if ((now_in_s - state_.first_trace_timestamp()) >
98       kMaxUploadResetPeriodInSeconds) {
99     state_.set_first_trace_timestamp(0);
100     state_.set_last_trace_timestamp(0);
101     state_.set_total_bytes_uploaded(0);
102     return ShouldTraceResponse::kOkToTrace;
103   }
104 
105   uint64_t max_upload_guardrail = kMaxUploadInBytes;
106   if (args.max_upload_bytes_override > 0) {
107     if (args.unique_session_name.empty()) {
108       PERFETTO_ELOG(
109           "Ignoring max_upload_per_day_bytes override as unique_session_name "
110           "not set");
111     } else {
112       max_upload_guardrail = args.max_upload_bytes_override;
113     }
114   }
115 
116   uint64_t uploaded_so_far = state_.total_bytes_uploaded();
117   if (!args.unique_session_name.empty()) {
118     uploaded_so_far = 0;
119     for (const auto& session_state : state_.session_state()) {
120       if (session_state.session_name() == args.unique_session_name) {
121         uploaded_so_far = session_state.total_bytes_uploaded();
122         break;
123       }
124     }
125   }
126 
127   if (uploaded_so_far > max_upload_guardrail) {
128     PERFETTO_ELOG("Guardrail: Uploaded %" PRIu64
129                   " in the last 24h. Limit is %" PRIu64 ".",
130                   uploaded_so_far, max_upload_guardrail);
131     if (!args.ignore_guardrails)
132       return ShouldTraceResponse::kHitUploadLimit;
133   }
134 
135   return ShouldTraceResponse::kOkToTrace;
136 }
137 
OnTraceDone(const Args & args,bool success,uint64_t bytes)138 bool RateLimiter::OnTraceDone(const Args& args, bool success, uint64_t bytes) {
139   uint64_t now_in_s = static_cast<uint64_t>(args.current_time.count());
140 
141   // Failed to upload? Don't update the state.
142   if (!success)
143     return false;
144 
145   if (!args.is_uploading)
146     return true;
147 
148   // If the first trace timestamp is 0 (either because this is the
149   // first time or because it was reset for being more than 24h ago).
150   // -> We update it to the time of this trace.
151   if (state_.first_trace_timestamp() == 0)
152     state_.set_first_trace_timestamp(now_in_s);
153   // Always updated the last trace timestamp.
154   state_.set_last_trace_timestamp(now_in_s);
155 
156   if (args.unique_session_name.empty()) {
157     // Add the amount we uploaded to the running total.
158     state_.set_total_bytes_uploaded(state_.total_bytes_uploaded() + bytes);
159   } else {
160     PerSessionState* target_session = nullptr;
161     for (PerSessionState& session : *state_.mutable_session_state()) {
162       if (session.session_name() == args.unique_session_name) {
163         target_session = &session;
164         break;
165       }
166     }
167     if (!target_session) {
168       target_session = state_.add_session_state();
169       target_session->set_session_name(args.unique_session_name);
170     }
171     target_session->set_total_bytes_uploaded(
172         target_session->total_bytes_uploaded() + bytes);
173     target_session->set_last_trace_timestamp(now_in_s);
174   }
175 
176   if (!SaveState(state_)) {
177     PERFETTO_ELOG("Failed to save state.");
178     return false;
179   }
180 
181   return true;
182 }
183 
GetStateFilePath() const184 std::string RateLimiter::GetStateFilePath() const {
185   return std::string(kStateDir) + "/.guardraildata";
186 }
187 
StateFileExists()188 bool RateLimiter::StateFileExists() {
189   struct stat out;
190   return stat(GetStateFilePath().c_str(), &out) != -1;
191 }
192 
ClearState()193 bool RateLimiter::ClearState() {
194   gen::PerfettoCmdState zero{};
195   zero.set_total_bytes_uploaded(0);
196   zero.set_last_trace_timestamp(0);
197   zero.set_first_trace_timestamp(0);
198   bool success = SaveState(zero);
199   if (!success && StateFileExists())
200     remove(GetStateFilePath().c_str());
201   return success;
202 }
203 
LoadState(gen::PerfettoCmdState * state)204 bool RateLimiter::LoadState(gen::PerfettoCmdState* state) {
205   base::ScopedFile in_fd(base::OpenFile(GetStateFilePath(), O_RDONLY));
206   if (!in_fd)
207     return false;
208   std::string s;
209   base::ReadFileDescriptor(in_fd.get(), &s);
210   if (s.empty())
211     return false;
212   return state->ParseFromString(s);
213 }
214 
SaveState(const gen::PerfettoCmdState & input_state)215 bool RateLimiter::SaveState(const gen::PerfettoCmdState& input_state) {
216   gen::PerfettoCmdState state = input_state;
217 
218   // Keep only the N most recent per session states so the file doesn't
219   // grow indefinitely:
220   std::vector<PerSessionState>* sessions = state.mutable_session_state();
221   std::sort(sessions->begin(), sessions->end(),
222             [](const PerSessionState& a, const PerSessionState& b) {
223               return a.last_trace_timestamp() > b.last_trace_timestamp();
224             });
225   if (sessions->size() > kMaxSessionsInHistory) {
226     sessions->resize(kMaxSessionsInHistory);
227   }
228 
229   // Rationale for 0666: the cmdline client can be executed under two
230   // different Unix UIDs: shell and statsd. If we run one after the
231   // other and the file has 0600 permissions, then the 2nd run won't
232   // be able to read the file and will clear it, aborting the trace.
233   // SELinux still prevents that anything other than the perfetto
234   // executable can change the guardrail file.
235   std::vector<uint8_t> buf = state.SerializeAsArray();
236   base::ScopedFile out_fd(
237       base::OpenFile(GetStateFilePath(), O_WRONLY | O_CREAT | O_TRUNC, 0666));
238   if (!out_fd)
239     return false;
240   ssize_t written = base::WriteAll(out_fd.get(), buf.data(), buf.size());
241   return written >= 0 && static_cast<size_t>(written) == buf.size();
242 }
243 
244 }  // namespace perfetto
245