Skip to content

kermitbu/tshift

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

3 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

tshift

LD_PRELOAD-based per-process time shifting for Linux

English | δΈ­ζ–‡


Overview

tshift provides an independent time view for a single Linux process via LD_PRELOAD, without modifying the system clock. It is designed for:

  • Time replay (incident rehearsal, data replay)
  • Logical clock isolation (multi-tenant, test environments)
  • Java / Python / microservices time-related logic validation
  • Deterministic time in CI / automated tests

Supports RHEL / Ubuntu / Kylin / UOS, amd64 / aarch64, OpenJDK 8 / 11 / 17 / 21 / 25.

Highlights

  • βœ… Non-invasive: never touches system time, no impact on other processes
  • βœ… Lock-free fastpath: C11 stdatomic, no safepoints, no virtual-thread blocking
  • βœ… Lazy init: no __attribute__((constructor)), safe during early JVM boot
  • βœ… Zero IO: no reads from /proc, /sys, /etc (SELinux / kysec friendly)
  • βœ… Single offset model: display = real + offset β€” simple and predictable
  • βœ… Timeline switch: runtime switching via dlsym(tshift_switch_timeline)
  • βœ… Monotonic guarantee: CLOCK_MONOTONIC never regresses, decoupled from switches
  • βœ… Cross-language support: C, Java, Python (sleep scaling verified via external timing)

Quick Start

Build

make lib    # build libtshift.so only (production)
make all    # build libtshift.so + tshift launcher
make test   # build and run tests (C, Java, Python)

Simplest usage (anonymous timeline)

# Just anchor and go β€” no timeline name needed
LD_PRELOAD=./build/libtshift.so \
TSHIFT_TIMELINES="2025-01-01 12:00:00" \
./your_program

Launcher usage

# Anonymous timeline (positional)
./build/tshift "2025-01-01 12:00:00" date

# Named timeline
./build/tshift -t "replay_2025@2025-01-01 12:00:00" -u replay_2025 java -jar app.jar

# Multiple timelines + rate
./build/tshift -t "replay@2025-01-01 00:00:00,fast@2024-06-01 00:00:00@2" \
              -u replay date

Direct LD_PRELOAD (production)

export LD_PRELOAD=/path/to/libtshift.so
export TSHIFT_TIMELINES="replay_2025@2025-01-01 12:00:00"
export TSHIFT=replay_2025
java -jar your-app.jar

Usage Scenarios

1. Incident Replay (time travel)

Replay production traffic at a specific past point in time:

LD_PRELOAD=./build/libtshift.so \
TSHIFT_TIMELINES="2024-12-24 15:30:00" \
java -jar incident-replay.jar

2. Accelerated Time (rate > 1)

Run a 2-hour replay in 1 hour of real wall-clock time:

LD_PRELOAD=./build/libtshift.so \
TSHIFT_TIMELINES="2025-06-01@2" \
./your_app

All sleeps, timeouts, and timers fire at 2x speed. A 10-second Thread.sleep(10000) takes only 5 real seconds.

3. Half-Speed Debugging (rate < 1)

Slow down time-sensitive logic to observe race conditions:

LD_PRELOAD=./build/libtshift.so \
TSHIFT_TIMELINES="2025-01-01@0.5" \
./your_app

Everything runs at half speed. Useful for debugging timing-sensitive code.

4. Frozen Time (rate = 0)

Stop all time progression β€” clock_gettime returns the same value, sleeps never return:

LD_PRELOAD=./build/libtshift.so \
TSHIFT_TIMELINES="2025-01-01 12:00:00@0" \
./your_app

5. Multiple Timelines + Runtime Switching

Define multiple time periods and switch between them at runtime:

LD_PRELOAD=./build/libtshift.so \
TSHIFT_TIMELINES="january@2025-01-01,june@2025-06-01" \
TSHIFT="january" \
./your_app

In your application code, switch via dlsym:

typedef int (*switch_fn)(const char *);
switch_fn fn = (switch_fn)dlsym(RTLD_DEFAULT, "tshift_switch_timeline");
fn("june");  // instantly switch to June 2025

A REAL timeline is automatically appended to every configuration, so you can always switch back to real time:

fn("REAL");  // switch back to real system time

6. Multi-Tenant Time Isolation

Each tenant container gets its own time view:

# Tenant A: anchored at 2025-01-01
LD_PRELOAD=./libtshift.so \
TSHIFT_TIMELINES="2025-01-01" \
docker run tenant-a

# Tenant B: anchored at 2024-06-01 at 2x speed
LD_PRELOAD=./libtshift.so \
TSHIFT_TIMELINES="2024-06-01@2" \
docker run tenant-b

