Skip to content

Commit fe64f97

Browse files
committed
Add simple cache to gha_logs endpoint
1 parent 8800ee6 commit fe64f97

File tree

4 files changed

+80
-15
lines changed

4 files changed

+80
-15
lines changed

src/gha_logs.rs

Lines changed: 75 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,56 @@ use crate::handlers::Context;
33
use anyhow::Context as _;
44
use hyper::header::{CONTENT_SECURITY_POLICY, CONTENT_TYPE};
55
use hyper::{Body, Response, StatusCode};
6+
use std::collections::VecDeque;
67
use std::str::FromStr;
78
use std::sync::Arc;
89
use uuid::Uuid;
910

1011
const ANSI_UP_URL: &str = "https://cdn.jsdelivr.net/npm/ansi_up@6.0.6/+esm";
12+
const MAX_CACHE_CAPACITY_BYTES: u64 = 50 * 1024 * 1024; // 50 Mb
13+
14+
#[derive(Default)]
15+
pub struct GitHubActionLogsCache {
16+
capacity: u64,
17+
entries: VecDeque<(String, Arc<String>)>,
18+
}
19+
20+
impl GitHubActionLogsCache {
21+
pub fn get(&mut self, key: &String) -> Option<Arc<String>> {
22+
if let Some(pos) = self.entries.iter().position(|(k, _)| k == key) {
23+
// Move previously cached entry to the front
24+
let entry = self.entries.remove(pos).unwrap();
25+
self.entries.push_front(entry.clone());
26+
Some(entry.1)
27+
} else {
28+
None
29+
}
30+
}
31+
32+
pub fn put(&mut self, key: String, value: Arc<String>) -> Arc<String> {
33+
if value.len() as u64 > MAX_CACHE_CAPACITY_BYTES {
34+
// Entry is too large, don't cache, return as is
35+
return value;
36+
}
37+
38+
// Remove duplicate or last entry when necessary
39+
let removed = if let Some(pos) = self.entries.iter().position(|(k, _)| k == &key) {
40+
self.entries.remove(pos)
41+
} else if self.capacity + value.len() as u64 >= MAX_CACHE_CAPACITY_BYTES {
42+
self.entries.pop_back()
43+
} else {
44+
None
45+
};
46+
if let Some(removed) = removed {
47+
self.capacity -= removed.1.len() as u64;
48+
}
49+
50+
// Add entry the front of the list ane return it
51+
self.capacity += value.len() as u64;
52+
self.entries.push_front((key, value.clone()));
53+
value
54+
}
55+
}
1156

1257
pub async fn gha_logs(
1358
ctx: Arc<Context>,
@@ -52,27 +97,42 @@ async fn process_logs(
5297
anyhow::bail!("Repository `{repo}` is not part of team repos");
5398
}
5499

55-
let logs = ctx
56-
.github
57-
.raw_job_logs(
58-
&github::IssueRepository {
59-
organization: owner.to_string(),
60-
repository: repo.to_string(),
61-
},
62-
log_id,
63-
)
64-
.await
65-
.context("unable to get the raw logs")?;
100+
let log_uuid = format!("{owner}/{repo}${log_id}");
101+
102+
let logs = 'logs: {
103+
if let Some(logs) = ctx.gha_logs.write().await.get(&log_uuid) {
104+
tracing::info!("gha_logs: cache hit for {log_uuid}");
105+
break 'logs logs;
106+
}
66107

67-
let json_logs = serde_json::to_string(&logs).context("unable to JSON-ify the raw logs")?;
108+
tracing::info!("gha_logs: cache miss for {log_uuid}");
109+
let logs = ctx
110+
.github
111+
.raw_job_logs(
112+
&github::IssueRepository {
113+
organization: owner.to_string(),
114+
repository: repo.to_string(),
115+
},
116+
log_id,
117+
)
118+
.await
119+
.context("unable to get the raw logs")?;
120+
121+
let json_logs = serde_json::to_string(&*logs).context("unable to JSON-ify the raw logs")?;
122+
123+
ctx.gha_logs
124+
.write()
125+
.await
126+
.put(log_uuid.clone(), json_logs.into())
127+
};
68128

69129
let nonce = Uuid::new_v4().to_hyphenated().to_string();
70130

71131
let html = format!(
72132
r#"<!DOCTYPE html>
73133
<html>
74134
<head>
75-
<title>{owner}/{repo}${log_id} - triagebot</title>
135+
<title>{log_uuid} - triagebot</title>
76136
<meta charset="UTF-8">
77137
<meta name="viewport" content="width=device-width, initial-scale=1.0">
78138
<link rel="icon" sizes="32x32" type="image/png" href="https://rust-lang.org/static/images/favicon-32x32.png">
@@ -87,7 +147,7 @@ async fn process_logs(
87147
<script type="module" nonce="{nonce}">
88148
import {{ AnsiUp }} from '{ANSI_UP_URL}'
89149
90-
var logs = {json_logs};
150+
var logs = {logs};
91151
var ansi_up = new AnsiUp();
92152
93153
var html = ansi_up.ansi_to_html(logs);
@@ -101,7 +161,7 @@ async fn process_logs(
101161
</html>"#,
102162
);
103163

104-
tracing::info!("gha_logs: serving logs for {owner}/{repo}#{log_id}");
164+
tracing::info!("gha_logs: serving logs for {log_uuid}");
105165

106166
return Ok(Response::builder()
107167
.status(StatusCode::OK)

src/handlers.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::config::{self, Config, ConfigurationError};
2+
use crate::gha_logs::GitHubActionLogsCache;
23
use crate::github::{Event, GithubClient, IssueCommentAction, IssuesAction, IssuesEvent};
34
use crate::handlers::pr_tracking::ReviewerWorkqueue;
45
use crate::team_data::TeamClient;
@@ -380,4 +381,5 @@ pub struct Context {
380381
/// Represents the workqueue (assigned open PRs) of individual reviewers.
381382
/// tokio's RwLock is used to avoid deadlocks, since we run on a single-threaded tokio runtime.
382383
pub workqueue: Arc<tokio::sync::RwLock<ReviewerWorkqueue>>,
384+
pub gha_logs: Arc<tokio::sync::RwLock<GitHubActionLogsCache>>,
383385
}

src/main.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use tokio::{task, time};
1212
use tower::{Service, ServiceExt};
1313
use tracing as log;
1414
use tracing::Instrument;
15+
use triagebot::gha_logs::GitHubActionLogsCache;
1516
use triagebot::handlers::pr_tracking::ReviewerWorkqueue;
1617
use triagebot::handlers::pr_tracking::load_workqueue;
1718
use triagebot::jobs::{
@@ -318,6 +319,7 @@ async fn run_server(addr: SocketAddr) -> anyhow::Result<()> {
318319
team: team_api,
319320
octocrab: oc,
320321
workqueue: Arc::new(RwLock::new(workqueue)),
322+
gha_logs: Arc::new(RwLock::new(GitHubActionLogsCache::default())),
321323
zulip,
322324
});
323325

src/tests/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ impl TestContext {
8282
username: "triagebot-test".to_string(),
8383
octocrab,
8484
workqueue: Arc::new(RwLock::new(Default::default())),
85+
gha_logs: Arc::new(RwLock::new(Default::default())),
8586
};
8687

8788
Self {

0 commit comments

Comments
 (0)