@@ -3,11 +3,56 @@ use crate::handlers::Context;
3
3
use anyhow:: Context as _;
4
4
use hyper:: header:: { CONTENT_SECURITY_POLICY , CONTENT_TYPE } ;
5
5
use hyper:: { Body , Response , StatusCode } ;
6
+ use std:: collections:: VecDeque ;
6
7
use std:: str:: FromStr ;
7
8
use std:: sync:: Arc ;
8
9
use uuid:: Uuid ;
9
10
10
11
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
+ }
11
56
12
57
pub async fn gha_logs (
13
58
ctx : Arc < Context > ,
@@ -52,27 +97,42 @@ async fn process_logs(
52
97
anyhow:: bail!( "Repository `{repo}` is not part of team repos" ) ;
53
98
}
54
99
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
+ }
66
107
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
+ } ;
68
128
69
129
let nonce = Uuid :: new_v4 ( ) . to_hyphenated ( ) . to_string ( ) ;
70
130
71
131
let html = format ! (
72
132
r#"<!DOCTYPE html>
73
133
<html>
74
134
<head>
75
- <title>{owner}/{repo}${log_id } - triagebot</title>
135
+ <title>{log_uuid } - triagebot</title>
76
136
<meta charset="UTF-8">
77
137
<meta name="viewport" content="width=device-width, initial-scale=1.0">
78
138
<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(
87
147
<script type="module" nonce="{nonce}">
88
148
import {{ AnsiUp }} from '{ANSI_UP_URL}'
89
149
90
- var logs = {json_logs };
150
+ var logs = {logs };
91
151
var ansi_up = new AnsiUp();
92
152
93
153
var html = ansi_up.ansi_to_html(logs);
@@ -101,7 +161,7 @@ async fn process_logs(
101
161
</html>"# ,
102
162
) ;
103
163
104
- tracing:: info!( "gha_logs: serving logs for {owner}/{repo}#{log_id }" ) ;
164
+ tracing:: info!( "gha_logs: serving logs for {log_uuid }" ) ;
105
165
106
166
return Ok ( Response :: builder ( )
107
167
. status ( StatusCode :: OK )
0 commit comments