Skip to content

Commit

Permalink
Update: より詳細なユーザーエージェントを指定する
Browse files Browse the repository at this point in the history
  • Loading branch information
tsukumijima committed Dec 25, 2024
1 parent 1712b93 commit 29bed35
Show file tree
Hide file tree
Showing 7 changed files with 301 additions and 4 deletions.
71 changes: 70 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ pydantic = "^2.7.3"
starlette = "^0.41.3"
jaconv = "^0.3.4"
httpx = "^0.27.0"
gputil = "^1.4.0"
py-cpuinfo = "^9.0.0"
wmi = {version = "^1.5.1", platform = "win32"}
# truststore は HTTPS 通信時にシステムにインストールされた証明書ストアを使うために必要
## ref: https://github.com/psf/requests/issues/2966
## ref: https://truststore.readthedocs.io/en/latest/
Expand Down
2 changes: 1 addition & 1 deletion resources/engine_manifest_assets/dependency_licenses.json

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions run.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
engine_root,
get_save_dir,
)
from voicevox_engine.utility.user_agent_utility import generate_user_agent


def decide_boolean_from_env(env_name: str) -> bool:
Expand Down Expand Up @@ -344,6 +345,9 @@ def main() -> None:
if args.output_log_utf8:
set_output_log_utf8()

# 起動の一番早い段階で実行結果をキャッシュしておくのが重要
generate_user_agent("GPU" if args.use_gpu is True else "CPU")

logger.info(f"AivisSpeech Engine version {__version__}")
logger.info(f"Engine root directory: {engine_root()}")
logger.info(f"User data directory: {get_save_dir()}")
Expand Down
3 changes: 3 additions & 0 deletions tools/generate_licenses.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@ def generate_licenses() -> list[License]:
elif package_name == "flatbuffers":
text_url = "https://raw.githubusercontent.com/google/flatbuffers/v24.3.25/LICENSE" # noqa: B950
license_json["LicenseText"] = get_license_text(text_url)
elif package_name == "gputil":
text_url = "https://raw.githubusercontent.com/anderskm/gputil/refs/heads/master/LICENSE.txt" # noqa: B950
license_json["LicenseText"] = get_license_text(text_url)
elif package_name == "gradio_client":
text_url = "https://raw.githubusercontent.com/gradio-app/gradio/v3.41.0/LICENSE" # noqa: B950
license_json["LicenseText"] = get_license_text(text_url)
Expand Down
5 changes: 3 additions & 2 deletions voicevox_engine/aivm_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
)
from voicevox_engine.metas.MetasStore import Character
from voicevox_engine.model import AivmInfo, LibrarySpeaker
from voicevox_engine.utility.user_agent_utility import generate_user_agent

__all__ = ["AivmManager"]

Expand Down Expand Up @@ -471,7 +472,7 @@ async def fetch_latest_version(aivm_info: AivmInfo) -> None:
async with httpx.AsyncClient() as client:
response = await client.get(
f"{self.AIVISHUB_API_BASE_URL}/aivm-models/{aivm_info.manifest.uuid}",
headers={"User-Agent": f"AivisSpeech-Engine/{__version__}"},
headers={"User-Agent": generate_user_agent()},
timeout=5.0, # 5秒でタイムアウト
)

Expand Down Expand Up @@ -649,7 +650,7 @@ def install_aivm_from_url(self, url: str) -> None:
logger.info(f"Downloading AIVMX file from {url}...")
response = httpx.get(
url,
headers={"User-Agent": f"AivisSpeech-Engine/{__version__}"},
headers={"User-Agent": generate_user_agent()},
# リダイレクトを追跡する
follow_redirects=True,
)
Expand Down
217 changes: 217 additions & 0 deletions voicevox_engine/utility/user_agent_utility.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
import logging
import os
import platform
from typing import Literal

import GPUtil
import psutil
from cpuinfo import get_cpu_info

from voicevox_engine import __version__
from voicevox_engine.logging import logger

# 生成済みのユーザーエージェント文字列をキャッシュする
__user_agent_cache: str | None = None


def generate_user_agent(inference_type: Literal["CPU", "GPU"] = "CPU") -> str:
"""
ユーザーエージェント文字列を生成する。
エラーが発生した場合でも、最低限の情報を含むユーザーエージェント文字列を返す。
"""
global __user_agent_cache

if __user_agent_cache is not None:
return __user_agent_cache

def get_os_version() -> str:
"""
OS バージョンを取得する。
エラー時は 'Unknown' を返す。
"""
try:
os_name = platform.system()
if os_name == "Windows":
try:
wv = platform.win32_ver()
return f"Windows/{wv[1]}"
except Exception as e:
logger.warning("Failed to get Windows version: %s", e)
return "Windows/Unknown"
elif os_name == "Darwin":
try:
ver = platform.mac_ver()[0]
return f"macOS/{ver}" if ver else "macOS/Unknown"
except Exception as e:
logger.warning("Failed to get macOS version: %s", e)
return "macOS/Unknown"
elif os_name == "Linux":
try:
kernel = platform.release()
try:
with open("/etc/os-release") as f:
lines = f.readlines()
for line in lines:
if line.startswith("PRETTY_NAME="):
distro = line.split("=")[1].strip().strip('"')
return f"Linux/{distro} (Kernel: {kernel})"
except Exception as e:
logger.warning("Failed to read /etc/os-release: %s", e)
return f"Linux/{kernel}"
except Exception as e:
logger.warning("Failed to get Linux kernel version: %s", e)
return "Linux/Unknown"
return f"{os_name}/Unknown"
except Exception as e:
logger.error("Failed to get OS information: %s", e)
return "OS/Unknown"

