mirror of
				https://github.com/blakeblackshear/frigate.git
				synced 2025-10-27 10:52:11 +01:00 
			
		
		
		
	Replace subprocess usage with os module for better performance and maintainability (#6298)
* avoid executing external tools by using Python's built-in os module to interact with the filesystem directly * Refactor recording cleanup script to use os module instead of subprocess * black format util.py * Ooooops * Refactor get_cpu_stats() to properly identify recording process
This commit is contained in:
		
							parent
							
								
									19890310fe
								
							
						
					
					
						commit
						b38c9e82e2
					
				@ -3,7 +3,7 @@
 | 
				
			|||||||
import datetime
 | 
					import datetime
 | 
				
			||||||
import itertools
 | 
					import itertools
 | 
				
			||||||
import logging
 | 
					import logging
 | 
				
			||||||
import subprocess as sp
 | 
					import os
 | 
				
			||||||
import threading
 | 
					import threading
 | 
				
			||||||
from pathlib import Path
 | 
					from pathlib import Path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -192,12 +192,14 @@ class RecordingCleanup(threading.Thread):
 | 
				
			|||||||
            return
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        logger.debug(f"Oldest recording in the db: {oldest_timestamp}")
 | 
					        logger.debug(f"Oldest recording in the db: {oldest_timestamp}")
 | 
				
			||||||
        process = sp.run(
 | 
					
 | 
				
			||||||
            ["find", RECORD_DIR, "-type", "f", "!", "-newermt", f"@{oldest_timestamp}"],
 | 
					        files_to_check = []
 | 
				
			||||||
            capture_output=True,
 | 
					
 | 
				
			||||||
            text=True,
 | 
					        for root, _, files in os.walk(RECORD_DIR):
 | 
				
			||||||
        )
 | 
					            for file in files:
 | 
				
			||||||
        files_to_check = process.stdout.splitlines()
 | 
					                file_path = os.path.join(root, file)
 | 
				
			||||||
 | 
					                if os.path.getmtime(file_path) < oldest_timestamp:
 | 
				
			||||||
 | 
					                    files_to_check.append(file_path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for f in files_to_check:
 | 
					        for f in files_to_check:
 | 
				
			||||||
            p = Path(f)
 | 
					            p = Path(f)
 | 
				
			||||||
@ -216,12 +218,10 @@ class RecordingCleanup(threading.Thread):
 | 
				
			|||||||
        recordings: Recordings = Recordings.select()
 | 
					        recordings: Recordings = Recordings.select()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # get all recordings files on disk
 | 
					        # get all recordings files on disk
 | 
				
			||||||
        process = sp.run(
 | 
					        files_on_disk = []
 | 
				
			||||||
            ["find", RECORD_DIR, "-type", "f"],
 | 
					        for root, _, files in os.walk(RECORD_DIR):
 | 
				
			||||||
            capture_output=True,
 | 
					            for file in files:
 | 
				
			||||||
            text=True,
 | 
					                files_on_disk.append(os.path.join(root, file))
 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        files_on_disk = process.stdout.splitlines()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        recordings_to_delete = []
 | 
					        recordings_to_delete = []
 | 
				
			||||||
        for recording in recordings.objects().iterator():
 | 
					        for recording in recordings.objects().iterator():
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										112
									
								
								frigate/util.py
									
									
									
									
									
								
							
							
						
						
									
										112
									
								
								frigate/util.py
									
									
									
									
									
								
							@ -9,6 +9,7 @@ import signal
 | 
				
			|||||||
import traceback
 | 
					import traceback
 | 
				
			||||||
import urllib.parse
 | 
					import urllib.parse
 | 
				
			||||||
import yaml
 | 
					import yaml
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from abc import ABC, abstractmethod
 | 
					from abc import ABC, abstractmethod
 | 
				
			||||||
from collections import Counter
 | 
					from collections import Counter
 | 
				
			||||||
@ -740,55 +741,54 @@ def escape_special_characters(path: str) -> str:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_cgroups_version() -> str:
 | 
					def get_cgroups_version() -> str:
 | 
				
			||||||
    """Determine what version of cgroups is enabled"""
 | 
					    """Determine what version of cgroups is enabled."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    stat_command = ["stat", "-fc", "%T", "/sys/fs/cgroup"]
 | 
					    cgroup_path = "/sys/fs/cgroup"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    p = sp.run(
 | 
					    if not os.path.ismount(cgroup_path):
 | 
				
			||||||
        stat_command,
 | 
					        logger.debug(f"{cgroup_path} is not a mount point.")
 | 
				
			||||||
        encoding="ascii",
 | 
					        return "unknown"
 | 
				
			||||||
        capture_output=True,
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if p.returncode == 0:
 | 
					    try:
 | 
				
			||||||
        value: str = p.stdout.strip().lower()
 | 
					        with open("/proc/mounts", "r") as f:
 | 
				
			||||||
 | 
					            mounts = f.readlines()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if value == "cgroup2fs":
 | 
					        for mount in mounts:
 | 
				
			||||||
            return "cgroup2"
 | 
					            mount_info = mount.split()
 | 
				
			||||||
        elif value == "tmpfs":
 | 
					            if mount_info[1] == cgroup_path:
 | 
				
			||||||
            return "cgroup"
 | 
					                fs_type = mount_info[2]
 | 
				
			||||||
        else:
 | 
					                if fs_type == "cgroup2fs" or fs_type == "cgroup2":
 | 
				
			||||||
            logger.debug(
 | 
					                    return "cgroup2"
 | 
				
			||||||
                f"Could not determine cgroups version: unhandled filesystem {value}"
 | 
					                elif fs_type == "tmpfs":
 | 
				
			||||||
            )
 | 
					                    return "cgroup"
 | 
				
			||||||
    else:
 | 
					                else:
 | 
				
			||||||
        logger.debug(f"Could not determine cgroups version:  {p.stderr}")
 | 
					                    logger.debug(
 | 
				
			||||||
 | 
					                        f"Could not determine cgroups version: unhandled filesystem {fs_type}"
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                break
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        logger.debug(f"Could not determine cgroups version: {e}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return "unknown"
 | 
					    return "unknown"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_docker_memlimit_bytes() -> int:
 | 
					def get_docker_memlimit_bytes() -> int:
 | 
				
			||||||
    """Get mem limit in bytes set in docker if present. Returns -1 if no limit detected"""
 | 
					    """Get mem limit in bytes set in docker if present. Returns -1 if no limit detected."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # check running a supported cgroups version
 | 
					    # check running a supported cgroups version
 | 
				
			||||||
    if get_cgroups_version() == "cgroup2":
 | 
					    if get_cgroups_version() == "cgroup2":
 | 
				
			||||||
        memlimit_command = ["cat", "/sys/fs/cgroup/memory.max"]
 | 
					        memlimit_path = "/sys/fs/cgroup/memory.max"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        p = sp.run(
 | 
					        try:
 | 
				
			||||||
            memlimit_command,
 | 
					            with open(memlimit_path, "r") as f:
 | 
				
			||||||
            encoding="ascii",
 | 
					                value = f.read().strip()
 | 
				
			||||||
            capture_output=True,
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if p.returncode == 0:
 | 
					 | 
				
			||||||
            value: str = p.stdout.strip()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if value.isnumeric():
 | 
					            if value.isnumeric():
 | 
				
			||||||
                return int(value)
 | 
					                return int(value)
 | 
				
			||||||
            elif value.lower() == "max":
 | 
					            elif value.lower() == "max":
 | 
				
			||||||
                return -1
 | 
					                return -1
 | 
				
			||||||
        else:
 | 
					        except Exception as e:
 | 
				
			||||||
            logger.debug(f"Unable to get docker memlimit: {p.stderr}")
 | 
					            logger.debug(f"Unable to get docker memlimit: {e}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return -1
 | 
					    return -1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -796,49 +796,41 @@ def get_docker_memlimit_bytes() -> int:
 | 
				
			|||||||
def get_cpu_stats() -> dict[str, dict]:
 | 
					def get_cpu_stats() -> dict[str, dict]:
 | 
				
			||||||
    """Get cpu usages for each process id"""
 | 
					    """Get cpu usages for each process id"""
 | 
				
			||||||
    usages = {}
 | 
					    usages = {}
 | 
				
			||||||
    # -n=2 runs to ensure extraneous values are not included
 | 
					 | 
				
			||||||
    top_command = ["top", "-b", "-n", "2"]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    docker_memlimit = get_docker_memlimit_bytes() / 1024
 | 
					    docker_memlimit = get_docker_memlimit_bytes() / 1024
 | 
				
			||||||
 | 
					    total_mem = os.sysconf("SC_PAGE_SIZE") * os.sysconf("SC_PHYS_PAGES") / 1024
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    p = sp.run(
 | 
					    for pid in os.listdir("/proc"):
 | 
				
			||||||
        top_command,
 | 
					        if pid.isdigit():
 | 
				
			||||||
        encoding="ascii",
 | 
					 | 
				
			||||||
        capture_output=True,
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if p.returncode != 0:
 | 
					 | 
				
			||||||
        logger.error(p.stderr)
 | 
					 | 
				
			||||||
        return usages
 | 
					 | 
				
			||||||
    else:
 | 
					 | 
				
			||||||
        lines = p.stdout.split("\n")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for line in lines:
 | 
					 | 
				
			||||||
            stats = list(filter(lambda a: a != "", line.strip().split(" ")))
 | 
					 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
 | 
					                with open(f"/proc/{pid}/stat", "r") as f:
 | 
				
			||||||
 | 
					                    stats = f.readline().split()
 | 
				
			||||||
 | 
					                utime = int(stats[13])
 | 
				
			||||||
 | 
					                stime = int(stats[14])
 | 
				
			||||||
 | 
					                cpu_usage = round((utime + stime) / os.sysconf("SC_CLK_TCK"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                with open(f"/proc/{pid}/statm", "r") as f:
 | 
				
			||||||
 | 
					                    mem_stats = f.readline().split()
 | 
				
			||||||
 | 
					                mem_res = int(mem_stats[1]) * os.sysconf("SC_PAGE_SIZE") / 1024
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if docker_memlimit > 0:
 | 
					                if docker_memlimit > 0:
 | 
				
			||||||
                    mem_res = int(stats[5])
 | 
					                    mem_pct = round((mem_res / docker_memlimit) * 100, 1)
 | 
				
			||||||
                    mem_pct = str(
 | 
					 | 
				
			||||||
                        round((float(mem_res) / float(docker_memlimit)) * 100, 1)
 | 
					 | 
				
			||||||
                    )
 | 
					 | 
				
			||||||
                else:
 | 
					                else:
 | 
				
			||||||
                    mem_pct = stats[9]
 | 
					                    mem_pct = round((mem_res / total_mem) * 100, 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                idx = stats[0]
 | 
					                idx = pid
 | 
				
			||||||
 | 
					                if stats[1] == "(go2rtc)":
 | 
				
			||||||
                if stats[-1] == "go2rtc":
 | 
					 | 
				
			||||||
                    idx = "go2rtc"
 | 
					                    idx = "go2rtc"
 | 
				
			||||||
                elif stats[-1] == "frigate.r+":
 | 
					                if stats[1].startswith("(frigate.r"):
 | 
				
			||||||
                    idx = "recording"
 | 
					                    idx = "recording"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                usages[idx] = {
 | 
					                usages[idx] = {
 | 
				
			||||||
                    "cpu": stats[8],
 | 
					                    "cpu": str(round(cpu_usage, 2)),
 | 
				
			||||||
                    "mem": mem_pct,
 | 
					                    "mem": f"{mem_pct}",
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            except:
 | 
					            except:
 | 
				
			||||||
                continue
 | 
					                continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return usages
 | 
					    return usages
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_amd_gpu_stats() -> dict[str, str]:
 | 
					def get_amd_gpu_stats() -> dict[str, str]:
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user