Skip to content

Conversation

@Neo-vortex
Copy link
Contributor

Fix: Improve Clock Offset Calculation Stability by Using CLOCK_REALTIME

fixes :#108959

Summary

This pull request updates the SystemNative_GetBootTimeTicks function to use CLOCK_REALTIME instead of CLOCK_REALTIME_COARSE for calculating the time elapsed since the Unix Epoch.

This change is critical because the instability of the coarse clock was causing unstable process start time reads. Switching to CLOCK_REALTIME significantly reduces the observed clock synchronization jitter, leading to a much more stable and accurate boot-time offset value.


Detailed Rationale

The function calculates the boot time offset using the formula:

$$ Offset = UnixEpochTicks + Ticks(CLOCK_REALTIME_COARSE) - Ticks(CLOCK_BOOTTIME) $$

This calculated offset is used to determine key system timestamps, including the time when a process started.

Issue with CLOCK_REALTIME_COARSE

The coarse clock, designed for speed over accuracy, introduced substantial jitter into the offset calculation. Benchmark results showed the CLOCK_REALTIME_COARSE path exhibited a peak-to-peak jitter of up to approximately 1 ms (978 μs).

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <math.h>
#include <string.h>

#define NUM_TRIALS 100        
#define CALLS_PER_TRIAL 1000   

#define NSEC_PER_SEC 10000000LL
#define NSEC_PER_TICK 100LL  

long long timespec_to_ns(const struct timespec *ts) {
    return (long long)ts->tv_sec * NSEC_PER_SEC + ts->tv_nsec;
}

long long ns_to_ticks(long long ns) {
    return ns / NSEC_PER_TICK;
}

long long get_boot_ticks_fine() {
    struct timespec realtime, boottime;

    if (clock_gettime(CLOCK_REALTIME, &realtime) != 0) return -1;
    if (clock_gettime(CLOCK_BOOTTIME, &boottime) != 0) return -1;

    long long real_ns = timespec_to_ns(&realtime);
    long long boot_ns = timespec_to_ns(&boottime);
    return ns_to_ticks(real_ns - boot_ns);
}

long long get_boot_ticks_coarse() {
    struct timespec realtime_coarse, boottime_coarse;

    if (clock_gettime(CLOCK_REALTIME_COARSE, &realtime_coarse) != 0) return -1;
    if (clock_gettime(CLOCK_BOOTTIME, &boottime_coarse) != 0) return -1;

    long long real_ns = timespec_to_ns(&realtime_coarse);
    long long boot_ns = timespec_to_ns(&boottime_coarse);
    return ns_to_ticks(real_ns - boot_ns);
}

typedef struct {
    long long min_value;
    long long max_value;
    double mean;
    double std_dev; 
} AnalysisResult;

AnalysisResult analyze_results(long long *data, int n) {
    AnalysisResult res = { 
        .min_value = data[0], 
        .max_value = data[0], 
        .mean = 0.0, 
        .std_dev = 0.0 
    };
    double sum = 0.0;

    for (int i = 0; i < n; i++) {
        long long val = data[i];
        if (val < res.min_value) res.min_value = val;
        if (val > res.max_value) res.max_value = val;
        sum += val;
    }

    res.mean = sum / n;

    double sum_sq_diff = 0.0;
    for (int i = 0; i < n; i++) {
        sum_sq_diff += pow((double)data[i] - res.mean, 2);
    }
    res.std_dev = sqrt(sum_sq_diff / n);

    return res;
}

void run_benchmark(long long (*func)(void), long long *results_array) {
    
    
    for (int t = 0; t < NUM_TRIALS; t++) {
        
        long long final_offset_value = 0;
        for (int c = 0; c < CALLS_PER_TRIAL; c++) {
            final_offset_value = func();
        }
        results_array[t] = final_offset_value;

        
    }
}

// --- Conversion Macros ---
#define TICKS_TO_NS(ticks) ((double)(ticks) * NSEC_PER_TICK)
#define TICKS_TO_US(ticks) ((double)(ticks) * NSEC_PER_TICK / 1000.0)
#define TICKS_TO_MS(ticks) ((double)(ticks) * NSEC_PER_TICK / 1000000.0)

// --- Main Execution ---

