Skip to content

Commit

Permalink
Add transparent caching, --cache-create, --cache-delete and --cache-q… (
Browse files Browse the repository at this point in the history
#155)

* Add transparent caching, --cache-create, --cache-delete and --cache-query options

* Update version to 3.4.0; put rebuild_initramfs call back during --reset; update README

* Fixes #79 - tests passing for all mode transitions

* Address review comments

* Simplify cache object schema per review comment
  • Loading branch information
klmcwhirter authored Mar 6, 2024
1 parent 0a66637 commit 8ad51d6
Show file tree
Hide file tree
Showing 2 changed files with 201 additions and 27 deletions.
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

0 comments on commit 8ad51d6

Please sign in to comment.