"""Run well on a small machine without becoming a smaller application.
This is the fallback reached after normal optimisations. It turns down
decorative rendering costs instead of removing application features.
Nothing here changes what a run computes. Every setting it touches is
about how the application is DRAWN.
It is chosen automatically, it can be overridden either way, and it says
what it turned down -- a mode that quietly makes the application different
is one nobody can debug.
"""
from __future__ import annotations
import os
from typing import Dict, List, Optional, Tuple
#: Below this many usable cores, a machine is treated as small. Four is the
#: line because the ambient layer, the blur and the fit all want a core each
#: and the interface still needs one to stay answerable.
SMALL_CORE_COUNT = 4
#: Below this much RAM in GiB, likewise. 8 GiB is where a browser, a Python
#: process holding a plate of images and the compositor stop fitting.
SMALL_MEMORY_GIB = 8.0
#: The environment variable that overrides the decision either way.
#: ``1``/``true``/``on`` forces it on, ``0``/``false``/``off`` forces it off,
#: absent leaves it to the measurement.
OVERRIDE_VARIABLE = "SPACR_LAPTOP_MODE"
_TRUE = {"1", "true", "yes", "on"}
_FALSE = {"0", "false", "no", "off"}
[docs]
def usable_cores() -> int:
"""Cores this process may actually use, not cores the machine has.
`os.sched_getaffinity` where it exists: a container or a scheduler can
pin the process to fewer than `cpu_count` reports, and pinning is
exactly the situation this mode is for.
"""
try:
return max(1, len(os.sched_getaffinity(0)))
except (AttributeError, OSError): # pragma: no cover - not Linux
return max(1, os.cpu_count() or 1)
[docs]
def total_memory_gib() -> Optional[float]:
"""Physical memory in GiB, or None when it cannot be read."""
try:
pages = os.sysconf("SC_PHYS_PAGES")
size = os.sysconf("SC_PAGE_SIZE")
return (pages * size) / (1024 ** 3)
except (AttributeError, ValueError, OSError):
return None
[docs]
def override() -> Optional[bool]:
"""What the environment says, or None when it says nothing."""
raw = str(os.environ.get(OVERRIDE_VARIABLE, "")).strip().lower()
if raw in _TRUE:
return True
if raw in _FALSE:
return False
return None
[docs]
def measure() -> Dict[str, object]:
"""What the decision is made from, so it can be reported and tested."""
cores = usable_cores()
memory = total_memory_gib()
return {
"cores": cores,
"memory_gib": memory,
"few_cores": cores < SMALL_CORE_COUNT,
"little_memory": memory is not None and memory < SMALL_MEMORY_GIB,
"override": override(),
}
[docs]
def wanted(reading: Optional[Dict[str, object]] = None) -> Tuple[bool, str]:
"""Whether to run in laptop mode, and the sentence that says why.
:returns: ``(on, why)``. The reason is always populated, including when
the answer is no -- "spaCR decided not to" is a thing a user reports
and somebody has to be able to check.
"""
reading = measure() if reading is None else reading
chosen = reading.get("override")
cores = reading.get("cores")
memory = reading.get("memory_gib")
seen = (f"{cores} usable core(s)"
+ (f", {memory:.1f} GiB of memory" if memory else ""))
if chosen is True:
return True, f"laptop mode: on because {OVERRIDE_VARIABLE} asks for it"
if chosen is False:
return False, f"laptop mode: off because {OVERRIDE_VARIABLE} asks for it"
reasons: List[str] = []
if reading.get("few_cores"):
reasons.append(f"fewer than {SMALL_CORE_COUNT} usable cores")
if reading.get("little_memory"):
reasons.append(f"less than {SMALL_MEMORY_GIB:g} GiB of memory")
if reasons:
return True, (f"laptop mode: on -- this machine has {seen} "
f"({' and '.join(reasons)}). "
f"Set {OVERRIDE_VARIABLE}=0 to keep everything on.")
return False, (f"laptop mode: off -- this machine has {seen}. "
f"Set {OVERRIDE_VARIABLE}=1 to turn it on anyway.")
[docs]
def what_it_turns_down() -> Tuple[Tuple[str, str], ...]:
"""Each thing the mode changes, and what a user loses by it.
ONLY DRAWING. Nothing here is read by a pipeline, so a run computes the
same answer in either mode -- which is the promise that makes an
automatic decision acceptable at all.
"""
return (
("the ambient background animation",
"the moving backdrop stops; every colour and control is unchanged"),
("the backdrop blur",
"panels sit on a flat ground instead of a blurred one"),
)
[docs]
def describe() -> str:
"""The whole decision as one block, for the launch log."""
on, why = wanted()
lines = [why]
if on:
lines.append("turned down, and only the drawing:")
lines.extend(f" - {what}: {cost}" for what, cost in what_it_turns_down())
lines.append("a run computes exactly the same answer either way.")
return "\n".join(lines)
#: The process-local backdrop switch, and the only thing this module turns.
#:
#: SUPPRESSING IS NOT THE SAME AS DECIDING, and this module used to do the
#: second. It called `preferences.set_ambient_enabled(False)`, which is the
#: USER'S stored answer -- so on a machine under the core or memory bar,
#: every launch overwrote a preference the user had set, and a user who
#: turned the animation back on got it taken away again the next time they
#: started spaCR, with nothing on the surface to say why. A hardware guess
#: was writing into the one row that is supposed to mean "what I chose".
#:
#: `SPACR_NO_BACKDROP` is the mechanism that already existed for exactly
#: this, and `get_ambient_enabled` documents it: process-local, never saved,
#: honoured before the stored value, "so the next clean run brings it back
#: with nothing for the user to undo". `crash_recovery` uses it after spaCR
#: has died on launch twice. A performance constraint is the same shape of
#: thing -- true for this run, on this machine, and none of it the user's
#: opinion.
#:
#: Found by the Preferences audit.
_NO_BACKDROP = "SPACR_NO_BACKDROP"
#: Whether the variable above is OURS to clear.
#:
#: `crash_recovery` sets the same variable, and it sets it because spaCR has
#: already failed to start twice. Turning the mode off must not hand the
#: backdrop back to a driver that has crashed on it -- so `apply` clears the
#: variable only when this module is what set it.
_suppressed_here = False
[docs]
def apply(on: Optional[bool] = None) -> Dict[str, object]:
"""Turn the mode on or off. Returns what was decided and what changed.
:param on: force the answer; ``None`` measures the machine.
:returns: ``{"on", "why", "changed"}`` -- `changed` names each thing
actually done, so a caller can say what happened rather than
claim it.
NOTHING HERE IS PERSISTED. See :data:`_NO_BACKDROP` for why: this is a
decision about one run on one machine, and the user's stored preference
is not this module's to answer.
"""
global _suppressed_here
if on is None:
on, why = wanted()
else:
why = (f"laptop mode: {'on' if on else 'off'} because the caller "
f"asked for it")
changed: List[str] = []
if on:
try:
from . import preferences
if preferences.get_ambient_enabled():
os.environ[_NO_BACKDROP] = "1"
_suppressed_here = True
changed.append("ambient backdrop suppressed for this run")
except Exception: # noqa: BLE001
pass
elif _suppressed_here:
os.environ.pop(_NO_BACKDROP, None)
_suppressed_here = False
changed.append("ambient backdrop restored")
return {"on": on, "why": why, "changed": changed}