Monitoring Clock Synchronization in YugabyteDB with a Simple Bash Script

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.

Save the scipt to a file named 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 '(?<=<h2>Tablet Servers</h2>).*?(?=</table>)')

if [[ -z "$table" ]]; then
  echo "❌ No Tablet Servers table found at $URL"
  exit 1
fi

# Grab each <tr> row
rows=$(echo "$table" | grep -oP '<tr>.*?</tr>')

# 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 <td> cells
  td_fields=$(echo "$row" | grep -oP '<td[^>]*>.*?</td>')

  # 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-clocks page 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.1 if 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!

They're turning red... the prophecy is true. 🍅 The Attack of the Killer Tomatoes has begun. and it’s happening in my wife’s garden!