Skip to content

Fix inconsistent process start time#121935

Open
Neo-vortex wants to merge 5 commits into
dotnet:mainfrom
Neo-vortex:fix-inconsistente-process-start-time
Open

Fix inconsistent process start time#121935
Neo-vortex wants to merge 5 commits into
dotnet:mainfrom
Neo-vortex:fix-inconsistente-process-start-time

Conversation

@Neo-vortex
Copy link
Copy Markdown
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 <neo.vortex@pm.me>
@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
Copy Markdown
Contributor

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

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR stabilizes Linux/Android process start time calculations by changing SystemNative_GetBootTimeTicks to use CLOCK_REALTIME (higher resolution) instead of CLOCK_REALTIME_COARSE when computing the boot-time-to-Unix-epoch offset.

Changes:

  • Switch boot time offset calculation from CLOCK_REALTIME_COARSE to CLOCK_REALTIME in SystemNative_GetBootTimeTicks.

Comment on lines 101 to +103
int64_t sinceBootTicks = ((int64_t)ts.tv_sec * SecondsToTicks) + (ts.tv_nsec / TicksToNanoSeconds);

result = clock_gettime(CLOCK_REALTIME_COARSE, &ts);
result = clock_gettime(CLOCK_REALTIME, &ts);
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This behavior change affects cross-process determinism of Process.StartTime on Linux/Android, but there’s no regression coverage guarding it. Consider adding a managed test (e.g., using RemoteExecutor to run multiple separate processes that query the same target process’ StartTime) to ensure the computed boot-time offset is stable across process boundaries and doesn’t regress back to jittery values.

Copilot generated this review using guidance from repository custom instructions.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot apply changes based on this feedback

@danmoseley
Copy link
Copy Markdown
Member

@copilot address feedback push to this PR

@jkotas jkotas changed the title Fix inconsistente process start time Fix inconsistent process start time May 3, 2026
@danmoseley
Copy link
Copy Markdown
Member

I believe Copilot can't modify this PR as it's on a fork. @Neo-vortex do you want to give it a shot?

@Neo-vortex
Copy link
Copy Markdown
Contributor Author

@danmoseley
Ckeck it out

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.

5 participants