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