Skip to content

Commit a85fafb

Browse files
committed
Add simple cache to gha_logs endpoint
1 parent 6c42195 commit a85fafb

File tree

4 files changed

+72
-14
lines changed

4 files changed

+72
-14
lines changed

src/gha_logs.rs

Lines changed: 67 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,48 @@ 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/[email protected]/+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+
let entry = self.entries.remove(pos).unwrap();
24+
self.entries.push_front(entry);
25+
Some(&self.entries.front().unwrap().1)
26+
} else {
27+
None
28+
}
29+
}
30+
31+
pub fn put(&mut self, key: String, value: Arc<String>) -> &Arc<String> {
32+
let removed = if let Some(pos) = self.entries.iter().position(|(k, _)| k == &key) {
33+
self.entries.remove(pos)
34+
} else if self.capacity + value.len() as u64 >= MAX_CACHE_CAPACITY_BYTES {
35+
self.entries.pop_back()
36+
} else {
37+
None
38+
};
39+
if let Some(removed) = removed {
40+
self.capacity -= removed.1.len() as u64;
41+
}
42+
// Insert at the front (most recently used)
43+
self.capacity += value.len() as u64;
44+
self.entries.push_front((key, value));
45+
&self.entries.front().unwrap().1
46+
}
47+
}
1148

1249
pub async fn gha_logs(
1350
ctx: Arc<Context>,
@@ -52,27 +89,43 @@ async fn process_logs(
5289
anyhow::bail!("Repository `{repo}` is not part of team repos");
5390
}
5491

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")?;
92+
let log_uuid = format!("{owner}/{repo}${log_id}");
93+
94+
let logs = 'logs: {
95+
if let Some(logs) = ctx.gha_logs.write().await.get(&log_uuid) {
96+
tracing::info!("Cache found for {log_uuid}");
97+
break 'logs logs.clone();
98+
}
99+
100+
tracing::info!("Cache NOT found for {log_uuid}");
101+
let logs = ctx
102+
.github
103+
.raw_job_logs(
104+
&github::IssueRepository {
105+
organization: owner.to_string(),
106+
repository: repo.to_string(),
107+
},
108+
log_id,
109+
)
110+
.await
111+
.context("unable to get the raw logs")?;
112+
113+
ctx.gha_logs
114+
.write()
115+
.await
116+
.put(log_uuid.clone(), logs.into())
117+
.clone()
118+
};
66119

67-
let json_logs = serde_json::to_string(&logs).context("unable to JSON-ify the raw logs")?;
120+
let json_logs = serde_json::to_string(&*logs).context("unable to JSON-ify the raw logs")?;
68121

69122
let nonce = Uuid::new_v4().to_hyphenated().to_string();
70123

71124
let html = format!(
72125
r#"<!DOCTYPE html>
73126
<html>
74127
<head>
75-
<title>{owner}/{repo}${log_id} - triagebot</title>
128+
<title>{log_uuid} - triagebot</title>
76129
<meta charset="UTF-8">
77130
<meta name="viewport" content="width=device-width, initial-scale=1.0">
78131
<link rel="icon" sizes="32x32" type="image/png" href="https://rust-lang.org/static/images/favicon-32x32.png">
@@ -101,7 +154,7 @@ async fn process_logs(
101154
</html>"#,
102155
);
103156

104-
tracing::info!("Serving GHA Logs for {owner}/{repo}#{log_id}");
157+
tracing::info!("Serving GHA Logs for {log_uuid}");
105158

106159
return Ok(Response::builder()
107160
.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;
@@ -382,4 +383,5 @@ pub struct Context {
382383
/// Represents the workqueue (assigned open PRs) of individual reviewers.
383384
/// tokio's RwLock is used to avoid deadlocks, since we run on a single-threaded tokio runtime.
384385
pub workqueue: Arc<tokio::sync::RwLock<ReviewerWorkqueue>>,
386+
pub gha_logs: Arc<tokio::sync::RwLock<GitHubActionLogsCache>>,
385387
}

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::{
@@ -317,6 +318,7 @@ async fn run_server(addr: SocketAddr) -> anyhow::Result<()> {
317318
team: team_api,
318319
octocrab: oc,
319320
workqueue: Arc::new(RwLock::new(workqueue)),
321+
gha_logs: Arc::new(RwLock::new(GitHubActionLogsCache::default())),
320322
zulip,
321323
});
322324

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)