Skip to content

Commit 87b06ee

Browse files
committed
Auto merge of #381 - pietroalbini:restrict-log-size, r=pietroalbini
Restrict the amount of content allowed in build logs This PR implements proper restrictions on the amount of data that can be stored in build logs. Instead of killing the process after it outputted too much data (like what was proposed in #160) this PR just stops recording log entries after the thresholds are reached, outputting a warning at the end. This allows us to keep testing the crates and detect possible regressions. With the current configuration (tweakable through `cargo.toml`) logs are truncated when either of those conditions are reached: * More than 10000 log lines * More than 5MB of data @Mark-Simulacrum or @aidanhs, are you OK with those values or should we tweak them? Fixes #160
2 parents 1754343 + 47d8862 commit 87b06ee

File tree

21 files changed

+197
-21
lines changed

21 files changed

+197
-21
lines changed

config.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ local-crates = []
2929
[sandbox]
3030
# Maximum amount of RAM allowed during builds
3131
memory-limit = "1536M" # 1.5G
32+
# Restrictions on the amount of information stored in build logs
33+
build-log-max-size = "5M"
34+
build-log-max-lines = 10000
3235

3336

3437
# These sections allows to customize how crater treats specific crates/repos

src/agent/results.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::agent::api::AgentApi;
2+
use crate::config::Config;
23
use crate::crates::{Crate, GitHubRepo};
34
use crate::experiments::Experiment;
45
use crate::logs::{self, LogStorage};
@@ -49,12 +50,13 @@ impl<'a> WriteResults for ResultsUploader<'a> {
4950
toolchain: &Toolchain,
5051
krate: &Crate,
5152
existing_logs: Option<LogStorage>,
53+
config: &Config,
5254
f: F,
5355
) -> Fallible<TestResult>
5456
where
5557
F: FnOnce() -> Fallible<TestResult>,
5658
{
57-
let storage = existing_logs.unwrap_or_else(|| LogStorage::new(LevelFilter::Info));
59+
let storage = existing_logs.unwrap_or_else(|| LogStorage::new(LevelFilter::Info, config));
5860
let result = logs::capture(&storage, f)?;
5961
let output = storage.to_string();
6062

src/config.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ pub struct DemoCrates {
6767
#[serde(rename_all = "kebab-case")]
6868
pub struct SandboxConfig {
6969
pub memory_limit: Size,
70+
pub build_log_max_size: Size,
71+
pub build_log_max_lines: usize,
7072
}
7173

7274
#[derive(Clone, Serialize, Deserialize)]
@@ -241,6 +243,8 @@ impl Default for Config {
241243
local_crates: HashMap::new(),
242244
sandbox: SandboxConfig {
243245
memory_limit: Size::Gigabytes(2),
246+
build_log_max_size: Size::Megabytes(1),
247+
build_log_max_lines: 1000,
244248
},
245249
server: ServerConfig {
246250
bot_acl: Vec::new(),
@@ -275,6 +279,8 @@ mod tests {
275279
"local-crates = []\n",
276280
"[sandbox]\n",
277281
"memory-limit = \"2G\"\n",
282+
"build-log-max-size = \"2M\"\n",
283+
"build-log-max-lines = 1000\n",
278284
"[crates]\n",
279285
"lazy_static = { skip = true }\n",
280286
"[github-repos]\n",

src/lib.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
#![recursion_limit = "256"]
2-
#![allow(clippy::needless_pass_by_value, clippy::new_ret_no_self)]
2+
#![allow(
3+
clippy::needless_pass_by_value,
4+
clippy::new_ret_no_self,
5+
clippy::too_many_arguments
6+
)]
37

48
#[cfg_attr(test, macro_use)]
59
extern crate toml;

src/logs/mod.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
mod storage;
22

3-
use crate::prelude::*;
43
use log::{Log, Metadata, Record};
54
use std::cell::RefCell;
65
use std::sync::Once;
@@ -60,9 +59,9 @@ impl Log for MultiLogger {
6059
}
6160
}
6261

63-
pub fn capture<F, R, L>(storage: &L, f: F) -> Fallible<R>
62+
pub fn capture<F, R, L>(storage: &L, f: F) -> R
6463
where
65-
F: FnOnce() -> Fallible<R>,
64+
F: FnOnce() -> R,
6665
L: Log + Clone + 'static,
6766
{
6867
let storage = Box::new(storage.clone());

src/logs/storage.rs

Lines changed: 123 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,40 @@
1+
use crate::config::Config;
12
use log::{Level, LevelFilter, Log, Metadata, Record};
23
use std::fmt;
34
use std::sync::{Arc, Mutex};
45

56
#[derive(Clone)]
7+
#[cfg_attr(test, derive(Debug, PartialEq, Eq))]
68
struct StoredRecord {
79
level: Level,
810
message: String,
911
}
1012

1113
struct InnerStorage {
1214
records: Vec<StoredRecord>,
15+
size: usize,
16+
truncated: bool,
1317
}
1418

1519
#[derive(Clone)]
1620
pub struct LogStorage {
1721
inner: Arc<Mutex<InnerStorage>>,
1822
min_level: LevelFilter,
23+
max_size: usize,
24+
max_lines: usize,
1925
}
2026

2127
impl LogStorage {
22-
pub(crate) fn new(min_level: LevelFilter) -> Self {
28+
pub(crate) fn new(min_level: LevelFilter, config: &Config) -> Self {
2329
LogStorage {
2430
inner: Arc::new(Mutex::new(InnerStorage {
2531
records: Vec::new(),
32+
truncated: false,
33+
size: 0,
2634
})),
2735
min_level,
36+
max_size: config.sandbox.build_log_max_size.to_bytes(),
37+
max_lines: config.sandbox.build_log_max_lines,
2838
}
2939
}
3040

@@ -33,8 +43,12 @@ impl LogStorage {
3343
LogStorage {
3444
inner: Arc::new(Mutex::new(InnerStorage {
3545
records: inner.records.clone(),
46+
truncated: inner.truncated,
47+
size: inner.size,
3648
})),
3749
min_level: self.min_level,
50+
max_size: self.max_size,
51+
max_lines: self.max_lines,
3852
}
3953
}
4054
}
@@ -49,9 +63,30 @@ impl Log for LogStorage {
4963
return;
5064
}
5165
let mut inner = self.inner.lock().unwrap();
66+
if inner.truncated {
67+
return;
68+
}
69+
if inner.records.len() >= self.max_lines {
70+
inner.records.push(StoredRecord {
71+
level: Level::Warn,
72+
message: "too many lines in the log, truncating it".into(),
73+
});
74+
inner.truncated = true;
75+
return;
76+
}
77+
let message = record.args().to_string();
78+
if inner.size + message.len() >= self.max_size {
79+
inner.records.push(StoredRecord {
80+
level: Level::Warn,
81+
message: "too much data in the log, truncating it".into(),
82+
});
83+
inner.truncated = true;
84+
return;
85+
}
86+
inner.size += message.len();
5287
inner.records.push(StoredRecord {
5388
level: record.level(),
54-
message: record.args().to_string(),
89+
message,
5590
});
5691
}
5792

@@ -67,3 +102,89 @@ impl fmt::Display for LogStorage {
67102
Ok(())
68103
}
69104
}
105+
106+
#[cfg(test)]
107+
mod tests {
108+
use super::{LogStorage, StoredRecord};
109+
use crate::config::Config;
110+
use crate::logs;
111+
use crate::prelude::*;
112+
use crate::utils::size::Size;
113+
use log::{Level, LevelFilter};
114+
115+
#[test]
116+
fn test_log_storage() {
117+
logs::init_test();
118+
let config = Config::default();
119+
120+
let storage = LogStorage::new(LevelFilter::Info, &config);
121+
logs::capture(&storage, || {
122+
info!("an info record");
123+
warn!("a warn record");
124+
trace!("a trace record");
125+
});
126+
127+
assert_eq!(
128+
storage.inner.lock().unwrap().records,
129+
vec![
130+
StoredRecord {
131+
level: Level::Info,
132+
message: "an info record".to_string(),
133+
},
134+
StoredRecord {
135+
level: Level::Warn,
136+
message: "a warn record".to_string(),
137+
},
138+
]
139+
);
140+
}
141+
142+
#[test]
143+
fn test_too_much_content() {
144+
logs::init_test();
145+
146+
let mut config = Config::default();
147+
config.sandbox.build_log_max_size = Size::Kilobytes(4);
148+
149+
let storage = LogStorage::new(LevelFilter::Info, &config);
150+
logs::capture(&storage, || {
151+
let content = (0..Size::Kilobytes(8).to_bytes())
152+
.map(|_| '.')
153+
.collect::<String>();
154+
info!("{}", content);
155+
});
156+
157+
let inner = storage.inner.lock().unwrap();
158+
assert_eq!(inner.records.len(), 1);
159+
assert!(inner
160+
.records
161+
.last()
162+
.unwrap()
163+
.message
164+
.contains("too much data"));
165+
}
166+
167+
#[test]
168+
fn test_too_many_lines() {
169+
logs::init_test();
170+
171+
let mut config = Config::default();
172+
config.sandbox.build_log_max_lines = 100;
173+
174+
let storage = LogStorage::new(LevelFilter::Info, &config);
175+
logs::capture(&storage, || {
176+
for _ in 0..200 {
177+
info!("a line");
178+
}
179+
});
180+
181+
let inner = storage.inner.lock().unwrap();
182+
assert_eq!(inner.records.len(), 101);
183+
assert!(inner
184+
.records
185+
.last()
186+
.unwrap()
187+
.message
188+
.contains("too many lines"));
189+
}
190+
}

src/report/archives.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -127,25 +127,25 @@ mod tests {
127127
// Fill some dummy results into the database
128128
let results = DatabaseDB::new(&db);
129129
results
130-
.record_result(&ex, &ex.toolchains[0], &crate1, None, || {
130+
.record_result(&ex, &ex.toolchains[0], &crate1, None, &config, || {
131131
info!("tc1 crate1");
132132
Ok(TestResult::TestPass)
133133
})
134134
.unwrap();
135135
results
136-
.record_result(&ex, &ex.toolchains[1], &crate1, None, || {
136+
.record_result(&ex, &ex.toolchains[1], &crate1, None, &config, || {
137137
info!("tc2 crate1");
138138
Ok(TestResult::BuildFail(FailureReason::Unknown))
139139
})
140140
.unwrap();
141141
results
142-
.record_result(&ex, &ex.toolchains[0], &crate2, None, || {
142+
.record_result(&ex, &ex.toolchains[0], &crate2, None, &config, || {
143143
info!("tc1 crate2");
144144
Ok(TestResult::TestPass)
145145
})
146146
.unwrap();
147147
results
148-
.record_result(&ex, &ex.toolchains[1], &crate2, None, || {
148+
.record_result(&ex, &ex.toolchains[1], &crate2, None, &config, || {
149149
info!("tc2 crate2");
150150
Ok(TestResult::TestPass)
151151
})

src/results/db.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::config::Config;
12
use crate::crates::{Crate, GitHubRepo};
23
use crate::db::{Database, QueryUtils};
34
use crate::experiments::Experiment;
@@ -169,12 +170,13 @@ impl<'a> WriteResults for DatabaseDB<'a> {
169170
toolchain: &Toolchain,
170171
krate: &Crate,
171172
existing_logs: Option<LogStorage>,
173+
config: &Config,
172174
f: F,
173175
) -> Fallible<TestResult>
174176
where
175177
F: FnOnce() -> Fallible<TestResult>,
176178
{
177-
let storage = existing_logs.unwrap_or_else(|| LogStorage::new(LevelFilter::Info));
179+
let storage = existing_logs.unwrap_or_else(|| LogStorage::new(LevelFilter::Info, config));
178180
let result = logs::capture(&storage, f)?;
179181
let output = storage.to_string();
180182
self.store_result(ex, krate, toolchain, result, output.as_bytes())?;
@@ -299,7 +301,7 @@ mod tests {
299301

300302
// Record a result with a message in it
301303
results
302-
.record_result(&ex, &MAIN_TOOLCHAIN, &krate, None, || {
304+
.record_result(&ex, &MAIN_TOOLCHAIN, &krate, None, &config, || {
303305
info!("hello world");
304306
Ok(TestResult::TestPass)
305307
})
@@ -336,7 +338,7 @@ mod tests {
336338

337339
// Add another result
338340
results
339-
.record_result(&ex, &TEST_TOOLCHAIN, &krate, None, || {
341+
.record_result(&ex, &TEST_TOOLCHAIN, &krate, None, &config, || {
340342
info!("Another log message!");
341343
Ok(TestResult::TestFail(FailureReason::Unknown))
342344
})

src/results/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ mod db;
22
#[cfg(test)]
33
mod dummy;
44

5+
use crate::config::Config;
56
use crate::crates::{Crate, GitHubRepo};
67
use crate::experiments::Experiment;
78
use crate::logs::LogStorage;
@@ -43,6 +44,7 @@ pub trait WriteResults {
4344
toolchain: &Toolchain,
4445
krate: &Crate,
4546
existing_logs: Option<LogStorage>,
47+
config: &Config,
4648
f: F,
4749
) -> Fallible<TestResult>
4850
where

src/runner/graph.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ impl TasksGraph {
191191
ex: &Experiment,
192192
db: &DB,
193193
state: &RunnerState,
194+
config: &Config,
194195
error: &F,
195196
result: TestResult,
196197
) -> Fallible<()> {
@@ -199,11 +200,13 @@ impl TasksGraph {
199200
.neighbors_directed(node, Direction::Incoming)
200201
.collect::<Vec<_>>();
201202
for child in children.drain(..) {
202-
self.mark_as_failed(child, ex, db, state, error, result)?;
203+
self.mark_as_failed(child, ex, db, state, config, error, result)?;
203204
}
204205

205206
match self.graph[node] {
206-
Node::Task { ref task, .. } => task.mark_as_failed(ex, db, state, error, result)?,
207+
Node::Task { ref task, .. } => {
208+
task.mark_as_failed(ex, db, state, config, error, result)?
209+
}
207210
Node::CrateCompleted | Node::Root => return Ok(()),
208211
}
209212

0 commit comments

Comments
 (0)