11use futures:: StreamExt ;
22use std:: fmt;
33use std:: sync:: Arc ;
4+ use std:: time:: Duration ;
45use tokio:: time:: Instant ;
56
67use arc_swap:: ArcSwap ;
@@ -66,7 +67,7 @@ impl ProjectStore {
6667 /// [`ProjectState`] is still pending or already deemed expired.
6768 #[ must_use = "an incomplete fetch must be retried" ]
6869 pub fn complete_fetch ( & mut self , fetch : CompletedFetch , config : & Config ) -> Option < Fetch > {
69- let project_key = fetch. project_key ;
70+ let project_key = fetch. project_key ( ) ;
7071
7172 // Eviction is not possible for projects which are currently being fetched.
7273 // Hence if there was a started fetch, the project state must always exist at this stage.
@@ -333,10 +334,7 @@ impl Fetch {
333334
334335 /// Completes the fetch with a result and returns a [`CompletedFetch`].
335336 pub fn complete ( self , state : SourceProjectState ) -> CompletedFetch {
336- CompletedFetch {
337- project_key : self . project_key ,
338- state,
339- }
337+ CompletedFetch { fetch : self , state }
340338 }
341339
342340 fn with_revision ( mut self , revision : Revision ) -> Self {
@@ -349,14 +347,45 @@ impl Fetch {
349347#[ must_use = "a completed fetch must be acted upon" ]
350348#[ derive( Debug ) ]
351349pub struct CompletedFetch {
352- project_key : ProjectKey ,
350+ fetch : Fetch ,
353351 state : SourceProjectState ,
354352}
355353
356354impl CompletedFetch {
357355 /// Returns the [`ProjectKey`] of the project which was fetched.
358356 pub fn project_key ( & self ) -> ProjectKey {
359- self . project_key
357+ self . fetch . project_key
358+ }
359+
360+ /// Returns the update latency of the fetched project config from the upstream.
361+ ///
362+ /// Is `None`, when no project config could be fetched, or if this was the first
363+ /// fetch of a project config.
364+ ///
365+ /// Note: this latency is computed on access, it does not use the time when the [`Fetch`]
366+ /// was marked as (completed)[`Fetch::complete`].
367+ pub fn latency ( & self ) -> Option < Duration > {
368+ // We're not interested in initial fetches. The latency on the first fetch
369+ // has no meaning about how long it takes for an updated project config to be
370+ // propagated to a Relay.
371+ let is_first_fetch = self . fetch . revision ( ) . as_str ( ) . is_none ( ) ;
372+ if is_first_fetch {
373+ return None ;
374+ }
375+
376+ let project_info = match & self . state {
377+ SourceProjectState :: New ( ProjectState :: Enabled ( project_info) ) => project_info,
378+ // Not modified or deleted/disabled -> no latency to track.
379+ _ => return None ,
380+ } ;
381+
382+ // A matching revision is not an update.
383+ if project_info. rev == self . fetch . revision ( ) {
384+ return None ;
385+ }
386+
387+ let elapsed = chrono:: Utc :: now ( ) - project_info. last_change ?;
388+ elapsed. to_std ( ) . ok ( )
360389 }
361390
362391 /// Returns `true` if the fetch completed with a pending status.
0 commit comments