In a distributed SQL database like YugabyteDB, clock synchronization across nodes isn’t just a nice-to-have, it’s critical. Coordinated timestamps underpin transaction consistency, hybrid time calculations, and correct behavior of features like snapshot isolation and conflict resolution.
YugabyteDB offers a handy built-in view for this: the /tablet-server-clocks page on the master UI. Typically accessible via http://<yb-master>:7000/tablet-server-clocks, this page displays clock-related metrics across all TServers in the cluster:
While this information is incredibly useful, sometimes you want this data at your fingertips, say, from the command line, in a script, or as part of your automation pipeline.
Below is a minimal and dependency-free Bash script that scrapes this view from the master UI, extracts clock info for each TServer then sorts the output by RTT to help you spot outliers.
🆕 UPDATE:
Recent YugabyteDB releases updated the /tablet-server-clocks endpoint to use a single Placement column instead of separate Cloud/Region/Zone fields. The script has been updated accordingly to parse the new format and maintain correct alignment.
yb_tablet_server_clocks.sh.
#!/bin/bash
HOST=${1:-127.0.0.1}
PORT=7000
URL="http://$HOST:$PORT/tablet-server-clocks"
echo "📡 YugabyteDB Tablet Server Clocks - $HOST"
echo ""
# Fetch and flatten HTML
html=$(curl -s --connect-timeout 5 "$URL")
table=$(echo "$html" | tr '\n' ' ' | grep -oP '(?<=Tablet Servers
).*?(?=)')
if [[ -z "$table" ]]; then
echo "❌ No Tablet Servers table found at $URL"
exit 1
fi
# Grab each row
rows=$(echo "$table" | grep -oP ' .*? ')
# One format string for header + rows to keep columns perfectly aligned
FORMAT="%-21s %-32s %-14s %-16s %-26s %-44s %-10s %-18s %-12s %-8s\n"
# Header
printf "$FORMAT" \
"IP_Address" "UUID" "Time_Since_HB" "Status_Uptime" \
"Physical_Time_UTC" "Hybrid_Time_UTC" "RTT" "Cloud" "Region" "Zone"
# Use temp file to hold sortable output
tmpfile=$(mktemp)
while read -r row; do
# Extract all cells
td_fields=$(echo "$row" | grep -oP ' ]*>.*? ')
# Skip header or empty rows
[[ -z "$td_fields" ]] && continue
# Strip HTML tags and surrounding whitespace
mapfile -t clean_fields < <(
echo "$td_fields" \
| sed -E 's/<[^>]+>//g' \
| sed 's/^[[:space:]]*//;s/[[:space:]]*$//'
)
# Need at least core fields:
# 0: addr+uuid
# 1: time since hb
# 2: status/uptime
# 3: physical time
# 4: hybrid time
# 5+: RTT (and possibly placement/cloud/region/zone)
if [[ "${#clean_fields[@]}" -lt 6 ]]; then
continue
fi
ip_uuid="${clean_fields[0]}"
ip=$(echo "$ip_uuid" | awk '{print $1}')
uuid=$(echo "$ip_uuid" | awk '{print $2}')
time_since="${clean_fields[1]}"
status_uptime="${clean_fields[2]}"
physical_ts="${clean_fields[3]}"
hybrid_ts="${clean_fields[4]}"
# Detect RTT column (allow negative values too)
rtt_field=""
rtt_index=-1
for i in "${!clean_fields[@]}"; do
if [[ "${clean_fields[$i]}" =~ ^-?[0-9.]+ms$ ]]; then
rtt_field="${clean_fields[$i]}"
rtt_index="$i"
break
fi
done
# Fallback if pattern didn’t match but we have at least 6 fields
if [[ -z "$rtt_field" ]]; then
if [[ "${#clean_fields[@]}" -ge 6 ]]; then
rtt_index=5
rtt_field="${clean_fields[5]}"
else
continue
fi
fi
# ------------------------------
# Placement / Cloud / Region / Zone handling
# ------------------------------
cloud="-"
region="-"
zone="-"
# New-style: a single Placement column like "aws.us-east-1.us-east-1a"
placement=""
if (( rtt_index + 1 < ${#clean_fields[@]} )); then
placement="${clean_fields[rtt_index+1]}"
fi
if [[ -n "$placement" && "$placement" == *.*.* ]]; then
# Split "aws.us-east-1.us-east-1a" -> cloud, region, zone
IFS='.' read -r cloud region zone <<< "$placement"
else
# Old-style (or unexpected): take separate columns if present
if (( rtt_index + 1 < ${#clean_fields[@]} )); then
cloud="${clean_fields[rtt_index+1]}"
fi
if (( rtt_index + 2 < ${#clean_fields[@]} )); then
region="${clean_fields[rtt_index+2]}"
fi
if (( rtt_index + 3 < ${#clean_fields[@]} )); then
zone="${clean_fields[rtt_index+3]}"
fi
fi
# Numeric RTT value for sorting; treat negative RTT as N/A visually but still sortable
rtt_ms=$(echo "$rtt_field" | sed 's/ms//')
if [[ "$rtt_ms" == -* ]]; then
# Dead / bogus RTT -> display N/A, push to bottom in sort
sort_key="999999999"
rtt_display="N/A"
else
sort_key="$rtt_ms"
rtt_display="$rtt_field"
fi
formatted_row=$(printf "$FORMAT" \
"$ip" "$uuid" "$time_since" "$status_uptime" \
"$physical_ts" "$hybrid_ts" "$rtt_display" \
"$cloud" "$region" "$zone")
echo "$sort_key|$formatted_row" >> "$tmpfile"
done <<< "$rows"
# Sort by RTT and print
sort -n -t'|' -k1 "$tmpfile" | cut -d'|' -f2-
# Cleanup
rm -f "$tmpfile"
Make sure that you make it executable!
chmod +x yb_tablet_server_clocks.sh
This script:
• Fetches the
/tablet-server-clockspage from a YB-Master• Parses the HTML (without any external tools)
• Displays a clean, tabular view
• Sorts by Round-Trip Time (RTT) so you can see slowest nodes first
- • Accepts a hostname or IP address as a command-line parameter (defaults to
127.0.0.1if not provided)
👉 Usage:
./yb_tablet_server_clocks.sh [hostname-or-ip]
Sample Output
[ec2-user@ip-10-9-3-89 ~]$ ./yb_tablet_server_clocks.sh 172.161.27.158
📡 YugabyteDB Tablet Server Clocks - 172.161.27.158
IP_Address UUID Time_Since_HB Status_Uptime Physical_Time_UTC Hybrid_Time_UTC RTT Cloud Region Zone
172.161.27.158:9000 87f56d59485b4547a185a2f1e9620be4 0.6s ALIVE: 0:26:33 2025-11-28 16:26:34.917689 2025-11-28 16:26:34.917688 0.45ms aws us-east-2 us-east-2a
172.152.18.7:9000 7fb24ce9295f438984a82acf25b08266 0.2s ALIVE: 0:26:36 2025-11-28 16:26:35.333816 2025-11-28 16:26:35.333815 11.80ms aws us-east-1 us-east-1a
172.150.28.137:9000 7bf35e16013a4a47a3c6bfb598ffbf30 0.9s ALIVE: 0:26:32 2025-11-28 16:26:34.550885 2025-11-28 16:26:34.550884 49.23ms aws us-west-1 us-west-1a
Why This Is Useful
This script gives you instant CLI access to heartbeat timings and clock synchronization details across your TServers, without needing to manually inspect the master UI.
You can:
• Detect outliers with high RTT
• Monitor uptime trends
• Audit regional placement and zone awareness
• Verify time sync (e.g., via chrony/PTP)
Want More?
For more context on how and why this data is captured, check out:
Or dig deeper into Hybrid Time and clock sync in theYugabyteDB docs!
Have Fun!
