Skip to content

Commit

Permalink
ST: Use clock_gettime to prevent time jumping backwards. v7.0.17 (#3979)
Browse files Browse the repository at this point in the history
try to fix #3978 

**Background**
check #3978 

**Research**

I referred the Android platform's solution, because I have android
background, and there is a loop to handle message inside android.


https://github.com/aosp-mirror/platform_frameworks_base/blob/ff007a03c01bf936d1e961a13adff9f266d5189c/core/java/android/os/Handler.java#L701-L706C6

```
    public final boolean sendMessageDelayed(@nonnull Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
```


https://github.com/aosp-mirror/platform_system_core/blob/59d9dc1f50b1ae8630ec11a431858a3cb66487b7/libutils/SystemClock.cpp#L37-L51

```
/*
 * native public static long uptimeMillis();
 */
int64_t uptimeMillis()
{
    return nanoseconds_to_milliseconds(uptimeNanos());
}


/*
 * public static native long uptimeNanos();
 */
int64_t uptimeNanos()
{
    return systemTime(SYSTEM_TIME_MONOTONIC);
}
```


https://github.com/aosp-mirror/platform_system_core/blob/59d9dc1f50b1ae8630ec11a431858a3cb66487b7/libutils/Timers.cpp#L32-L55
```
#if defined(__linux__)
nsecs_t systemTime(int clock) {
    checkClockId(clock);
    static constexpr clockid_t clocks[] = {CLOCK_REALTIME, CLOCK_MONOTONIC,
                                           CLOCK_PROCESS_CPUTIME_ID, CLOCK_THREAD_CPUTIME_ID,
                                           CLOCK_BOOTTIME};
    static_assert(clock_id_max == arraysize(clocks));
    timespec t = {};
    clock_gettime(clocks[clock], &t);
    return nsecs_t(t.tv_sec)*1000000000LL + t.tv_nsec;
}
#else
nsecs_t systemTime(int clock) {
    // TODO: is this ever called with anything but REALTIME on mac/windows?
    checkClockId(clock);


    // Clock support varies widely across hosts. Mac OS doesn't support
    // CLOCK_BOOTTIME (and doesn't even have clock_gettime until 10.12).
    // Windows is windows.
    timeval t = {};
    gettimeofday(&t, nullptr);
    return nsecs_t(t.tv_sec)*1000000000LL + nsecs_t(t.tv_usec)*1000LL;
}
#endif
```
For Linux system, we can use `clock_gettime` api, but it's first
appeared in Mac OSX 10.12.

`man clock_gettime`

The requirement is to find an alternative way to get the timestamp in
microsecond unit, but the `clock_gettime` get nanoseconds, the math
formula is the nanoseconds / 1000 = microsecond. Then I check the
performance of this api + math division.

I used those code to check the `clock_gettime` performance.

```
#include <sys/time.h>
#include <time.h>
#include <stdio.h>
#include <unistd.h>

int main() {
	struct timeval tv;
	struct timespec ts;
	clock_t start;
	clock_t end;
	long t;

	while (1) {
		start = clock();
		gettimeofday(&tv, NULL);
		end = clock();
		printf("gettimeofday clock is %lu\n", end - start);
		printf("gettimeofday is %lld\n", (tv.tv_sec * 1000000LL + tv.tv_usec));

		start = clock();
		clock_gettime(CLOCK_MONOTONIC, &ts);
		t = ts.tv_sec * 1000000L + ts.tv_nsec / 1000L;
		end = clock();
		printf("clock_monotonic clock is %lu\n", end - start);
		printf("clock_monotonic: seconds is %ld, nanoseconds is %ld, sum is %ld\n", ts.tv_sec, ts.tv_nsec, t);

		start = clock();
		clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
		t = ts.tv_sec * 1000000L + ts.tv_nsec / 1000L;
		end = clock();
		printf("clock_monotonic_raw clock is %lu\n", end - start);
		printf("clock_monotonic_raw: nanoseconds is %ld, sum is %ld\n", ts.tv_nsec, t);

		sleep(3);
	}
	
	return 0;
}

```

Here is output:

env: Mac OS M2 chip.

```
gettimeofday clock is 11
gettimeofday is 1709775727153949
clock_monotonic clock is 2
clock_monotonic: seconds is 1525204, nanoseconds is 409453000, sum is 1525204409453
clock_monotonic_raw clock is 2
clock_monotonic_raw: nanoseconds is 770493000, sum is 1525222770493
```
We can see the `clock_gettime` is faster than `gettimeofday`, so there
are no performance risks.

**MacOS solution**

`clock_gettime` api only available until mac os 10.12, for the mac os
older than 10.12, just keep the `gettimeofday`.
check osx version in `auto/options.sh`, then add MACRO in
`auto/depends.sh`, the MACRO is `MD_OSX_HAS_NO_CLOCK_GETTIME`.


**CYGWIN**
According to google search, it seems the
`clock_gettime(CLOCK_MONOTONIC)` is not support well at least 10 years
ago, but I didn't own an windows machine, so can't verify it. so keep
win's solution.

---------

Co-authored-by: winlin <[email protected]>
  • Loading branch information
suzp1984 and winlinvip authored Oct 15, 2024
1 parent 0de887d commit e7d7846
Show file tree
Hide file tree
Showing 9 changed files with 168 additions and 15 deletions.
5 changes: 3 additions & 2 deletions trunk/3rdparty/st-srs/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -185,11 +185,9 @@ endif
# make EXTRA_CFLAGS=-UMD_HAVE_EPOLL <target>
#
# or to enable sendmmsg(2) support:
#
# make EXTRA_CFLAGS="-DMD_HAVE_SENDMMSG -D_GNU_SOURCE"
#
# or to enable stats for ST:
#
# make EXTRA_CFLAGS=-DDEBUG_STATS
#
# or cache the stack and reuse it:
Expand All @@ -201,6 +199,9 @@ endif
# or enable support for asan:
# make EXTRA_CFLAGS="-DMD_ASAN -fsanitize=address -fno-omit-frame-pointer"
#
# or to disable the clock_gettime for MacOS before 10.12, see https://github.com/ossrs/srs/issues/3978
# make EXTRA_CFLAGS=-DMD_OSX_NO_CLOCK_GETTIME
#
# or enable the coverage for utest:
# make UTEST_FLAGS="-fprofile-arcs -ftest-coverage"
#
Expand Down
31 changes: 21 additions & 10 deletions trunk/3rdparty/st-srs/md.h
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,21 @@ extern void _st_md_cxt_restore(_st_jmp_buf_t env, int val);
#error Unknown CPU architecture
#endif

#define MD_GET_UTIME() \
struct timeval tv; \
(void) gettimeofday(&tv, NULL); \
return (tv.tv_sec * 1000000LL + tv.tv_usec)
#if defined (MD_OSX_NO_CLOCK_GETTIME)
#define MD_GET_UTIME() \
struct timeval tv; \
(void) gettimeofday(&tv, NULL); \
return (tv.tv_sec * 1000000LL + tv.tv_usec)
#else
/*
* https://github.com/ossrs/srs/issues/3978
* use clock_gettime to get the timestamp in microseconds.
*/
#define MD_GET_UTIME() \
struct timespec ts; \
clock_gettime(CLOCK_MONOTONIC, &ts); \
return (ts.tv_sec * 1000000LL + ts.tv_nsec / 1000)
#endif

#elif defined (LINUX)

Expand All @@ -120,13 +131,13 @@ extern void _st_md_cxt_restore(_st_jmp_buf_t env, int val);
#define MD_HAVE_SOCKLEN_T
/*
* All architectures and flavors of linux have the gettimeofday
* function but if you know of a faster way, use it.
* https://github.com/ossrs/srs/issues/3978
* use clock_gettime to get the timestamp in microseconds.
*/
#define MD_GET_UTIME() \
struct timeval tv; \
(void) gettimeofday(&tv, NULL); \
return (tv.tv_sec * 1000000LL + tv.tv_usec)
#define MD_GET_UTIME() \
struct timespec ts; \
clock_gettime(CLOCK_MONOTONIC, &ts); \
return (ts.tv_sec * 1000000LL + ts.tv_nsec / 1000)

#if defined(__i386__)
#define MD_GET_SP(_t) *((long *)&((_t)->context[0].__jmpbuf[4]))
Expand Down
3 changes: 3 additions & 0 deletions trunk/auto/depends.sh
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,9 @@ fi
# for osx, use darwin for st, donot use epoll.
if [[ $SRS_OSX == YES ]]; then
_ST_MAKE=darwin-debug && _ST_OBJ="DARWIN_`uname -r`_DBG"
if [[ $SRS_OSX_HAS_CLOCK_GETTIME != YES ]]; then
_ST_EXTRA_CFLAGS="$_ST_EXTRA_CFLAGS -DMD_OSX_NO_CLOCK_GETTIME"
fi
fi
# for windows/cygwin
if [[ $SRS_CYGWIN64 = YES ]]; then
Expand Down
6 changes: 5 additions & 1 deletion trunk/auto/options.sh
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ SRS_CROSS_BUILD_HOST=
SRS_CROSS_BUILD_PREFIX=
# For cache build
SRS_BUILD_CACHE=YES
# Only support MacOS 10.12+ for clock_gettime, see https://github.com/ossrs/srs/issues/3978
SRS_OSX_HAS_CLOCK_GETTIME=YES
#
#####################################################################################
# Toolchain for cross-build on Ubuntu for ARM or MIPS.
Expand Down Expand Up @@ -150,7 +152,9 @@ function apply_system_options() {
OS_IS_RISCV=$(gcc -dM -E - </dev/null |grep -q '#define __riscv 1' && echo YES)

# Set the os option automatically.
if [[ $OS_IS_OSX == YES ]]; then SRS_OSX=YES; fi
if [[ $OS_IS_OSX == YES ]]; then
SRS_OSX=YES;
fi
if [[ $OS_IS_CYGWIN == YES ]]; then SRS_CYGWIN64=YES; fi

if [[ $OS_IS_OSX == YES ]]; then SRS_JOBS=$(sysctl -n hw.ncpu 2>/dev/null || echo 1); fi
Expand Down
3 changes: 2 additions & 1 deletion trunk/configure
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,8 @@ if [[ $SRS_UTEST == YES ]]; then
MODULE_FILES=("srs_utest" "srs_utest_amf0" "srs_utest_kernel" "srs_utest_core"
"srs_utest_config" "srs_utest_rtmp" "srs_utest_http" "srs_utest_avc" "srs_utest_reload"
"srs_utest_mp4" "srs_utest_service" "srs_utest_app" "srs_utest_rtc" "srs_utest_config2"
"srs_utest_protocol" "srs_utest_protocol2" "srs_utest_kernel2" "srs_utest_protocol3")
"srs_utest_protocol" "srs_utest_protocol2" "srs_utest_kernel2" "srs_utest_protocol3"
"srs_utest_st")
if [[ $SRS_SRT == YES ]]; then
MODULE_FILES+=("srs_utest_srt")
fi
Expand Down
1 change: 1 addition & 0 deletions trunk/doc/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ The changelog for SRS.
<a name="v7-changes"></a>

## SRS 7.0 Changelog
* v7.0, 2024-10-15, Merge [#3979](https://github.com/ossrs/srs/pull/3979): ST: Use clock_gettime to prevent time jumping backwards. v7.0.17 (#3979)
* v7.0, 2024-09-09, Merge [#4158](https://github.com/ossrs/srs/pull/4158): Proxy: Support proxy server for SRS. v7.0.16 (#4158)
* v7.0, 2024-09-09, Merge [#4171](https://github.com/ossrs/srs/pull/4171): Heartbeat: Report ports for proxy server. v7.0.15 (#4171)
* v7.0, 2024-09-01, Merge [#4165](https://github.com/ossrs/srs/pull/4165): FLV: Refine source and http handler. v7.0.14 (#4165)
Expand Down
2 changes: 1 addition & 1 deletion trunk/src/core/srs_core_version7.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@

#define VERSION_MAJOR 7
#define VERSION_MINOR 0
#define VERSION_REVISION 16
#define VERSION_REVISION 17

#endif
117 changes: 117 additions & 0 deletions trunk/src/utest/srs_utest_st.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
//
// Copyright (c) 2013-2024 The SRS Authors
//
// SPDX-License-Identifier: MIT
//
#include <srs_utest_st.hpp>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>

using namespace std;

VOID TEST(StTest, StUtimeInMicroseconds)
{
st_utime_t st_time_1 = st_utime();
// sleep 1 microsecond
#if !defined(SRS_CYGWIN64)
usleep(1);
#endif
st_utime_t st_time_2 = st_utime();

EXPECT_GT(st_time_1, 0);
EXPECT_GT(st_time_2, 0);
EXPECT_GE(st_time_2, st_time_1);
// st_time_2 - st_time_1 should be in range of [1, 100] microseconds
EXPECT_GE(st_time_2 - st_time_1, 0);
EXPECT_LE(st_time_2 - st_time_1, 100);
}

static inline st_utime_t time_gettimeofday() {
struct timeval tv;
gettimeofday(&tv, NULL);
return (tv.tv_sec * 1000000LL + tv.tv_usec);
}

VOID TEST(StTest, StUtimePerformance)
{
clock_t start;
int gettimeofday_elapsed_time = 0;
int st_utime_elapsed_time = 0;

// Both the st_utime(clock_gettime or gettimeofday) and gettimeofday's
// elpased time to execute is dependence on whether it is the first time be called.
// In general, the gettimeofday has better performance, but the gap between
// them is really small, maybe less than 10 clock ~ 10 microseconds.

// check st_utime first, then gettimeofday
{
start = clock();
st_utime_t t2 = st_utime();
int elapsed_time = clock() - start;
st_utime_elapsed_time += elapsed_time;
EXPECT_GT(t2, 0);

start = clock();
st_utime_t t1 = time_gettimeofday();
elapsed_time = clock() - start;
gettimeofday_elapsed_time += elapsed_time;
EXPECT_GT(t1, 0);


EXPECT_GE(gettimeofday_elapsed_time, 0);
EXPECT_GE(st_utime_elapsed_time, 0);

// pass the test, if
EXPECT_LT(gettimeofday_elapsed_time > st_utime_elapsed_time ?
gettimeofday_elapsed_time - st_utime_elapsed_time :
st_utime_elapsed_time - gettimeofday_elapsed_time, 10);
}

// check gettimeofday first, then st_utime
{
start = clock();
st_utime_t t1 = time_gettimeofday();
int elapsed_time = clock() - start;
gettimeofday_elapsed_time += elapsed_time;
EXPECT_GT(t1, 0);

start = clock();
st_utime_t t2 = st_utime();
elapsed_time = clock() - start;
st_utime_elapsed_time += elapsed_time;
EXPECT_GT(t2, 0);

EXPECT_GE(gettimeofday_elapsed_time, 0);
EXPECT_GE(st_utime_elapsed_time, 0);

EXPECT_LT(gettimeofday_elapsed_time > st_utime_elapsed_time ?
gettimeofday_elapsed_time - st_utime_elapsed_time :
st_utime_elapsed_time - gettimeofday_elapsed_time, 10);
}

// compare st_utime & gettimeofday in a loop
for (int i = 0; i < 100; i++) {
start = clock();
st_utime_t t2 = st_utime();
int elapsed_time = clock() - start;
st_utime_elapsed_time = elapsed_time;
EXPECT_GT(t2, 0);
usleep(1);

start = clock();
st_utime_t t1 = time_gettimeofday();
elapsed_time = clock() - start;
gettimeofday_elapsed_time = elapsed_time;
EXPECT_GT(t1, 0);
usleep(1);

EXPECT_GE(gettimeofday_elapsed_time, 0);
EXPECT_GE(st_utime_elapsed_time, 0);

EXPECT_LT(gettimeofday_elapsed_time > st_utime_elapsed_time ?
gettimeofday_elapsed_time - st_utime_elapsed_time :
st_utime_elapsed_time - gettimeofday_elapsed_time, 10);

}
}
15 changes: 15 additions & 0 deletions trunk/src/utest/srs_utest_st.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// Copyright (c) 2013-2024 The SRS Authors
//
// SPDX-License-Identifier: MIT
//

#ifndef SRS_UTEST_ST_HPP
#define SRS_UTEST_ST_HPP

#include <srs_utest.hpp>

#include <st.h>

#endif // SRS_UTEST_ST_HPP

0 comments on commit e7d7846

Please sign in to comment.