Skip to content

Commit

Permalink
detect free RAM on startup for sane defaults
Browse files Browse the repository at this point in the history
* if free ram on startup is less than 2 GiB,
   use smaller chunks for parallel file hashing

* if --th-max-ram is lower than 0.25 (256 MiB),
   print a warning that thumbnails will not work

* make thumbnail cleaner immediately do a sweep on startup,
   forgetting any failed conversions so they can be retried
   in case the memory limit was increased since last run
  • Loading branch information
9001 committed Nov 10, 2024
1 parent 8aba5ae commit 2bf9055
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 4 deletions.
6 changes: 5 additions & 1 deletion copyparty/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@
PARTFTPY_VER,
PY_DESC,
PYFTPD_VER,
RAM_AVAIL,
RAM_TOTAL,
SQLITE_VER,
UNPLICATIONS,
Daemon,
Expand Down Expand Up @@ -1325,14 +1327,16 @@ def add_admin(ap):


def add_thumbnail(ap):
th_ram = (RAM_AVAIL or RAM_TOTAL or 9) * 0.6
th_ram = int(max(min(th_ram, 6), 1) * 10) / 10
ap2 = ap.add_argument_group('thumbnail options')
ap2.add_argument("--no-thumb", action="store_true", help="disable all thumbnails (volflag=dthumb)")
ap2.add_argument("--no-vthumb", action="store_true", help="disable video thumbnails (volflag=dvthumb)")
ap2.add_argument("--no-athumb", action="store_true", help="disable audio thumbnails (spectrograms) (volflag=dathumb)")
ap2.add_argument("--th-size", metavar="WxH", default="320x256", help="thumbnail res (volflag=thsize)")
ap2.add_argument("--th-mt", metavar="CORES", type=int, default=CORES, help="num cpu cores to use for generating thumbnails")
ap2.add_argument("--th-convt", metavar="SEC", type=float, default=60.0, help="conversion timeout in seconds (volflag=convt)")
ap2.add_argument("--th-ram-max", metavar="GB", type=float, default=6.0, help="max memory usage (GiB) permitted by thumbnailer; not very accurate")
ap2.add_argument("--th-ram-max", metavar="GB", type=float, default=th_ram, help="max memory usage (GiB) permitted by thumbnailer; not very accurate")
ap2.add_argument("--th-crop", metavar="TXT", type=u, default="y", help="crop thumbnails to 4:3 or keep dynamic height; client can override in UI unless force. [\033[32my\033[0m]=crop, [\033[32mn\033[0m]=nocrop, [\033[32mfy\033[0m]=force-y, [\033[32mfn\033[0m]=force-n (volflag=crop)")
ap2.add_argument("--th-x3", metavar="TXT", type=u, default="n", help="show thumbs at 3x resolution; client can override in UI unless force. [\033[32my\033[0m]=yes, [\033[32mn\033[0m]=no, [\033[32mfy\033[0m]=force-yes, [\033[32mfn\033[0m]=force-no (volflag=th3x)")
ap2.add_argument("--th-dec", metavar="LIBS", default="vips,pil,ff", help="image decoders, in order of preference")
Expand Down
9 changes: 9 additions & 0 deletions copyparty/svchub.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,15 @@ def __init__(
t = "WARNING: --s-rd-sz (%d) is larger than --iobuf (%d); this may lead to reduced performance"
self.log("root", t % (args.s_rd_sz, args.iobuf), 3)

zs = ""
if args.th_ram_max < 0.22:
zs = "generate thumbnails"
elif args.th_ram_max < 1:
zs = "generate audio waveforms or spectrograms"
if zs:
t = "WARNING: --th-ram-max is very small (%.2f GiB); will not be able to %s"
self.log("root", t % (args.th_ram_max, zs), 3)

if args.chpw and args.idp_h_usr:
t = "ERROR: user-changeable passwords is incompatible with IdP/identity-providers; you must disable either --chpw or --idp-h-usr"
self.log("root", t, 1)
Expand Down
9 changes: 7 additions & 2 deletions copyparty/th_srv.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
FFMPEG_URL,
Cooldown,
Daemon,
Pebkac,
afsenc,
fsenc,
min_ex,
Expand Down Expand Up @@ -164,6 +163,7 @@ def __init__(self, hub: "SvcHub") -> None:
self.ram: dict[str, float] = {}
self.memcond = threading.Condition(self.mutex)
self.stopping = False
self.rm_nullthumbs = True # forget failed conversions on startup
self.nthr = max(1, self.args.th_mt)

self.q: Queue[Optional[tuple[str, str, str, VFS]]] = Queue(self.nthr * 4)
Expand Down Expand Up @@ -862,7 +862,6 @@ def poke(self, tdir: str) -> None:
def cleaner(self) -> None:
interval = self.args.th_clean
while True:
time.sleep(interval)
ndirs = 0
for vol, histpath in self.asrv.vfs.histtab.items():
if histpath.startswith(vol):
Expand All @@ -876,6 +875,8 @@ def cleaner(self) -> None:
self.log("\033[Jcln err in %s: %r" % (histpath, ex), 3)

self.log("\033[Jcln ok; rm {} dirs".format(ndirs))
self.rm_nullthumbs = False
time.sleep(interval)

def clean(self, histpath: str) -> int:
ret = 0
Expand Down Expand Up @@ -939,6 +940,10 @@ def _clean(self, cat: str, thumbpath: str) -> int:

continue

if self.rm_nullthumbs and not inf.st_size:
bos.unlink(fp)
continue

if b64 == prev_b64:
self.log("rm replaced [{}]".format(fp))
bos.unlink(prev_fp)
Expand Down
24 changes: 23 additions & 1 deletion copyparty/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,27 @@ def _add_mimes() -> None:
VF_CAREFUL = {"mv_re_t": 5, "rm_re_t": 5, "mv_re_r": 0.1, "rm_re_r": 0.1}


def read_ram() -> tuple[float, float]:
a = b = 0
try:
with open("/proc/meminfo", "rb", 0x10000) as f:
zsl = f.read(0x10000).decode("ascii", "replace").split("\n")

p = re.compile("^MemTotal:.* kB")
zs = next((x for x in zsl if p.match(x)))
a = int((int(zs.split()[1]) / 0x100000) * 100) / 100

p = re.compile("^MemAvailable:.* kB")
zs = next((x for x in zsl if p.match(x)))
b = int((int(zs.split()[1]) / 0x100000) * 100) / 100
except:
pass
return a, b


RAM_TOTAL, RAM_AVAIL = read_ram()


pybin = sys.executable or ""
if EXE:
pybin = ""
Expand Down Expand Up @@ -1030,6 +1051,7 @@ def __init__(self, cores: int):
self.sz = 0
self.csz = 0
self.stop = False
self.readsz = 1024 * 1024 * (2 if (RAM_AVAIL or 2) < 1 else 12)
self.omutex = threading.Lock()
self.imutex = threading.Lock()
self.work_q: Queue[int] = Queue()
Expand Down Expand Up @@ -1105,7 +1127,7 @@ def hash_at(self, nch: int) -> tuple[int, str, int, int]:
while chunk_rem > 0:
with self.imutex:
f.seek(ofs)
buf = f.read(min(chunk_rem, 1024 * 1024 * 12))
buf = f.read(min(chunk_rem, self.readsz))

if not buf:
raise Exception("EOF at " + str(ofs))
Expand Down

0 comments on commit 2bf9055

Please sign in to comment.