Kobo/Elipsa: More fine-grained control over the amount of online CPU

cores

* Only keep a single core online most of the time.
* Device: Add an enableCPUCores method to allow controlling the amount of
  online CPU cores.
* Move the initial core onlining setup to Kobo:init, instead of the startup script.
* Enable two CPU cores while hinting new (e.g., cache miss) pages in PDF land.
* Enable two CPU cores while processing book metadata.
* Drive-by fix to isolate the DocCache pressure check to KoptInterface
  and actually apply it when it matters most (e.g., k2pdfopt stuff).
pull/8261/head
NiLuJe 3 years ago
parent 65abac9431
commit 48da545e32

@ -460,6 +460,10 @@ end
-- Device specific method for toggling the charging LED -- Device specific method for toggling the charging LED
function Device:toggleChargingLED(toggle) end function Device:toggleChargingLED(toggle) end
-- Device specific method for enabling a specific amount of CPU cores
-- (Should only be implemented on embedded platforms where we can afford to control that without screwing with the system).
function Device:enableCPUCores(amount) end
--[[ --[[
prepare for application shutdown prepare for application shutdown
--]] --]]

@ -71,6 +71,8 @@ local Kobo = Generic:new{
touch_dev = "/dev/input/event1", touch_dev = "/dev/input/event1",
-- Event code to use to detect contact pressure -- Event code to use to detect contact pressure
pressure_event = nil, pressure_event = nil,
-- Device features multiple CPU cores
isSMP = no,
} }
--- @todo hasKeys for some devices? --- @todo hasKeys for some devices?
@ -321,6 +323,7 @@ local KoboEuropa = Kobo:new{
battery_sysfs = "/sys/class/power_supply/battery", battery_sysfs = "/sys/class/power_supply/battery",
ntx_dev = "/dev/input/by-path/platform-ntx_event0-event", ntx_dev = "/dev/input/by-path/platform-ntx_event0-event",
touch_dev = "/dev/input/by-path/platform-0-0010-event", touch_dev = "/dev/input/by-path/platform-0-0010-event",
isSMP = yes,
} }
function Kobo:init() function Kobo:init()
@ -439,6 +442,9 @@ function Kobo:init()
-- * Turn it off on startup -- * Turn it off on startup
-- I've chosen the latter, as I find it vaguely saner, more useful, and it matches Nickel's behavior (I think). -- I've chosen the latter, as I find it vaguely saner, more useful, and it matches Nickel's behavior (I think).
self:toggleChargingLED(false) self:toggleChargingLED(false)
-- Only enable a single core on startup
self:enableCPUCores(1)
end end
function Kobo:setDateTime(year, month, day, hour, min, sec) function Kobo:setDateTime(year, month, day, hour, min, sec)
@ -657,7 +663,7 @@ function Kobo:suspend()
local has_wakeup_count = false local has_wakeup_count = false
f = io.open("/sys/power/wakeup_count", "r") f = io.open("/sys/power/wakeup_count", "r")
if f ~= nil then if f ~= nil then
io.close(f) f:close()
has_wakeup_count = true has_wakeup_count = true
end end
@ -682,7 +688,7 @@ function Kobo:suspend()
return false return false
end end
re, err_msg, err_code = f:write("1\n") re, err_msg, err_code = f:write("1\n")
io.close(f) f:close()
logger.info("Kobo suspend: asked the kernel to put subsystems to sleep, ret:", re) logger.info("Kobo suspend: asked the kernel to put subsystems to sleep, ret:", re)
if not re then if not re then
logger.err('write error: ', err_msg, err_code) logger.err('write error: ', err_msg, err_code)
@ -709,7 +715,7 @@ function Kobo:suspend()
err_msg, err_msg,
err_code) err_code)
end end
io.close(f) f:close()
end end
--]] --]]
@ -723,7 +729,7 @@ function Kobo:suspend()
logger.err("cannot open /sys/power/state-extended for writing!") logger.err("cannot open /sys/power/state-extended for writing!")
else else
ext_fd:write("0\n") ext_fd:write("0\n")
io.close(ext_fd) ext_fd:close()
end end
return false return false
end end
@ -735,7 +741,7 @@ function Kobo:suspend()
if not re then if not re then
logger.err('write error: ', err_msg, err_code) logger.err('write error: ', err_msg, err_code)
end end
io.close(f) f:close()
-- NOTE: Ideally, we'd need a way to warn the user that suspending -- NOTE: Ideally, we'd need a way to warn the user that suspending
-- gloriously failed at this point... -- gloriously failed at this point...
-- We can safely assume that just from a non-zero return code, without -- We can safely assume that just from a non-zero return code, without
@ -786,7 +792,7 @@ function Kobo:resume()
return false return false
end end
local re, err_msg, err_code = f:write("0\n") local re, err_msg, err_code = f:write("0\n")
io.close(f) f:close()
logger.info("Kobo resume: unflagged kernel subsystems for resume, ret:", re) logger.info("Kobo resume: unflagged kernel subsystems for resume, ret:", re)
if not re then if not re then
logger.err('write error: ', err_msg, err_code) logger.err('write error: ', err_msg, err_code)
@ -799,7 +805,7 @@ function Kobo:resume()
f = io.open("/sys/devices/virtual/input/input1/neocmd", "w") f = io.open("/sys/devices/virtual/input/input1/neocmd", "w")
if f ~= nil then if f ~= nil then
f:write("a\n") f:write("a\n")
io.close(f) f:close()
end end
end end
@ -892,7 +898,53 @@ function Kobo:toggleChargingLED(toggle)
f:flush() f:flush()
end end
io.close(f) f:close()
end
-- Return the highest core number
local function getCPUCount()
local fd = io.open("/sys/devices/system/cpu/possible", "r")
if fd then
local str = fd:read("*line")
fd:close()
-- Format is n-N, where n is the first core, and N the last (e.g., 0-3)
return tonumber(str:match("%d+$")) or 0
else
return 0
end
end
function Kobo:enableCPUCores(amount)
if not self:isSMP() then
return
end
if not self.cpu_count then
self.cpu_count = getCPUCount()
end
-- Not actually SMP or getCPUCount failed...
if self.cpu_count == 0 then
return
end
-- CPU0 is *always* online ;).
for n=1, self.cpu_count do
local path = "/sys/devices/system/cpu/cpu" .. n .. "/online"
local up
if n >= amount then
up = "0"
else
up = "1"
end
local f = io.open(path, "w")
if f then
f:write(up)
f:close()
end
end
end end
function Kobo:isStartupScriptUpToDate() function Kobo:isStartupScriptUpToDate()