7. CI Deterministic Time

Run tests with a fixed, deterministic time to avoid flaky time-dependent tests:

LD_PRELOAD=./libtshift.so \
TSHIFT_TIMELINES="2025-01-01 12:00:00" \
pytest tests/

8. Multiple Anonymous Timelines

Define multiple anonymous timelines and switch by _0_, _1_, _2_:

# Three anonymous timelines: 2025-01-01(2x), 2025-06-01, REAL(0.5x)
LD_PRELOAD=./build/libtshift.so \
TSHIFT_TIMELINES="2025-01-01@2,2025-06-01,REAL@0.5" \
./your_app

Switch by name in code:

fn("_0_");  // switch to 2025-01-01@2
fn("_1_");  // switch to 2025-06-01
fn("_2_");  // switch to REAL@0.5

9. External Timeline Switching (Production)

How to trigger timeline switches from outside the process?

Option 1: Unix Signals (SIGUSR1/SIGUSR2)

#include <signal.h>
#include <dlfcn.h>

void handle_sigusr1(int sig) {
    (void)sig;
    typedef int (*switch_fn)(const char *);
    switch_fn fn = (switch_fn)dlsym(RTLD_DEFAULT, "tshift_switch_timeline");
    fn("_1_");  // switch to second timeline
}
void handle_sigusr2(int sig) {
    (void)sig;
    typedef int (*switch_fn)(const char *);
    switch_fn fn = (switch_fn)dlsym(RTLD_DEFAULT, "tshift_switch_timeline");
    fn("REAL");  // switch back to real time
}

int main() {
    signal(SIGUSR1, handle_sigusr1);
    signal(SIGUSR2, handle_sigusr2);
    // ... business logic
}

// External trigger: kill -SIGUSR1 <pid>   switch to _1_
//                   kill -SIGUSR2 <pid>   switch to real time

Option 2: HTTP Endpoint

# Expose HTTP endpoint for timeline switching
from http.server import HTTPServer, BaseHTTPRequestHandler
import ctypes

class SwitchHandler(BaseHTTPRequestHandler):
    def do_POST(self):
        name = self.path.strip('/')
        lib = ctypes.CDLL(None)
        lib.tshift_switch_timeline(name.encode())
        self.send_response(200)
        self.end_headers()
        self.wfile.write(b"switched to " + name.encode())

# External trigger: curl -X POST http://localhost:9999/_1_

Option 3: File Watcher

# Monitor file changes to trigger switches
while inotifywait -e modify /tmp/tshift_control; do
    timeline=$(cat /tmp/tshift_control)
    echo "switching to $timeline"
    kill -SIGUSR1 $(pgrep -f your_app)
done

Anonymous Timeline Notes

Anonymous timelines simplify single-timeline usage without naming:

# Equivalent to: TSHIFT_TIMELINES="replay@2025-01-01" + TSHIFT="replay"
TSHIFT_TIMELINES="2025-01-01"

Properties:

  • Supports multiple, comma-separated: TSHIFT_TIMELINES="2025-01-01@2,2025-06-01,REAL"
  • Auto-named as _0_, _1_, _2_ ... switchable by name
  • TSHIFT env var not required, defaults to _0_
  • Auto-REAL append rules still apply (anchor_ns=0 skips duplicate)
  • Cannot mix with named format (digit or R as first char triggers anonymous mode)

Environment Variables

Variable Required Description
TSHIFT_TIMELINES yes Comma-separated timeline definitions (named or anonymous)
TSHIFT no Active timeline name; defaults to first timeline
TSHIFT_LIB no Hint for launcher to locate libtshift.so

Timeline syntax

Named format:

name@ANCHOR[@RATE]

Anonymous format (first character is digit or R):

ANCHOR[@RATE]
  • name: 1-63 alphanumeric / underscore
  • ANCHOR: YYYY-MM-DD HH:MM:SS, YYYY-MM-DDTHH:MM:SS, YYYY-MM-DD, REAL, or Unix seconds (with optional fraction)
  • RATE: float, default 1.0, 0 to freeze

REAL timeline: automatically appended to every configuration (unless already present). Provides a way to switch back to real system time at runtime.

CLI Options

Short Long Meaning
-t --timelines Timeline definitions
-u --use Use a named timeline
-v --version Print version
-h --help Show help

Syscall Interception

Class Syscalls Behavior
πŸ”΄ A clock_gettime, gettimeofday, time, ftime Return real + offset
🟠 B clock_getres Return 1ns resolution
🟑 C nanosleep, clock_nanosleep, setitimer, alarm, pselect Scale duration by 1/rate
🟒 D clock_settime, settimeofday, adjtimex Reject: -1, errno=EPERM
βšͺ E clock_getcpuclockid, pthread_getcpuclockid, CPU clocks Passthrough

