Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add transparent caching, --cache-create, --cache-delete and --cache-q… #155

Merged
merged 6 commits into from
Mar 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ options:
--use-nvidia-current Use nvidia-current instead of nvidia for kernel modules
--reset-sddm Restore default Xsetup file
--reset Revert changes made by EnvyControl
--cache-create Create cache used by EnvyControl; only works in hybrid mode
--cache-delete Delete cache created by EnvyControl
--cache-query Show cache created by EnvyControl
--verbose Enable verbose mode
```

Expand Down Expand Up @@ -121,6 +124,55 @@ Revert all changes made by EnvyControl:
sudo envycontrol --reset
```

### Caching added with 3.4.0
A cache was added in version 3.4.0. The main purpose is to cache the Nvidia PCI bus ID so that a transition from integrated mode directly to nvidia mode is possible.

#### Cache file location

```python
CACHE_FILE_PATH = '/var/cache/envycontrol/cache.json'
```

#### File format

```json
{
"nvidia_gpu_pci_bus": "PCI:1:0:0"
}
```

The cache is automatically re-created whenever a switch from hybrid mode is performed.

#### Caching command line examples

Create cache used by EnvyControl; only works in hybrid mode

```
sudo envycontrol --cache-create
```

When create cache is called when the system is in integrated or nvidia modes

```
sudo envycontrol --cache-create
...
ValueError: --cache-create requires that the system be in the hybrid Optimus mode
```


Delete cache created by EnvyControl

```
sudo envycontrol --cache-delete
```

Show cache created by EnvyControl

```
sudo envycontrol --cache-query
```


## ⬇️ Getting EnvyControl

### Arch Linux ([AUR](https://aur.archlinux.org/packages/envycontrol))
Expand Down
176 changes: 149 additions & 27 deletions envycontrol.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
#!/usr/bin/env python3
import argparse
import sys
import logging
import os
import re
import subprocess
import logging
import sys
from contextlib import contextmanager

# begin constants definition

VERSION = '3.3.1'
VERSION = '3.4.0'

# Note: Do NOT remove this in cleanup!
CACHE_FILE_PATH = '/var/cache/envycontrol/cache.json'

BLACKLIST_PATH = '/etc/modprobe.d/blacklist-nvidia.conf'