@ -136,8 +136,8 @@ function DjvuDocument:findText(pattern, origin, reverse, caseInsensitive, page)
return self.koptinterface:findText(self, pattern, origin, reverse, caseInsensitive, page) return self.koptinterface:findText(self, pattern, origin, reverse, caseInsensitive, page)
end end
function DjvuDocument:renderPage(pageno, rect, zoom, rotation, gamma, render_mode) function DjvuDocument:renderPage(pageno, rect, zoom, rotation, gamma, render_mode, hinting)
return self.koptinterface:renderPage(self, pageno, rect, zoom, rotation, gamma, render_mode) return self.koptinterface:renderPage(self, pageno, rect, zoom, rotation, gamma, render_mode, hinting)
end end
function DjvuDocument:hintPage(pageno, zoom, rotation, gamma, render_mode) function DjvuDocument:hintPage(pageno, zoom, rotation, gamma, render_mode)

@ -1,6 +1,7 @@
local Blitbuffer = require("ffi/blitbuffer") local Blitbuffer = require("ffi/blitbuffer")
local CacheItem = require("cacheitem") local CacheItem = require("cacheitem")
local Configurable = require("configurable") local Configurable = require("configurable")
local Device = require("device")
local DocCache = require("document/doccache") local DocCache = require("document/doccache")
local DrawContext = require("ffi/drawcontext") local DrawContext = require("ffi/drawcontext")
local CanvasContext = require("document/canvascontext") local CanvasContext = require("document/canvascontext")
@ -392,7 +393,7 @@ function Document:getFullPageHash(pageno, zoom, rotation, gamma, render_mode, co
..(self.reflowable_font_size and "|"..self.reflowable_font_size or "") ..(self.reflowable_font_size and "|"..self.reflowable_font_size or "")
end end
function Document:renderPage(pageno, rect, zoom, rotation, gamma, render_mode) function Document:renderPage(pageno, rect, zoom, rotation, gamma, render_mode, hinting)
local hash_excerpt local hash_excerpt
local hash = self:getFullPageHash(pageno, zoom, rotation, gamma, render_mode, self.render_color) local hash = self:getFullPageHash(pageno, zoom, rotation, gamma, render_mode, self.render_color)
local tile = DocCache:check(hash, TileCacheItem) local tile = DocCache:check(hash, TileCacheItem)
@ -411,6 +412,9 @@ function Document:renderPage(pageno, rect, zoom, rotation, gamma, render_mode)
end end
end end
if hinting then
Device:enableCPUCores(2)
end
self:preRenderPage() self:preRenderPage()
local page_size = self:getPageDimensions(pageno, zoom, rotation) local page_size = self:getPageDimensions(pageno, zoom, rotation)
@ -466,17 +470,17 @@ function Document:renderPage(pageno, rect, zoom, rotation, gamma, render_mode)
DocCache:insert(hash, tile) DocCache:insert(hash, tile)
self:postRenderPage() self:postRenderPage()
if hinting then
Device:enableCPUCores(1)
end
return tile return tile
end end
-- a hint for the cache engine to paint a full page to the cache -- a hint for the cache engine to paint a full page to the cache
--- @todo this should trigger a background operation --- @todo this should trigger a background operation
function Document:hintPage(pageno, zoom, rotation, gamma, render_mode) function Document:hintPage(pageno, zoom, rotation, gamma, render_mode)
--- @note: Crappy safeguard around memory issues like in #7627: if we're eating too much RAM, drop half the cache...
DocCache:memoryPressureCheck()
logger.dbg("hinting page", pageno) logger.dbg("hinting page", pageno)
self:renderPage(pageno, nil, zoom, rotation, gamma, render_mode) self:renderPage(pageno, nil, zoom, rotation, gamma, render_mode, true)
end end
--[[ --[[

@ -6,6 +6,7 @@ local CacheItem = require("cacheitem")
local CanvasContext = require("document/canvascontext") local CanvasContext = require("document/canvascontext")
local DataStorage = require("datastorage") local DataStorage = require("datastorage")
local DEBUG = require("dbg") local DEBUG = require("dbg")
local Device = require("device")
local DocCache = require("document/doccache") local DocCache = require("document/doccache")
local Document = require("document/document") local Document = require("document/document")
local Geom = require("ui/geometry") local Geom = require("ui/geometry")
@ -99,12 +100,20 @@ function KoptInterface:setDefaultConfigurable(configurable)
end end
function KoptInterface:waitForContext(kc) function KoptInterface:waitForContext(kc)
-- if koptcontext is being processed in background thread -- If our koptcontext is busy in a background thread, isPreCache will return 1.
-- the isPreCache will return 1. local waited = false
while kc and kc:isPreCache() == 1 do while kc and kc:isPreCache() == 1 do
waited = true
logger.dbg("waiting for background rendering") logger.dbg("waiting for background rendering")
util.usleep(100000) util.usleep(100000)
end end
if waited or self.bg_thread then
-- Background thread is done, go back to a single CPU core.
Device:enableCPUCores(1)
self.bg_thread = nil
end
return kc return kc
end end
@ -266,7 +275,7 @@ function KoptInterface:getCachedContext(doc, pageno)
logger.dbg("reflowing page", pageno, "in foreground") logger.dbg("reflowing page", pageno, "in foreground")
-- reflow page -- reflow page
--local secs, usecs = util.gettime() --local secs, usecs = util.gettime()
page:reflow(kc, 0) page:reflow(kc)
page:close() page:close()
--local nsecs, nusecs = util.gettime() --local nsecs, nusecs = util.gettime()
--local dur = nsecs - secs + (nusecs - usecs) / 1000000 --local dur = nsecs - secs + (nusecs - usecs) / 1000000
@ -322,13 +331,13 @@ function KoptInterface:getCoverPageImage(doc)
end end
end end
function KoptInterface:renderPage(doc, pageno, rect, zoom, rotation, gamma, render_mode) function KoptInterface:renderPage(doc, pageno, rect, zoom, rotation, gamma, render_mode, hinting)
if doc.configurable.text_wrap == 1 then if doc.configurable.text_wrap == 1 then
return self:renderReflowedPage(doc, pageno, rect, zoom, rotation, render_mode) return self:renderReflowedPage(doc, pageno, rect, zoom, rotation, render_mode, hinting)
elseif doc.configurable.page_opt == 1 then elseif doc.configurable.page_opt == 1 then
return self:renderOptimizedPage(doc, pageno, rect, zoom, rotation, render_mode) return self:renderOptimizedPage(doc, pageno, rect, zoom, rotation, render_mode, hinting)
else else
return Document.renderPage(doc, pageno, rect, zoom, rotation, gamma, render_mode) return Document.renderPage(doc, pageno, rect, zoom, rotation, gamma, render_mode, hinting)
end end
end end
@ -372,7 +381,7 @@ Render optimized page into tile cache.
Inherited from common document interface. Inherited from common document interface.
--]] --]]
function KoptInterface:renderOptimizedPage(doc, pageno, rect, zoom, rotation, render_mode) function KoptInterface:renderOptimizedPage(doc, pageno, rect, zoom, rotation, render_mode, hinting)
doc.render_mode = render_mode doc.render_mode = render_mode
local bbox = doc:getPageBBox(pageno) local bbox = doc:getPageBBox(pageno)
local hash_list = { "renderoptpg" } local hash_list = { "renderoptpg" }
@ -381,6 +390,10 @@ function KoptInterface:renderOptimizedPage(doc, pageno, rect, zoom, rotation, re
local cached = DocCache:check(hash, TileCacheItem) local cached = DocCache:check(hash, TileCacheItem)
if not cached then if not cached then
if hinting then
Device:enableCPUCores(2)
end
local page_size = Document.getNativePageDimensions(doc, pageno) local page_size = Document.getNativePageDimensions(doc, pageno)
local full_page_bbox = { local full_page_bbox = {
x0 = 0, y0 = 0, x0 = 0, y0 = 0,
@ -409,6 +422,11 @@ function KoptInterface:renderOptimizedPage(doc, pageno, rect, zoom, rotation, re
tile.size = tonumber(tile.bb.stride) * tile.bb.h + 512 -- estimation tile.size = tonumber(tile.bb.stride) * tile.bb.h + 512 -- estimation
kc:free() kc:free()
DocCache:insert(hash, tile) DocCache:insert(hash, tile)
if hinting then
Device:enableCPUCores(1)
end
return tile return tile
else else
return cached return cached
@ -416,10 +434,13 @@ function KoptInterface:renderOptimizedPage(doc, pageno, rect, zoom, rotation, re
end end
function KoptInterface:hintPage(doc, pageno, zoom, rotation, gamma, render_mode) function KoptInterface:hintPage(doc, pageno, zoom, rotation, gamma, render_mode)
--- @note: Crappy safeguard around memory issues like in #7627: if we're eating too much RAM, drop half the cache...
DocCache:memoryPressureCheck()
if doc.configurable.text_wrap == 1 then if doc.configurable.text_wrap == 1 then
self:hintReflowedPage(doc, pageno, zoom, rotation, gamma, render_mode) self:hintReflowedPage(doc, pageno, zoom, rotation, gamma, render_mode, true)
elseif doc.configurable.page_opt == 1 then elseif doc.configurable.page_opt == 1 then
self:renderOptimizedPage(doc, pageno, nil, zoom, rotation, gamma, render_mode) self:renderOptimizedPage(doc, pageno, nil, zoom, rotation, gamma, render_mode, true)
else else
Document.hintPage(doc, pageno, zoom, rotation, gamma, render_mode) Document.hintPage(doc, pageno, zoom, rotation, gamma, render_mode)
end end
@ -434,24 +455,32 @@ off by calling self:waitForContext(kctx)
Inherited from common document interface. Inherited from common document interface.
--]] --]]
function KoptInterface:hintReflowedPage(doc, pageno, zoom, rotation, gamma, render_mode) function KoptInterface:hintReflowedPage(doc, pageno, zoom, rotation, gamma, render_mode, hinting)
local bbox = doc:getPageBBox(pageno) local bbox = doc:getPageBBox(pageno)
local hash_list = { "kctx" } local hash_list = { "kctx" }
self:getContextHash(doc, pageno, bbox, hash_list) self:getContextHash(doc, pageno, bbox, hash_list)
local hash = table.concat(hash_list, "|") local hash = table.concat(hash_list, "|")
local cached = DocCache:check(hash) local cached = DocCache:check(hash)
if not cached then if not cached then
if hinting then
Device:enableCPUCores(2)
end
local kc = self:createContext(doc, pageno, bbox) local kc = self:createContext(doc, pageno, bbox)
local page = doc._document:openPage(pageno) local page = doc._document:openPage(pageno)
logger.dbg("hinting page", pageno, "in background") logger.dbg("hinting page", pageno, "in background")
-- reflow will return immediately and running in background thread -- reflow will return immediately and running in background thread
kc:setPreCache() kc:setPreCache()
page:reflow(kc, 0) self.bg_thread = true
page:reflow(kc)
page:close() page:close()
DocCache:insert(hash, ContextCacheItem:new{ DocCache:insert(hash, ContextCacheItem:new{
size = self.last_context_size or self.default_context_size, size = self.last_context_size or self.default_context_size,
kctx = kc, kctx = kc,
}) })
-- We'll wait until the background thread is done to go back to a single core, as this returns immediately!
-- c.f., :waitForContext
end end
end end

@ -340,8 +340,8 @@ function PdfDocument:findText(pattern, origin, reverse, caseInsensitive, page)
return self.koptinterface:findText(self, pattern, origin, reverse, caseInsensitive, page) return self.koptinterface:findText(self, pattern, origin, reverse, caseInsensitive, page)
end end
function PdfDocument:renderPage(pageno, rect, zoom, rotation, gamma, render_mode) function PdfDocument:renderPage(pageno, rect, zoom, rotation, gamma, render_mode, hinting)
return self.koptinterface:renderPage(self, pageno, rect, zoom, rotation, gamma, render_mode) return self.koptinterface:renderPage(self, pageno, rect, zoom, rotation, gamma, render_mode, hinting)
end end
function PdfDocument:hintPage(pageno, zoom, rotation, gamma, render_mode) function PdfDocument:hintPage(pageno, zoom, rotation, gamma, render_mode)

@ -262,15 +262,6 @@ else
KOBO_TS_INPUT="/dev/input/event1" KOBO_TS_INPUT="/dev/input/event1"
fi fi
# Make sure we only keep two cores online on the Elipsa.
# NOTE: That's a bit optimistic, we might actually need to tone that down to one,
# and just toggle the second one on demand (e.g., PDF).
if [ "${PRODUCT}" = "europa" ]; then
echo "1" >"/sys/devices/system/cpu/cpu1/online"
echo "0" >"/sys/devices/system/cpu/cpu2/online"
echo "0" >"/sys/devices/system/cpu/cpu3/online"
fi
# We'll want to ensure Portrait rotation to allow us to use faster blitting codepaths @ 8bpp, # We'll want to ensure Portrait rotation to allow us to use faster blitting codepaths @ 8bpp,
# so remember the current one before fbdepth does its thing. # so remember the current one before fbdepth does its thing.
IFS= read -r ORIG_FB_ROTA <"/sys/class/graphics/fb0/rotate" IFS= read -r ORIG_FB_ROTA <"/sys/class/graphics/fb0/rotate"

@ -659,6 +659,9 @@ function BookInfoManager:collectSubprocesses()
end end
end end
end end
-- We're done, back to a single core
Device:enableCPUCores(1)
end end
function BookInfoManager:terminateBackgroundJobs() function BookInfoManager:terminateBackgroundJobs()
@ -698,6 +701,11 @@ function BookInfoManager:extractInBackground(files)
self.cleanup_needed = true -- so we will remove temporary cache directory created by subprocess self.cleanup_needed = true -- so we will remove temporary cache directory created by subprocess
-- If it's the first subprocess we're launching, enable 2 CPU cores
if #self.subprocesses_pids == 0 then
Device:enableCPUCores(2)
end
-- Run task in sub-process, and remember its pid -- Run task in sub-process, and remember its pid
local task_pid = FFIUtil.runInSubProcess(task) local task_pid = FFIUtil.runInSubProcess(task)
if not task_pid then if not task_pid then

Loading…
Cancel
Save