[timeval] RIP on All Saints Day (#9686)

reviewable/pr9726/r1
zwim 2 years ago committed by GitHub
parent f10ea7d339
commit 94d3d3b487
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,310 +0,0 @@
--[[--
A simple module to module to compare and do arithmetic with time values.
@usage
local TimeVal = require("ui/timeval")
local tv_start = TimeVal:now()
-- Do some stuff.
-- You can add and subtract `TimeVal` objects.
local tv_duration = TimeVal:now() - tv_start
-- And convert that object to various more human-readable formats, e.g.,
print(string.format("Stuff took %.3fms", tv_duration:tomsecs()))
]]
local ffi = require("ffi")
require("ffi/posix_h")
local logger = require("logger")
local util = require("ffi/util")
local C = ffi.C
-- We prefer CLOCK_MONOTONIC_COARSE if it's available and has a decent resolution,
-- as we generally don't need nano/micro second precision,
-- and it can be more than twice as fast as CLOCK_MONOTONIC/CLOCK_REALTIME/gettimeofday...
local PREFERRED_MONOTONIC_CLOCKID = C.CLOCK_MONOTONIC
-- Ditto for REALTIME (for :realtime_coarse only, :realtime uses gettimeofday ;)).
local PREFERRED_REALTIME_CLOCKID = C.CLOCK_REALTIME
-- CLOCK_BOOTTIME is only available on Linux 2.6.39+...
local HAVE_BOOTTIME = false
if ffi.os == "Linux" then
-- Unfortunately, it was only implemented in Linux 2.6.32, and we may run on older kernels than that...
-- So, just probe it to see if we can rely on it.
local probe_ts = ffi.new("struct timespec")
if C.clock_getres(C.CLOCK_MONOTONIC_COARSE, probe_ts) == 0 then
-- Now, it usually has a 1ms resolution on modern x86_64 systems,
-- but it only provides a 10ms resolution on all my armv7 devices :/.
if probe_ts.tv_sec == 0 and probe_ts.tv_nsec <= 1000000 then
PREFERRED_MONOTONIC_CLOCKID = C.CLOCK_MONOTONIC_COARSE
end
end
logger.dbg("TimeVal: Preferred MONOTONIC clock source is", PREFERRED_MONOTONIC_CLOCKID == C.CLOCK_MONOTONIC_COARSE and "CLOCK_MONOTONIC_COARSE" or "CLOCK_MONOTONIC")
if C.clock_getres(C.CLOCK_REALTIME_COARSE, probe_ts) == 0 then
if probe_ts.tv_sec == 0 and probe_ts.tv_nsec <= 1000000 then
PREFERRED_REALTIME_CLOCKID = C.CLOCK_REALTIME_COARSE
end
end
logger.dbg("TimeVal: Preferred REALTIME clock source is", PREFERRED_REALTIME_CLOCKID == C.CLOCK_REALTIME_COARSE and "CLOCK_REALTIME_COARSE" or "CLOCK_REALTIME")
if C.clock_getres(C.CLOCK_BOOTTIME, probe_ts) == 0 then
HAVE_BOOTTIME = true
end
logger.dbg("TimeVal: BOOTTIME clock source is", HAVE_BOOTTIME and "supported" or "NOT supported")
probe_ts = nil --luacheck: ignore
end
--[[--
TimeVal object. Maps to a POSIX struct timeval (<sys/time.h>).
@table TimeVal
@int sec floored number of seconds
@int usec number of microseconds past that second.
]]
local TimeVal = {
sec = 0,
usec = 0,
}
--[[--
Creates a new TimeVal object.
@usage
local timev = TimeVal:new{
sec = 10,
usec = 10000,
}
@treturn TimeVal
]]
function TimeVal:new(from_o)
local o = from_o or {}
if o.sec == nil then
o.sec = 0
end
if o.usec == nil then
o.usec = 0
elseif o.usec > 1000000 then
o.sec = o.sec + math.floor(o.usec / 1000000)
o.usec = o.usec % 1000000
end
setmetatable(o, self)
self.__index = self
return o
end
-- Based on <bsd/sys/time.h>
function TimeVal:__lt(time_b)
if self.sec == time_b.sec then
return self.usec < time_b.usec
else
return self.sec < time_b.sec
end
end
function TimeVal:__le(time_b)
if self.sec == time_b.sec then
return self.usec <= time_b.usec
else
return self.sec <= time_b.sec
end
end
function TimeVal:__eq(time_b)
if self.sec == time_b.sec then
return self.usec == time_b.usec
else
return false
end
end
-- If sec is negative, time went backwards!
function TimeVal:__sub(time_b)
local diff = TimeVal:new{ sec = 0, usec = 0 }
diff.sec = self.sec - time_b.sec
diff.usec = self.usec - time_b.usec
if diff.usec < 0 then
diff.sec = diff.sec - 1
diff.usec = diff.usec + 1000000
end
return diff
end
function TimeVal:__add(time_b)
local sum = TimeVal:new{ sec = 0, usec = 0 }
sum.sec = self.sec + time_b.sec
sum.usec = self.usec + time_b.usec
if sum.usec >= 1000000 then
sum.sec = sum.sec + 1
sum.usec = sum.usec - 1000000
end
return sum
end
--[[--
Creates a new TimeVal object based on the current wall clock time.
(e.g., gettimeofday / clock_gettime(CLOCK_REALTIME).
This is a simple wrapper around util.gettime() to get all the niceties of a TimeVal object.
If you don't need sub-second precision, prefer os.time().
Which means that, yes, this is a fancier POSIX Epoch ;).
@usage
local TimeVal = require("ui/timeval")
local tv_start = TimeVal:realtime()
-- Do some stuff.
-- You can add and substract `TimeVal` objects.
local tv_duration = TimeVal:realtime() - tv_start
@treturn TimeVal
]]
function TimeVal:realtime()
local sec, usec = util.gettime()
return TimeVal:new{ sec = sec, usec = usec }
end
--[[--
Creates a new TimeVal object based on the current value from the system's MONOTONIC clock source.
(e.g., clock_gettime(CLOCK_MONOTONIC).)
POSIX guarantees that this clock source will *never* go backwards (but it *may* return the same value multiple times).
On Linux, this will not account for time spent with the device in suspend (unlike CLOCK_BOOTTIME).
@treturn TimeVal
]]
function TimeVal:monotonic()
local timespec = ffi.new("struct timespec")
C.clock_gettime(C.CLOCK_MONOTONIC, timespec)
-- TIMESPEC_TO_TIMEVAL
return TimeVal:new{ sec = tonumber(timespec.tv_sec), usec = math.floor(tonumber(timespec.tv_nsec / 1000)) }
end
--- Ditto, but w/ CLOCK_MONOTONIC_COARSE if it's available and has a 1ms resolution or better (uses CLOCK_MONOTONIC otherwise).
function TimeVal:monotonic_coarse()
local timespec = ffi.new("struct timespec")
C.clock_gettime(PREFERRED_MONOTONIC_CLOCKID, timespec)
-- TIMESPEC_TO_TIMEVAL
return TimeVal:new{ sec = tonumber(timespec.tv_sec), usec = math.floor(tonumber(timespec.tv_nsec / 1000)) }
end
--- Ditto, but w/ CLOCK_REALTIME_COARSE if it's available and has a 1ms resolution or better (uses CLOCK_REALTIME otherwise).
function TimeVal:realtime_coarse()
local timespec = ffi.new("struct timespec")
C.clock_gettime(PREFERRED_REALTIME_CLOCKID, timespec)
-- TIMESPEC_TO_TIMEVAL
return TimeVal:new{ sec = tonumber(timespec.tv_sec), usec = math.floor(tonumber(timespec.tv_nsec / 1000)) }
end
--- Since CLOCK_BOOTIME may not be supported, we offer a few aliases with automatic fallbacks to MONOTONIC or REALTIME
if HAVE_BOOTTIME then
--- Ditto, but w/ CLOCK_BOOTTIME (will return a TimeVal set to 0, 0 if the clock source is unsupported, as it's 2.6.39+)
--- Only use it if you *know* it's going to be supported, otherwise, prefer the four following aliases.
function TimeVal:boottime()
local timespec = ffi.new("struct timespec")
C.clock_gettime(C.CLOCK_BOOTTIME, timespec)
-- TIMESPEC_TO_TIMEVAL
return TimeVal:new{ sec = tonumber(timespec.tv_sec), usec = math.floor(tonumber(timespec.tv_nsec / 1000)) }
end
TimeVal.boottime_or_monotonic = TimeVal.boottime
TimeVal.boottime_or_monotonic_coarse = TimeVal.boottime
TimeVal.boottime_or_realtime = TimeVal.boottime
TimeVal.boottime_or_realtime_coarse = TimeVal.boottime
else
function TimeVal:boottime()
logger.warn("TimeVal: Attemped to call boottime on a platform where it's unsupported!")
return TimeVal:new{ sec = 0, usec = 0 }
end
TimeVal.boottime_or_monotonic = TimeVal.monotonic
TimeVal.boottime_or_monotonic_coarse = TimeVal.monotonic_coarse
TimeVal.boottime_or_realtime = TimeVal.realtime
TimeVal.boottime_or_realtime_coarse = TimeVal.realtime_coarse
end
--[[-- Alias for `monotonic_coarse`.
The assumption being anything that requires accurate timestamps expects a monotonic clock source.
This is certainly true for KOReader's UI scheduling.
]]
TimeVal.now = TimeVal.monotonic_coarse
--- Converts a TimeVal object to a Lua (decimal) number (sec.usecs) (accurate to the ms, rounded to 4 decimal places)
function TimeVal:tonumber()
-- Round to 4 decimal places
return math.floor((self.sec + self.usec / 1000000) * 10000) / 10000
end
--- Converts a TimeVal object to a Lua (int) number (resolution: 1µs)
function TimeVal:tousecs()
return math.floor(self.sec * 1000000 + self.usec + 0.5)
end
--[[-- Converts a TimeVal object to a Lua (int) number (resolution: 1ms).
(Mainly useful when computing a time lapse for benchmarking purposes).
]]
function TimeVal:tomsecs()
return self:tousecs() / 1000
end
--- Converts a Lua (decimal) number (sec.usecs) to a TimeVal object
function TimeVal:fromnumber(seconds)
local sec = math.floor(seconds)
local usec = math.floor((seconds - sec) * 1000000 + 0.5)
return TimeVal:new{ sec = sec, usec = usec }
end
--[[-- Compare a past *MONOTONIC* TimeVal object to *now*, returning the elapsed time between the two. (sec.usecs variant)
Returns a Lua (decimal) number (sec.usecs) (accurate to the ms, rounded to 4 decimal places) (i.e., :tonumber())
]]
function TimeVal:getDuration(start_tv)
return (TimeVal:now() - start_tv):tonumber()
end
--[[-- Compare a past *MONOTONIC* TimeVal object to *now*, returning the elapsed time between the two. (µs variant)
Returns a Lua (int) number (resolution: 1µs) (i.e., :tousecs())
]]
function TimeVal:getDurationUs(start_tv)
return (TimeVal:now() - start_tv):tousecs()
end
--[[-- Compare a past *MONOTONIC* TimeVal object to *now*, returning the elapsed time between the two. (ms variant)
Returns a Lua (int) number (resolution: 1ms) (i.e., :tomsecs())
]]
function TimeVal:getDurationMs(start_tv)
return (TimeVal:now() - start_tv):tomsecs()
end
--- Checks if a TimeVal object is positive
function TimeVal:isPositive()
return self.sec >= 0
end
--- Checks if a TimeVal object is zero
function TimeVal:isZero()
return self.sec == 0 and self.usec == 0
end
--- We often need a const TimeVal set to zero...
--- LuaJIT doesn't actually support const values (Lua 5.4+): Do *NOT* modify it.
TimeVal.zero = TimeVal:new{ sec = 0, usec = 0 }
--- Ditto for one set to math.huge
TimeVal.huge = TimeVal:new{ sec = math.huge, usec = 0 }
return TimeVal

@ -48,10 +48,10 @@ describe("device module", function()
end)
describe("kobo", function()
local TimeVal
local time
local NickelConf
setup(function()
TimeVal = require("ui/timeval")
time = require("ui/time")
NickelConf = require("device/kobo/nickel_conf")
end)
@ -98,13 +98,13 @@ describe("device module", function()
type = C.EV_ABS,
code = C.ABS_X,
value = y,
time = TimeVal:realtime(),
time = time:realtime(),
}
local ev_y = {
type = C.EV_ABS,
code = C.ABS_Y,
value = Screen:getWidth() - 1 - x,
time = TimeVal:realtime(),
time = time:realtime(),
}
kobo_dev.input:eventAdjustHook(ev_x)

@ -1,5 +1,4 @@
require("commonrequire")
local TimeVal = require("ui/timeval")
local time = require("ui/time")
local ffi = require("ffi")
local dummy = require("ffi/posix_h")
@ -34,31 +33,31 @@ function MockTime:install()
assert(self.original_util_time ~= nil)
end
if self.original_tv_realtime == nil then
self.original_tv_realtime = TimeVal.realtime
self.original_tv_realtime = time.realtime
assert(self.original_tv_realtime ~= nil)
end
if self.original_tv_realtime_coarse == nil then
self.original_tv_realtime_coarse = TimeVal.realtime_coarse
self.original_tv_realtime_coarse = time.realtime_coarse
assert(self.original_tv_realtime_coarse ~= nil)
end
if self.original_tv_monotonic == nil then
self.original_tv_monotonic = TimeVal.monotonic
self.original_tv_monotonic = time.monotonic
assert(self.original_tv_monotonic ~= nil)
end
if self.original_tv_monotonic_coarse == nil then
self.original_tv_monotonic_coarse = TimeVal.monotonic_coarse
self.original_tv_monotonic_coarse = time.monotonic_coarse
assert(self.original_tv_monotonic_coarse ~= nil)
end
if self.original_tv_boottime == nil then
self.original_tv_boottime = TimeVal.boottime
self.original_tv_boottime = time.boottime
assert(self.original_tv_boottime ~= nil)
end
if self.original_tv_boottime_or_realtime_coarse == nil then
self.original_tv_boottime_or_realtime_coarse = TimeVal.boottime_or_realtime_coarse
self.original_tv_boottime_or_realtime_coarse = time.boottime_or_realtime_coarse
assert(self.original_tv_boottime_or_realtime_coarse ~= nil)
end
if self.original_tv_now == nil then
self.original_tv_now = TimeVal.now
self.original_tv_now = time.now
assert(self.original_tv_now ~= nil)
end
@ -76,33 +75,33 @@ function MockTime:install()
logger.dbg("MockTime:util.gettime: ", self.realtime)
return self.realtime, 0
end
TimeVal.realtime = function()
logger.dbg("MockTime:TimeVal.realtime: ", self.realtime)
return TimeVal:new{ sec = self.realtime }
time.realtime = function()
logger.dbg("MockTime:Time.realtime: ", self.realtime)
return time:new{ sec = self.realtime }
end
TimeVal.realtime_coarse = function()
logger.dbg("MockTime:TimeVal.realtime_coarse: ", self.realtime)
return TimeVal:new{ sec = self.realtime }
time.realtime_coarse = function()
logger.dbg("MockTime:Time.realtime_coarse: ", self.realtime)
return time:new{ sec = self.realtime }
end
TimeVal.monotonic = function()
logger.dbg("MockTime:TimeVal.monotonic: ", self.monotonic)
return TimeVal:new{ sec = self.monotonic }
time.monotonic = function()
logger.dbg("MockTime:Time.monotonic: ", self.monotonic)
return time:new{ sec = self.monotonic }
end
TimeVal.monotonic_coarse = function()
logger.dbg("MockTime:TimeVal.monotonic_coarse: ", self.monotonic)
return TimeVal:new{ sec = self.monotonic }
time.monotonic_coarse = function()
logger.dbg("MockTime:Time.monotonic_coarse: ", self.monotonic)
return time:new{ sec = self.monotonic }
end
TimeVal.boottime = function()
logger.dbg("MockTime:TimeVal.boottime: ", self.boottime)
return TimeVal:new{ sec = self.boottime }
time.boottime = function()
logger.dbg("MockTime:Time.boottime: ", self.boottime)
return time:new{ sec = self.boottime }
end
TimeVal.boottime_or_realtime_coarse = function()
logger.dbg("MockTime:TimeVal.boottime: ", self.boottime_or_realtime_coarse)
return TimeVal:new{ sec = self.boottime_or_realtime_coarse }
time.boottime_or_realtime_coarse = function()
logger.dbg("MockTime:Time.boottime: ", self.boottime_or_realtime_coarse)
return time:new{ sec = self.boottime_or_realtime_coarse }
end
TimeVal.now = function()
logger.dbg("MockTime:TimeVal.now: ", self.monotonic)
return TimeVal:new{ sec = self.monotonic }
time.now = function()
logger.dbg("MockTime:Time.now: ", self.monotonic)
return time:new{ sec = self.monotonic }
end
if self.original_tv_realtime_time == nil then
@ -141,31 +140,31 @@ function MockTime:install()
self.monotonic = tonumber(timespec.tv_sec) * 1e6
time.realtime = function()
logger.dbg("MockTime:TimeVal.realtime: ", self.realtime_time)
logger.dbg("MockTime:Time.realtime: ", self.realtime_time)
return self.realtime_time
end
time.realtime_coarse = function()
logger.dbg("MockTime:TimeVal.realtime_coarse: ", self.realtime_coarse_time)
logger.dbg("MockTime:Time.realtime_coarse: ", self.realtime_coarse_time)
return self.realtime_coarse_time
end
time.monotonic = function()
logger.dbg("MockTime:TimeVal.monotonic: ", self.monotonic)
logger.dbg("MockTime:Time.monotonic: ", self.monotonic)
return self.monotonic_time
end
time.monotonic_coarse = function()
logger.dbg("MockTime:TimeVal.monotonic_coarse: ", self.monotonic)
logger.dbg("MockTime:Time.monotonic_coarse: ", self.monotonic)
return self.monotonic_time
end
time.boottime = function()
logger.dbg("MockTime:TimeVal.boottime: ", self.boottime_time)
logger.dbg("MockTime:Time.boottime: ", self.boottime_time)
return self.boottime_time
end
time.boottime_or_realtime_coarse = function()
logger.dbg("MockTime:TimeVal.boottime: ", self.boottime_or_realtime_coarse_time)
logger.dbg("MockTime:Time.boottime: ", self.boottime_or_realtime_coarse_time)
return self.boottime_or_realtime_coarse_time
end
time.now = function()
logger.dbg("MockTime:TimeVal.now: ", self.monotonic)
logger.dbg("MockTime:Time.now: ", self.monotonic)
return self.monotonic_time
end
@ -177,25 +176,25 @@ function MockTime:uninstall()
util.gettime = self.original_util_time
end
if self.original_tv_realtime ~= nil then
TimeVal.realtime = self.original_tv_realtime
time.realtime = self.original_tv_realtime
end
if self.original_tv_realtime_coarse ~= nil then
TimeVal.realtime_coarse = self.original_tv_realtime_coarse
time.realtime_coarse = self.original_tv_realtime_coarse
end
if self.original_tv_monotonic ~= nil then
TimeVal.monotonic = self.original_tv_monotonic
time.monotonic = self.original_tv_monotonic
end
if self.original_tv_monotonic_coarse ~= nil then
TimeVal.monotonic_coarse = self.original_tv_monotonic_coarse
time.monotonic_coarse = self.original_tv_monotonic_coarse
end
if self.original_tv_boottime ~= nil then
TimeVal.boottime = self.original_tv_boottime
time.boottime = self.original_tv_boottime
end
if self.original_tv_boottime_or_realtime_coarse ~= nil then
TimeVal.boottime_or_realtime_coarse = self.original_tv_boottime_or_realtime_coarse
time.boottime_or_realtime_coarse = self.original_tv_boottime_or_realtime_coarse
end
if self.original_tv_now ~= nil then
TimeVal.now = self.original_tv_now
time.now = self.original_tv_now
end
end

@ -1,90 +0,0 @@
describe("TimeVal module", function()
local TimeVal, dbg, dbg_on
setup(function()
require("commonrequire")
TimeVal = require("ui/timeval")
dbg = require("dbg")
dbg_on = dbg.is_on
end)
after_each(function()
if dbg_on then
dbg:turnOn()
else
dbg:turnOff()
end
end)
it("should add", function()
local timev1 = TimeVal:new{ sec = 5, usec = 5000}
local timev2 = TimeVal:new{ sec = 10, usec = 6000}
local timev3 = TimeVal:new{ sec = 10, usec = 50000000}
assert.is.same({sec = 15, usec = 11000}, timev1 + timev2)
assert.is.same({sec = 65, usec = 5000}, timev1 + timev3)
end)
it("should subtract", function()
local timev1 = TimeVal:new{ sec = 5, usec = 5000}
local timev2 = TimeVal:new{ sec = 10, usec = 6000}
assert.is.same({sec = 5, usec = 1000}, timev2 - timev1)
local backwards_sub = timev1 - timev2
assert.is.same({sec = -6, usec = 999000}, backwards_sub)
-- Check that to/from float conversions behave, even for negative values.
assert.is.same(-5.001, backwards_sub:tonumber())
assert.is.same({sec = -6, usec = 999000}, TimeVal:fromnumber(-5.001))
local tv = TimeVal:new{ sec = -6, usec = 1000 }
assert.is.same(-5.999, tv:tonumber())
assert.is.same({sec = -6, usec = 1000}, TimeVal:fromnumber(-5.999))
-- We lose precision because of rounding if we go higher resolution than a ms...
tv = TimeVal:new{ sec = -6, usec = 101 }
assert.is.same(-5.9999, tv:tonumber())
assert.is.same({sec = -6, usec = 100}, TimeVal:fromnumber(-5.9999))
-- ^ precision loss
tv = TimeVal:new{ sec = -6, usec = 11 }
assert.is.same(-6, tv:tonumber())
-- ^ precision loss
assert.is.same({sec = -6, usec = 10}, TimeVal:fromnumber(-5.99999))
-- ^ precision loss
tv = TimeVal:new{ sec = -6, usec = 1 }
assert.is.same(-6, tv:tonumber())
-- ^ precision loss
assert.is.same({sec = -6, usec = 1}, TimeVal:fromnumber(-5.999999))
end)
it("should derive sec and usec from more than 1 sec worth of usec", function()
local timev1 = TimeVal:new{ sec = 5, usec = 5000000}
assert.is.same({sec = 10,usec = 0}, timev1)
end)
it("should compare", function()
local timev1 = TimeVal:new{ sec = 5, usec = 5000}
local timev2 = TimeVal:new{ sec = 10, usec = 6000}
local timev3 = TimeVal:new{ sec = 5, usec = 5000}
local timev4 = TimeVal:new{ sec = 5, usec = 6000}
assert.is_true(timev2 > timev1)
assert.is_false(timev2 < timev1)
assert.is_true(timev2 >= timev1)
assert.is_true(timev4 > timev1)
assert.is_false(timev4 < timev1)
assert.is_true(timev4 >= timev1)
assert.is_true(timev1 < timev2)
assert.is_false(timev1 > timev2)
assert.is_true(timev1 <= timev2)
assert.is_true(timev1 == timev3)
assert.is_false(timev1 == timev2)
assert.is_true(timev1 >= timev3)
assert.is_true(timev1 <= timev3)
end)
end)
Loading…
Cancel
Save