Expand Down Expand Up @@ -216,9 +220,12 @@ def graphics_mode_switcher(graphics_mode, user_display_manager, enable_force_com
if graphics_mode == 'integrated':

if logging.getLogger().level == logging.DEBUG:
service = subprocess.run(["systemctl", "disable", "nvidia-persistenced.service"])
service = subprocess.run(
["systemctl", "disable", "nvidia-persistenced.service"])
else:
service = subprocess.run(["systemctl", "disable", "nvidia-persistenced.service"],stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
service = subprocess.run(
["systemctl", "disable", "nvidia-persistenced.service"],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
if service.returncode == 0:
print('Successfully disabled nvidia-persistenced.service')
else:
Expand All @@ -239,9 +246,12 @@ def graphics_mode_switcher(graphics_mode, user_display_manager, enable_force_com
cleanup()

if logging.getLogger().level == logging.DEBUG:
service = subprocess.run(["systemctl", "enable", "nvidia-persistenced.service"])
service = subprocess.run(
["systemctl", "enable", "nvidia-persistenced.service"])
else:
service = subprocess.run(["systemctl", "enable", "nvidia-persistenced.service"],stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
service = subprocess.run(
["systemctl", "enable", "nvidia-persistenced.service"],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
if service.returncode == 0:
print('Successfully enabled nvidia-persistenced.service')
else:
Expand All @@ -255,7 +265,8 @@ def graphics_mode_switcher(graphics_mode, user_display_manager, enable_force_com
else:
# setup rtd3
if use_nvidia_current:
create_file(MODESET_PATH, MODESET_CURRENT_RTD3.format(rtd3_value))
create_file(
MODESET_PATH, MODESET_CURRENT_RTD3.format(rtd3_value))
else:
create_file(MODESET_PATH, MODESET_RTD3.format(rtd3_value))
create_file(UDEV_PM_PATH, UDEV_PM_CONTENT)
Expand All @@ -266,9 +277,12 @@ def graphics_mode_switcher(graphics_mode, user_display_manager, enable_force_com
print(f"Enable Coolbits: {coolbits_value or False}")

if logging.getLogger().level == logging.DEBUG:
service = subprocess.run(["systemctl", "enable", "nvidia-persistenced.service"])
service = subprocess.run(
["systemctl", "enable", "nvidia-persistenced.service"])
else:
service = subprocess.run(["systemctl", "enable", "nvidia-persistenced.service"],stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
service = subprocess.run(
["systemctl", "enable", "nvidia-persistenced.service"],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
if service.returncode == 0:
print('Successfully enabled nvidia-persistenced.service')
else:
Expand Down Expand Up @@ -522,6 +536,12 @@ def main():
help='Restore default Xsetup file')
parser.add_argument('--reset', action='store_true',
help='Revert changes made by EnvyControl')
parser.add_argument('--cache-create', action='store_true',
help='Create cache used by EnvyControl; only works in hybrid mode')
parser.add_argument('--cache-delete', action='store_true',
help='Delete cache created by EnvyControl')
parser.add_argument('--cache-query', action='store_true',
help='Show cache created by EnvyControl')
parser.add_argument('--verbose', default=False, action='store_true',
help='Enable verbose mode')

Expand All @@ -540,26 +560,128 @@ def main():
logging.getLogger().setLevel(logging.DEBUG)

if args.query:
if os.path.exists(BLACKLIST_PATH) and os.path.exists(UDEV_INTEGRATED_PATH):
mode = 'integrated'
elif os.path.exists(XORG_PATH) and os.path.exists(MODESET_PATH):
mode = 'nvidia'
else:
mode = 'hybrid'
mode = get_current_mode()
print(mode)
elif args.switch:
assert_root()
graphics_mode_switcher(args.switch, args.dm,
args.force_comp, args.coolbits, args.rtd3, args.use_nvidia_current)
elif args.reset_sddm:
return
elif args.cache_create:
assert_root()
create_file(SDDM_XSETUP_PATH, SDDM_XSETUP_CONTENT, True)
print('Operation completed successfully')
elif args.reset:
CachedConfig(args).create_cache_file()
return
elif args.cache_delete:
assert_root()
cleanup()
rebuild_initramfs()
print('Operation completed successfully')
CachedConfig.delete_cache_file()
return
elif args.cache_query:
CachedConfig.show_cache_file()
return

if args.switch or args.reset_sddm or args.reset:
with CachedConfig(args).adapter():
if args.switch:
assert_root()
graphics_mode_switcher(
args.switch, args.dm,
args.force_comp, args.coolbits, args.rtd3, args.use_nvidia_current
)
elif args.reset_sddm:
assert_root()
create_file(SDDM_XSETUP_PATH, SDDM_XSETUP_CONTENT, True)
print('Operation completed successfully')
elif args.reset:
assert_root()
cleanup()
CachedConfig.delete_cache_file()
rebuild_initramfs()
print('Operation completed successfully')


class CachedConfig:
'''Adapter for config from CACHE_FILE_PATH'''

def __init__(self, app_args) -> None:
self.app_args = app_args
self.current_mode = get_current_mode()

@contextmanager
def adapter(self):
global get_nvidia_gpu_pci_bus
use_cache = os.path.exists(CACHE_FILE_PATH)

if self.is_hybrid(): # recreate cache file when in hybrid mode
self.create_cache_file()

if use_cache:
self.read_cache_file() # might not be in hybrid mode

# rebind function to use cached value instead of detection
get_nvidia_gpu_pci_bus = self.get_nvidia_gpu_pci_bus

yield # back to main ...

def create_cache_file(self):
if not self.is_hybrid():
raise ValueError(
'--cache-create requires that the system be in the hybrid Optimus mode')

self.nvidia_gpu_pci_bus = get_nvidia_gpu_pci_bus()
self.obj = self.create_cache_obj(self.nvidia_gpu_pci_bus)
self.write_cache_file()

def create_cache_obj(self, nvidia_gpu_pci_bus):
return {
'nvidia_gpu_pci_bus': nvidia_gpu_pci_bus
}

def is_hybrid(self):
return 'hybrid' == self.current_mode

def get_nvidia_gpu_pci_bus(self):
return self.nvidia_gpu_pci_bus

@staticmethod
def delete_cache_file():
os.remove(CACHE_FILE_PATH)
os.removedirs(os.path.dirname(CACHE_FILE_PATH))
logging.debug(f"Removed file {CACHE_FILE_PATH}")

def read_cache_file(self):
from json import loads
if os.path.exists(CACHE_FILE_PATH):
with open(CACHE_FILE_PATH, 'r', encoding='utf-8') as f:
content = f.read()
self.obj = loads(content)
self.nvidia_gpu_pci_bus = self.obj['nvidia_gpu_pci_bus']
elif self.is_hybrid():
self.nvidia_gpu_pci_bus = get_nvidia_gpu_pci_bus()
else:
raise ValueError(
'No cache present. Operation requires that the system be in the hybrid Optimus mode')

@staticmethod
def show_cache_file():
content = f'ERROR: Could not read {CACHE_FILE_PATH}'
if os.path.exists(CACHE_FILE_PATH):
with open(CACHE_FILE_PATH, 'r', encoding='utf-8') as f:
content = f.read()
print(content)

def write_cache_file(self):
from json import dump
os.makedirs(os.path.dirname(CACHE_FILE_PATH), exist_ok=True)

with open(CACHE_FILE_PATH, 'w', encoding='utf-8') as f:
dump(self.obj, fp=f, indent=4, sort_keys=False)

logging.debug(f"Created file {CACHE_FILE_PATH}")


def get_current_mode():
mode = 'hybrid'
if os.path.exists(BLACKLIST_PATH) and os.path.exists(UDEV_INTEGRATED_PATH):
mode = 'integrated'
elif os.path.exists(XORG_PATH) and os.path.exists(MODESET_PATH):
mode = 'nvidia'
return mode


if __name__ == '__main__':
Expand Down