int main() {
    printf("--- Clock Offset Stability Benchmark (Consecutive Trials) ---\n");
    printf("Goal: Measure the consistency (jitter) of the calculated clock offset.\n");
    printf("Settings: %d trials, %d calls per trial (run back-to-back).\n", NUM_TRIALS, CALLS_PER_TRIAL);
    printf("Offset unit: 1 tick = %lld nanoseconds\n\n", NSEC_PER_TICK);
    
    long long fine_results[NUM_TRIALS];
    long long coarse_results[NUM_TRIALS];

    printf("Running Fine Clock Benchmark (CLOCK_REALTIME/CLOCK_BOOTTIME)...\n");
    run_benchmark(get_boot_ticks_fine, fine_results);
    AnalysisResult fine_analysis = analyze_results(fine_results, NUM_TRIALS);

    printf("Running Coarse Clock Benchmark (CLOCK_REALTIME_COARSE/CLOCK_MONOTONIC_COARSE)...\n");
    run_benchmark(get_boot_ticks_coarse, coarse_results);
    AnalysisResult coarse_analysis = analyze_results(coarse_results, NUM_TRIALS);

    printf("\n--- RESULTS: Stability of Clock Offset ---\n");

    long long fine_jitter_ticks = fine_analysis.max_value - fine_analysis.min_value;
    long long coarse_jitter_ticks = coarse_analysis.max_value - coarse_analysis.min_value;

    // FINE Results
    printf("\n[Fine Clock Offset (High-Resolution)]\n");
    printf("  Mean Calculated Offset: %.0f ticks\n", fine_analysis.mean);
    
    printf("  Observed Range (Peak-to-Peak Jitter):\n");
    printf("    Min Offset: %lld ticks\n", fine_analysis.min_value);
    printf("    Max Offset: %lld ticks\n", fine_analysis.max_value);
    
    // --- FINE: PEAK-TO-PEAK JITTER DURATION (Max - Min) ---
    printf("  Total Peak-to-Peak Jitter:\n");
    printf("    In Ticks: %lld ticks\n", fine_jitter_ticks);
    printf("    In Time:  %.0f ns / %.2f us / %.3f ms\n",
           TICKS_TO_NS(fine_jitter_ticks),
           TICKS_TO_US(fine_jitter_ticks),
           TICKS_TO_MS(fine_jitter_ticks));
        


    // COARSE Results
    printf("\n[Coarse Clock Offset (Low-Resolution)]\n");
    printf("  Mean Calculated Offset: %.0f ticks\n", coarse_analysis.mean);
    
    printf("  Observed Range (Peak-to-Peak Jitter):\n");
    printf("    Min Offset: %lld ticks\n", coarse_analysis.min_value);
    printf("    Max Offset: %lld ticks\n", coarse_analysis.max_value);
    
    // --- COARSE: PEAK-TO-PEAK JITTER DURATION (Max - Min) ---
    printf("  Total Peak-to-Peak Jitter:\n");
    printf("    In Ticks: %lld ticks\n", coarse_jitter_ticks);
    printf("    In Time:  %.0f ns / %.2f us / %.3f ms\n",
           TICKS_TO_NS(coarse_jitter_ticks),
           TICKS_TO_US(coarse_jitter_ticks),
           TICKS_TO_MS(coarse_jitter_ticks));
        

    return 0;
}

--- RESULTS: Stability of Clock Offset ---

[Fine Clock Offset (High-Resolution)]
  Mean Calculated Offset: 176397190940062 ticks
  Observed Range (Peak-to-Peak Jitter):
    Min Offset: 176397190940062 ticks
    Max Offset: 176397190940062 ticks
  Total Peak-to-Peak Jitter:
    In Ticks: 0 ticks
    In Time:  0 ns / 0.00 us / 0.000 ms

[Coarse Clock Offset (Low-Resolution)]
  Mean Calculated Offset: 176397190925664 ticks
  Observed Range (Peak-to-Peak Jitter):
    Min Offset: 176397190921175 ticks
    Max Offset: 176397190930953 ticks
  Total Peak-to-Peak Jitter:
    In Ticks: 9778 ticks
    In Time:  977800 ns / 977.80 us / 0.978 ms


This high variability directly results in unstable process start time reads, making the resulting boot time offset unreliable for high-precision scenarios.

Stability of CLOCK_REALTIME

The corresponding high-resolution clock path, using CLOCK_REALTIME instead of COARSE, showed zero jitter, providing a stable and consistent offset value.

Performance Justification

Although CLOCK_REALTIME_COARSE is intended to be faster, performance profiling confirms that the difference in call overhead between the two clock functions is negligible in our target execution environment.


Proposed Change

The function is updated to use CLOCK_REALTIME to ensure the final calculation relies on stable, high-precision time sources, thereby eliminating the approximately 1 ms jitter currently observed.

This guarantees that SystemNative_GetBootTimeTicks returns a highly consistent value across all calls.

improve accurecy of SystemNative_GetBootTimeTicks by using CLOCK_BOOTTIME instead of CLOCK_REALTIME_COARSE

Signed-off-by: Mohamadreza Nakhleh <[email protected]>
@github-actions github-actions bot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Nov 24, 2025
@dotnet-policy-service dotnet-policy-service bot added the community-contribution Indicates that the PR has been added by a community member label Nov 24, 2025
@jkotas jkotas added area-System.Diagnostics.Process and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels Nov 25, 2025
@dotnet-policy-service
Copy link
Contributor

Tagging subscribers to this area: @dotnet/area-system-diagnostics-process
See info in area-owners.md if you want to be subscribed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-System.Diagnostics.Process community-contribution Indicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants