blob: 1e2ef4f1e8febb12593150291d04cd59b351ea0d [file] [log] [blame]
Hector Dearman86cfbe12018-03-22 11:58:42 +00001/*
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
Primiano Tucci3cbb10a2018-04-10 17:52:40 +010017#include "src/perfetto_cmd/rate_limiter.h"
Hector Dearman86cfbe12018-03-22 11:58:42 +000018
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) {
Primiano Tucci3cbb10a2018-04-10 17:52:40 +010046 uint64_t now_in_s = static_cast<uint64_t>(args.current_time.count());
Hector Dearman86cfbe12018-03-22 11:58:42 +000047
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.
Lalit Maganti41844ed2018-05-23 14:41:43 +010099 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) {
Hector Dearman86cfbe12018-03-22 11:58:42 +0000103 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) {
Primiano Tucci3cbb10a2018-04-10 17:52:40 +0100112 uint64_t now_in_s = static_cast<uint64_t>(args.current_time.count());
Hector Dearman86cfbe12018-03-22 11:58:42 +0000113
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;
Primiano Tucci3cbb10a2018-04-10 17:52:40 +0100169 return state->ParseFromArray(&buf, static_cast<int>(bytes));
Hector Dearman86cfbe12018-03-22 11:58:42 +0000170}
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];
Primiano Tucci3cbb10a2018-04-10 17:52:40 +0100179 size_t size = static_cast<size_t>(state.ByteSize());
180 PERFETTO_CHECK(size < sizeof(buf));
181 if (!state.SerializeToArray(&buf, static_cast<int>(size)))
Hector Dearman86cfbe12018-03-22 11:58:42 +0000182 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