Monotonic Behavior

CLOCK_MONOTONIC, MONOTONIC_RAW, MONOTONIC_COARSE, BOOTTIME:

  • No offset applied (fully decoupled from REALTIME)
  • Only scaled by rate
  • Always monotonically non-decreasing; switch never causes regression

Cross-Language Support

Language Time Retrieval Sleep Scaling Notes
C βœ… Fully supported βœ… Fully supported Direct syscall interception
Java βœ… Fully supported βœ… Supported Thread.sleep() intercepted via clock_nanosleep
Python βœ… Fully supported βœ… Supported pselect/syscall interception covers time.sleep()

Sleep Scaling Verification

When rate != 1, sleep scaling cannot be verified from inside the process because all clocks (including CLOCK_MONOTONIC) are scaled. The measured interval will always equal rate Γ— real_elapsed, making in-process self-tests always fail. This is a measurement artifact, not a defect.

Verification must use external timing (e.g., parent process date +%s%N before/after), which the integration tests (run_tests.sh test_group 8) correctly do and confirm sleep scaling works.

Design

See docs/DESIGN.zh.md (Chinese).

Concurrency & Process Safety

  • Multi-process: each process loads its own libtshift.so with independent state β€” fully isolated.
  • Multi-threaded reads: lock-free design; safe under JDK Virtual Threads.
  • Timeline switching: switching is thread-safe, but it is recommended to drive switches from a single control thread (e.g., an HTTP Admin worker) to avoid unpredictable behavior from concurrent switches.

Testing

make test

Test environment requirements are automatically detected:

  • Java: requires java and javac in PATH
  • Python: requires python3 in PATH
  • C: always available (built-in)

Test Coverage

Category Tests
C Language Parsing unit tests (11 items), offset correctness, anonymous timeline, continuous/reset mode, multi-timeline selection, time()/gettimeofday()/ftime() forging, monotonic non-regression, rate=2/0.5/0 scaling, CLOCK_REALTIME_COARSE/BOOTTIME/MONOTONIC_RAW/MONOTONIC_COARSE/BOOTTIME_ALARM forging, clock_getres() precision, pselect() scaling, alarm() scaling, setitimer() scaling, clock_nanosleep(ABSTIME) scaling, launcher CLI, D class rejection (clock_settime, settimeofday, adjtimex), timeline runtime switch, 32 threads Γ— 10000 iterations concurrent stress test
Java System.currentTimeMillis(), System.nanoTime() monotonicity, Instant.now(), Date(), Thread.sleep() scaling (external timing)
Python time.time(), time.time_ns(), time.localtime(), datetime.datetime.now(), datetime.datetime.utcnow(), time.monotonic() monotonicity, time.sleep() scaling (external timing)

Project Layout

tshift/
β”œβ”€β”€ LICENSE                  MIT License
β”œβ”€β”€ Makefile                 Build system
β”œβ”€β”€ README.md                English docs
β”œβ”€β”€ README.zh.md             Chinese docs
β”œβ”€β”€ prompts.md               Development prompts
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ tshift.h             Public header
β”‚   β”œβ”€β”€ tshift_core.c        Core library (LD_PRELOAD)
β”‚   β”œβ”€β”€ tshift.c             Launcher
β”‚   └── env_parse.c          Env var parsing
β”œβ”€β”€ tests/
β”‚   β”œβ”€β”€ c/                   C language tests
β”‚   β”‚   β”œβ”€β”€ helper.c         Time-reading helper
β”‚   β”‚   β”œβ”€β”€ test_parse.c     Parsing unit tests
β”‚   β”‚   β”œβ”€β”€ test_concurrent.c Concurrent stress test
β”‚   β”‚   β”œβ”€β”€ test_pselect.c   pselect interception test
β”‚   β”‚   └── test_switch.c    Timeline runtime switch test
β”‚   β”œβ”€β”€ java/                Java tests
β”‚   β”‚   └── TimeShiftTest.java  JVM time retrieval test
β”‚   β”œβ”€β”€ python/              Python tests
β”‚   β”‚   └── TimeShiftTest.py    Python time retrieval test
β”‚   β”œβ”€β”€ run_tests.sh         Integration test script
β”‚   └── stress.sh            Stress test script

License

MIT β€” see LICENSE.

About

LD_PRELOAD-based per-process time shifting for Linux: offset, scale, freeze, or replay time for any process without modifying the system clock.

Topics

Resources

License

Stars

1 star

Watchers

1 watching

Forks

Packages

 
 
 

Contributors