Skip to content

Commit 10b7359

Browse files
lmvasquezgmeta-codesync[bot]
authored andcommitted
modern-sync: compute how far behind the AWS shadow is
Summary: Eyeballing two hashes does not tell you how stale the mirror is. This grabs both master changesets -- the prod tip and the AWS shadow tip -- and diffs their author dates to report the lag in wall-clock terms (e.g. "AWS is 3h 12m behind prod"), or "in sync" when they match. Both changeset ids exist in the prod blobstore (modern sync uploads identical changesets), so the AWS tip can be loaded locally without another round trip. Falls back to "unknown" when either master or the timestamp can't be resolved. Reviewed By: RajivTS Differential Revision: D109581205 fbshipit-source-id: 6aaa7b441837594a0da9f67d9ad7e4220440c80a
1 parent ec2c732 commit 10b7359

2 files changed

Lines changed: 98 additions & 2 deletions

File tree

eden/mononoke/tools/admin/src/commands/modern_sync.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@ pub struct Repo {
6969
#[derive(Subcommand)]
7070
pub enum ModernSyncSubcommand {
7171
/// Compare a repo's synced bookmark between the internal repo and its AWS
72-
/// shadow, plus the latest bookmark movement on each side.
72+
/// shadow: the bookmark and latest movement on each side, and how far the
73+
/// shadow is behind.
7374
Status(StatusArgs),
7475
}
7576

eden/mononoke/tools/admin/src/commands/modern_sync/status.rs

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ use commit_id::print_commit_id;
1818
use context::CoreContext;
1919
use futures::stream::TryStreamExt;
2020
use metaconfig_types::RepoConfigRef;
21+
use mononoke_types::ChangesetId;
22+
use mononoke_types::DateTime;
2123
use repo_identity::RepoIdentityRef;
2224
use tokio::process::Command;
2325

@@ -41,6 +43,10 @@ const AWS_K8S_NAMESPACE: &str = "default";
4143
const AWS_DEPLOYMENT: &str = "mononoke-server";
4244
const AWS_CONTAINER: &str = "server";
4345

46+
// How many recent prod bookmark moves to scan when locating the AWS shadow's
47+
// current changeset to time how far behind it is.
48+
const BEHIND_LOOKBACK: u32 = 1000;
49+
4450
#[derive(Args)]
4551
pub struct StatusArgs {
4652
/// The bookmark modern sync mirrors (it only syncs this one bookmark)
@@ -78,7 +84,15 @@ pub async fn status(ctx: &CoreContext, repo: &Repo, args: StatusArgs) -> Result<
7884
Some(cs_id) => print_commit_id(ctx, repo, BONSAI, cs_id).await?,
7985
None => println!("(not set)"),
8086
}
81-
print_aws_value(kubeconfig_ok, &shadow_repo, &["get", &bookmark]).await;
87+
let aws_master = print_aws_value(kubeconfig_ok, &shadow_repo, &["get", &bookmark]).await;
88+
print_behind(
89+
ctx,
90+
repo,
91+
&args.bookmark,
92+
internal_master,
93+
aws_master.as_deref(),
94+
)
95+
.await?;
8296
println!();
8397

8498
// --- latest movement (internal vs AWS) ---
@@ -134,6 +148,87 @@ async fn print_internal_latest_movement(
134148
Ok(())
135149
}
136150

151+
/// Print how far the AWS shadow's bookmark is behind the internal repo.
152+
///
153+
/// We use the server-assigned `bookmark_update_log` timestamps (monotonic), not
154+
/// a changeset's client-provided author date which can be skewed. The gap is
155+
/// between when the internal repo last moved the bookmark and when it moved the
156+
/// bookmark onto the changeset the shadow currently points at.
157+
async fn print_behind(
158+
ctx: &CoreContext,
159+
repo: &Repo,
160+
bookmark: &BookmarkKey,
161+
internal_master: Option<ChangesetId>,
162+
aws_master_raw: Option<&str>,
163+
) -> Result<()> {
164+
let aws_master = aws_master_raw.and_then(|s| s.parse::<ChangesetId>().ok());
165+
let (Some(internal_master), Some(aws_master)) = (internal_master, aws_master) else {
166+
println!(" behind: unknown (missing a bookmark value)");
167+
return Ok(());
168+
};
169+
if internal_master == aws_master {
170+
println!(" behind: in sync");
171+
return Ok(());
172+
}
173+
174+
// Newest-first list of recent moves of this bookmark, with server timestamps.
175+
let entries: Vec<_> = repo
176+
.bookmark_update_log()
177+
.list_bookmark_log_entries(
178+
ctx.clone(),
179+
bookmark.clone(),
180+
BEHIND_LOOKBACK,
181+
None,
182+
Freshness::MostRecent,
183+
)
184+
.try_collect()
185+
.await
186+
.context("Failed to list bookmark log entries")?;
187+
188+
let internal_secs = entries
189+
.first()
190+
.map(|(_, _, _, ts)| DateTime::from(*ts).timestamp_secs());
191+
let aws_secs = entries
192+
.iter()
193+
.find(|(_, cs_id, _, _)| *cs_id == Some(aws_master))
194+
.map(|(_, _, _, ts)| DateTime::from(*ts).timestamp_secs());
195+
196+
match (internal_secs, aws_secs) {
197+
(Some(now), Some(then)) if now >= then => {
198+
println!(
199+
" behind: AWS is {} behind prod",
200+
human_duration(now - then)
201+
)
202+
}
203+
(Some(now), Some(then)) => println!(
204+
" behind: AWS bookmark is {} newer than prod (?)",
205+
human_duration(then - now)
206+
),
207+
(Some(_), None) => println!(
208+
" behind: AWS changeset not in the last {BEHIND_LOOKBACK} prod moves (very stale or diverged)"
209+
),
210+
_ => println!(" behind: unknown (no bookmark history)"),
211+
}
212+
Ok(())
213+
}
214+
215+
/// Render a non-negative duration in seconds as a coarse "Xd Yh" / "Xh Ym" string.
216+
fn human_duration(secs: i64) -> String {
217+
let days = secs / 86400;
218+
let hours = (secs % 86400) / 3600;
219+
let mins = (secs % 3600) / 60;
220+
let seconds = secs % 60;
221+
if days > 0 {
222+
format!("{days}d {hours}h")
223+
} else if hours > 0 {
224+
format!("{hours}h {mins}m")
225+
} else if mins > 0 {
226+
format!("{mins}m {seconds}s")
227+
} else {
228+
format!("{seconds}s")
229+
}
230+
}
231+
137232
/// Point kubectl at the AWS shadow cluster. Returns false (and prints a note) if
138233
/// the `cloud` CLI is missing or fails, so the rest of the report still shows.
139234
async fn ensure_aws_kubeconfig() -> bool {

0 commit comments

Comments
 (0)