• 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 <sys/stat.h>
20 #include <sys/types.h>
21 #include <unistd.h>
22 
23 #include "perfetto/base/file_utils.h"
24 #include "perfetto/base/logging.h"
25 #include "perfetto/base/scoped_file.h"
26 #include "perfetto/base/utils.h"
27 #include "src/perfetto_cmd/perfetto_cmd.h"
28 
29 #if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
30 #include <sys/system_properties.h>
31 #endif  // PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
32 
33 namespace perfetto {
34 namespace {
35 
36 // 5 mins between traces.
37 const uint64_t kCooldownInSeconds = 60 * 5;
38 
39 // Every 24 hours we reset how much we've uploaded.
40 const uint64_t kMaxUploadResetPeriodInSeconds = 60 * 60 * 24;
41 
42 // Maximum of 10mb every 24h.
43 const uint64_t kMaxUploadInBytes = 1024 * 1024 * 10;
44 
IsUserBuild()45 bool IsUserBuild() {
46 #if PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD)
47   char value[PROP_VALUE_MAX];
48   if (!__system_property_get("ro.build.type", value)) {
49     PERFETTO_ELOG("Unable to read ro.build.type: assuming user build");
50     return true;
51   }
52   return strcmp(value, "user") == 0;
53 #else
54   return false;
55 #endif  // PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD)
56 }
57 
58 }  // namespace
59 
60 RateLimiter::RateLimiter() = default;
61 RateLimiter::~RateLimiter() = default;
62 
ShouldTrace(const Args & args)63 bool RateLimiter::ShouldTrace(const Args& args) {
64   uint64_t now_in_s = static_cast<uint64_t>(args.current_time.count());
65 
66   // Not storing in Dropbox?
67   // -> We can just trace.
68   if (!args.is_dropbox)
69     return true;
70 
71   // If we're tracing a user build we should only trace if the override in
72   // the config is set:
73   if (IsUserBuild() && !args.allow_user_build_tracing) {
74     PERFETTO_ELOG(
75         "Guardrail: allow_user_build_tracing must be set to trace on user "
76         "builds");
77     return false;
78   }
79 
80   // The state file is gone.
81   // Maybe we're tracing for the first time or maybe something went wrong the
82   // last time we tried to save the state. Either way reinitialize the state
83   // file.
84   if (!StateFileExists()) {
85     // We can't write the empty state file?
86     // -> Give up.
87     if (!ClearState()) {
88       PERFETTO_ELOG("Guardrail: failed to initialize guardrail state.");
89       return false;
90     }
91   }
92 
93   bool loaded_state = LoadState(&state_);
94 
95   // Failed to load the state?
96   // Current time is before either saved times?
97   // Last saved trace time is before first saved trace time?
98   // -> Try to save a clean state but don't trace.
99   if (!loaded_state || now_in_s < state_.first_trace_timestamp() ||
100       now_in_s < state_.last_trace_timestamp() ||
101       state_.last_trace_timestamp() < state_.first_trace_timestamp()) {
102     ClearState();
103     PERFETTO_ELOG("Guardrail: state invalid, clearing it.");
104     if (!args.ignore_guardrails)
105       return false;
106   }
107 
108   // If we've uploaded in the last 5mins we shouldn't trace now.
109   if ((now_in_s - state_.last_trace_timestamp()) < kCooldownInSeconds) {
110     PERFETTO_ELOG("Guardrail: Uploaded to DropBox in the last 5mins.");
111     if (!args.ignore_guardrails)
112       return false;
113   }
114 
115   // First trace was more than 24h ago? Reset state.
116   if ((now_in_s - state_.first_trace_timestamp()) >
117       kMaxUploadResetPeriodInSeconds) {
118     state_.set_first_trace_timestamp(0);
119     state_.set_last_trace_timestamp(0);
120     state_.set_total_bytes_uploaded(0);
121     return true;
122   }
123 
124   // If we've uploaded more than 10mb in the last 24 hours we shouldn't trace
125   // now.
126   uint64_t max_upload_guardrail = args.max_upload_bytes_override > 0
127                                       ? args.max_upload_bytes_override
128                                       : kMaxUploadInBytes;
129   if (state_.total_bytes_uploaded() > max_upload_guardrail) {
130     PERFETTO_ELOG("Guardrail: Uploaded >10mb DropBox in the last 24h.");
131     if (!args.ignore_guardrails)
132       return false;
133   }
134 
135   return true;
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_dropbox)
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   // Add the amount we uploaded to the running total.
156   state_.set_total_bytes_uploaded(state_.total_bytes_uploaded() + bytes);
157 
158   if (!SaveState(state_)) {
159     PERFETTO_ELOG("Failed to save state.");
160     return false;
161   }
162 
163   return true;
164 }
165 
GetStateFilePath() const166 std::string RateLimiter::GetStateFilePath() const {
167   return std::string(kTempDropBoxTraceDir) + "/.guardraildata";
168 }
169 
StateFileExists()170 bool RateLimiter::StateFileExists() {
171   struct stat out;
172   return stat(GetStateFilePath().c_str(), &out) != -1;
173 }
174 
ClearState()175 bool RateLimiter::ClearState() {
176   PerfettoCmdState zero{};
177   zero.set_total_bytes_uploaded(0);
178   zero.set_last_trace_timestamp(0);
179   zero.set_first_trace_timestamp(0);
180   bool success = SaveState(zero);
181   if (!success && StateFileExists())
182     remove(GetStateFilePath().c_str());
183   return success;
184 }
185 
LoadState(PerfettoCmdState * state)186 bool RateLimiter::LoadState(PerfettoCmdState* state) {
187   base::ScopedFile in_fd(base::OpenFile(GetStateFilePath(), O_RDONLY));
188 
189   if (!in_fd)
190     return false;
191   char buf[1024];
192   ssize_t bytes = PERFETTO_EINTR(read(in_fd.get(), &buf, sizeof(buf)));
193   if (bytes <= 0)
194     return false;
195   return state->ParseFromArray(&buf, static_cast<int>(bytes));
196 }
197 
SaveState(const PerfettoCmdState & state)198 bool RateLimiter::SaveState(const PerfettoCmdState& state) {
199   // Rationale for 0666: the cmdline client can be executed under two
200   // different Unix UIDs: shell and statsd. If we run one after the
201   // other and the file has 0600 permissions, then the 2nd run won't
202   // be able to read the file and will clear it, aborting the trace.
203   // SELinux still prevents that anything other than the perfetto
204   // executable can change the guardrail file.
205   base::ScopedFile out_fd(
206       base::OpenFile(GetStateFilePath(), O_WRONLY | O_CREAT | O_TRUNC, 0666));
207   if (!out_fd)
208     return false;
209   char buf[1024];
210   size_t size = static_cast<size_t>(state.ByteSize());
211   PERFETTO_CHECK(size < sizeof(buf));
212   if (!state.SerializeToArray(&buf, static_cast<int>(size)))
213     return false;
214   ssize_t written = base::WriteAll(out_fd.get(), &buf, size);
215   return written >= 0 && static_cast<size_t>(written) == size;
216 }
217 
218 }  // namespace perfetto
219