1- use crate :: { http, http :: is_token_flagged , Config } ;
1+ use crate :: { http, Config } ;
22use anyhow:: Error ;
33use log:: { debug, error, warn} ;
4- use prometheus:: core:: Desc ;
4+ use prometheus:: core:: AtomicI64 ;
5+ use prometheus:: core:: { Desc , GenericGauge } ;
56use prometheus:: proto:: MetricFamily ;
67use prometheus:: { core:: Collector , IntGauge , Opts } ;
8+ use reqwest:: header:: LINK ;
79use std:: collections:: HashMap ;
810use std:: sync:: { Arc , RwLock } ;
911use tokio:: time:: Duration ;
@@ -23,15 +25,6 @@ struct Runner {
2325 os : String ,
2426 status : String ,
2527 busy : bool ,
26- labels : Vec < Label > ,
27- }
28-
29- #[ derive( Debug , serde:: Deserialize ) ]
30- struct Label {
31- id : usize ,
32- name : String ,
33- #[ serde( rename = "type" ) ]
34- the_type : String ,
3528}
3629
3730#[ derive( Clone ) ]
@@ -40,8 +33,6 @@ pub struct GithubRunners {
4033 token : String ,
4134 // repos to track gha runners
4235 repos : Vec < String > ,
43- // metric namespace
44- ns : String ,
4536 // actual metrics
4637 metrics : Arc < RwLock < Vec < IntGauge > > > ,
4738 // default metric description
@@ -57,15 +48,13 @@ impl GithubRunners {
5748 . map ( |v| v. trim ( ) . to_string ( ) )
5849 . collect ( ) ;
5950
60- let ns = String :: from ( "gha_runner" ) ;
6151 let rv = Self {
6252 token,
6353 repos,
64- ns : ns. clone ( ) ,
6554 metrics : Arc :: new ( RwLock :: new ( Vec :: new ( ) ) ) ,
6655 desc : Desc :: new (
67- ns ,
68- "GHA runner's status" . to_string ( ) ,
56+ String :: from ( "gha_runner" ) ,
57+ String :: from ( "GHA runner's status" ) ,
6958 Vec :: new ( ) ,
7059 HashMap :: new ( ) ,
7160 )
@@ -90,63 +79,47 @@ impl GithubRunners {
9079 async fn update_stats ( & mut self ) -> Result < ( ) , Error > {
9180 let mut gauges = Vec :: new ( ) ;
9281 for repo in self . repos . iter ( ) {
93- let url = String :: from ( GH_RUNNERS_ENDPOINT ) . replace ( "{owner_repo}" , repo) ;
82+ let mut url: Option < String > = String :: from ( GH_RUNNERS_ENDPOINT )
83+ . replace ( "{owner_repo}" , repo)
84+ . into ( ) ;
85+
86+ while url. is_some ( ) {
87+ let response = http:: get ( & self . token , & url. unwrap ( ) ) . send ( ) . await ?;
88+ url = match response. headers ( ) . get ( LINK ) {
89+ Some ( data) => {
90+ //TODO: parse Link header for rel=next
91+ warn ! ( "{:#?}" , data) ;
92+ None
93+ }
94+ _ => None ,
95+ } ;
96+
97+ let resp = response. json :: < ApiResponse > ( ) . await ?;
98+ debug ! ( "{:#?}" , resp) ;
99+
100+ for runner in resp. runners . iter ( ) {
101+ let online = metric_factory (
102+ "online" ,
103+ "runner is online" ,
104+ & self . desc . fq_name ,
105+ & repo,
106+ & runner. name ,
107+ ) ;
94108
95- // does this token still able to query github api? (rate limit wise)
96- match is_token_flagged ( & self . token ) . await {
97- Err ( e ) => {
98- error ! ( "checking if token is flagged: {:?}" , e ) ;
99- continue ;
100- }
101- Ok ( true ) => {
102- warn ! (
103- "token: '{}' is currently flagged. skipping data gathering" ,
104- & self . token
109+ online . set ( if runner . status == "online" { 1 } else { 0 } ) ;
110+ gauges . push ( online ) ;
111+
112+ // busy
113+ let busy = metric_factory (
114+ "busy" ,
115+ "runner is busy" ,
116+ & self . desc . fq_name ,
117+ & repo ,
118+ & runner . name ,
105119 ) ;
106- continue ;
120+ busy. set ( if runner. busy { 1 } else { 0 } ) ;
121+ gauges. push ( busy) ;
107122 }
108- Ok ( false ) => { }
109- }
110-
111- debug ! ( "Querying gha runner's status at: {}" , url) ;
112- let resp = http:: get ( & self . token , & url)
113- . send ( )
114- . await ?
115- . json :: < ApiResponse > ( )
116- . await ?;
117-
118- debug ! ( "ApiResponse: {:#?}" , resp) ;
119-
120- // convert to metrics
121- for runner in resp. runners . iter ( ) {
122- let status = & runner. status . clone ( ) ;
123- let value_busy = if runner. busy { 1 } else { 0 } ;
124- let label_repo = repo. clone ( ) ;
125- let label_runner = runner. name . clone ( ) ;
126-
127- // online
128- let online = IntGauge :: with_opts (
129- Opts :: new ( "online" , "runner is online." )
130- . namespace ( self . ns . clone ( ) )
131- . const_label ( "repo" , label_repo. clone ( ) )
132- . const_label ( "runner" , label_runner. clone ( ) ) ,
133- )
134- . unwrap ( ) ;
135-
136- online. set ( if status == "online" { 1 } else { 0 } ) ;
137- gauges. push ( online) ;
138-
139- // busy
140- let busy = IntGauge :: with_opts (
141- Opts :: new ( "busy" , "runner is busy." )
142- . namespace ( self . ns . clone ( ) )
143- . const_label ( "repo" , label_repo)
144- . const_label ( "runner" , label_runner) ,
145- )
146- . unwrap ( ) ;
147-
148- busy. set ( value_busy) ;
149- gauges. push ( busy) ;
150123 }
151124 }
152125
@@ -167,7 +140,7 @@ impl Collector for GithubRunners {
167140 self . metrics . read ( ) . map_or_else (
168141 |e| {
169142 error ! ( "Unable to collect: {:#?}" , e) ;
170- Vec :: new ( )
143+ Vec :: with_capacity ( 0 )
171144 } ,
172145 |guard| {
173146 guard. iter ( ) . fold ( Vec :: new ( ) , |mut acc, item| {
@@ -178,3 +151,19 @@ impl Collector for GithubRunners {
178151 )
179152 }
180153}
154+
155+ fn metric_factory < S : Into < String > > (
156+ name : S ,
157+ help : S ,
158+ ns : S ,
159+ repo : S ,
160+ runner : S ,
161+ ) -> GenericGauge < AtomicI64 > {
162+ IntGauge :: with_opts (
163+ Opts :: new ( name, help)
164+ . namespace ( ns)
165+ . const_label ( "repo" , repo)
166+ . const_label ( "runner" , runner) ,
167+ )
168+ . unwrap ( )
169+ }
0 commit comments