def get_architecture() -> str:
"""
アーキテクチャ情報を取得する。
エラー時は 'Unknown' を返す。
"""
try:
return platform.machine()
except Exception as e:
logger.error("Failed to get architecture information: %s", e)
return "Unknown"

def get_cpu_name() -> str:
"""
CPU 名を取得する。
エラー時は 'Unknown' を返す。
"""
try:
cpu_info = get_cpu_info()
return cpu_info.get("brand_raw", "Unknown")
except Exception as e:
logger.error("Failed to get CPU information: %s", e)
return "Unknown"

def get_gpu_names() -> list[str]:
"""
GPU 名を取得する。
複数の GPU がある場合、すべての名前をリストで返す。
エラー時は ['Unknown'] を返す。
"""
try:
os_name = platform.system()
if os_name == "Windows":
try:
import wmi # type: ignore

w = wmi.WMI()
gpus = w.Win32_VideoController()
names = [gpu.Name for gpu in gpus if hasattr(gpu, "Name")]
return names if names else ["Unknown"]
except Exception as e:
logger.warning("Failed to get Windows GPU information: %s", e)
return ["Unknown"]
elif os_name == "Linux":
try:
if not GPUtil:
return ["NoGPULib"]
gpus = GPUtil.getGPUs()
names = [gpu.name for gpu in gpus if hasattr(gpu, "name")]
return names if names else ["NoGPU"]
except Exception as e:
logger.warning("Failed to get Linux GPU information: %s", e)
return ["Unknown"]
return ["Unknown"]
except Exception as e:
logger.error("Failed to get GPU information: %s", e)
return ["Unknown"]

def get_memory_info() -> tuple[float | None, float | None]:
"""
メモリ情報 (全体のメモリ量と使用可能なメモリ量) を取得する。
エラー時は None, None を返す。
"""
try:
if not psutil:
return None, None
vm = psutil.virtual_memory()
total_gb = round(vm.total / (1024**3), 1)
available_gb = round(vm.available / (1024**3), 1)
return total_gb, available_gb
except Exception as e:
logger.error("Failed to get memory information: %s", e)
return None, None

def is_docker() -> bool:
"""
Docker コンテナ内で実行されているかを判定する。
エラー時は False を返す。
"""
try:
if os.path.exists("/.dockerenv"):
return True
try:
with open("/proc/1/cgroup", "r") as f:
for line in f:
if "docker" in line or "kubepods" in line:
return True
except (FileNotFoundError, PermissionError) as e:
logger.debug("Docker check - could not read cgroup file: %s", e)
return False
except Exception as e:
logger.error("Failed to check Docker environment: %s", e)
return False

try:
# OS・アーキテクチャ・CPU 名
os_version = get_os_version()
arch = get_architecture()
cpu_name = get_cpu_name()

# GPU 情報の取得
# Mac では GPU 情報が取れないので、CPU 名を GPU 名として扱う
gpu_names = get_gpu_names()
gpu_info = ", ".join(gpu_names)
if gpu_info == "Unknown" and platform.system() == "Darwin":
gpu_info = cpu_name

# メモリ情報の取得
total_gb, available_gb = get_memory_info()
mem_info = (
f"{available_gb}GB:{total_gb}GB"
if total_gb is not None and available_gb is not None
else "Unknown"
)

# Docker 判定
docker_flag = " Docker;" if is_docker() else ""

# ユーザーエージェント文字列を生成
user_agent = (
f"AivisSpeech-Engine/{__version__} "
f"({os_version}; {arch};"
f"{docker_flag} "
f"CPU/{cpu_name}; "
f"GPU/{gpu_info}; "
f"Memory/{mem_info}; "
f"Inference/{inference_type})"
)

__user_agent_cache = user_agent
return user_agent

except Exception as e:
# 最悪の場合のフォールバック
logger.error("Failed to generate user agent string: %s", e)
fallback_agent = (
f"AivisSpeech-Engine/{__version__} "
f"(OS/Unknown; Arch/Unknown; "
f"CPU/Unknown; GPU/Unknown; "
f"Memory/Unknown; "
f"Inference/{inference_type})"
)
__user_agent_cache = fallback_agent
return fallback_agent


if __name__ == "__main__":
# Set up logging for standalone execution
logging.basicConfig(level=logging.DEBUG)
print(generate_user_agent("CPU"))

0 comments on commit 29bed35

Please sign in to comment.