123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175 |
- # Copyright 2024 The Chromium Authors
- # Use of this source code is governed by a BSD-style license that can be
- # found in the LICENSE file.
- """Provides telemetry configuration utilities."""
- import configparser
- import datetime
- import os
- from pathlib import Path
- import shutil
- import tempfile
- import time
- from typing import Literal
- import uuid
- DEFAULT_CONFIG_FILE = Path(Path.home(),
- '.config/chrome_infra/telemetry/telemetry.cfg')
- ROOT_SECTION_KEY = "root"
- NOTICE_COUNTDOWN_KEY = "notice_countdown"
- ENABLED_KEY = "enabled"
- ENABLED_REASON_KEY = "enabled_reason"
- TRACE_SECTION_KEY = "trace"
- DEFAULT_CONFIG = {
- ROOT_SECTION_KEY: {
- NOTICE_COUNTDOWN_KEY: 10
- },
- TRACE_SECTION_KEY: {},
- }
- BATCH_PUBLISHING_ENABLED_KEY = "batch_publishing"
- # The "telemetry in development" config to allow publishing the telemetry, but
- # easily filtering it out later.
- KEY_DEV = "development"
- # Can be set, but the value is unused, pending approvals.
- KEY_USER_UUID = "user_uuid"
- KEY_USER_UUID_TIMESTAMP = "user_uuid_generated"
- class TraceConfig:
- """Tracing specific config in Telemetry config."""
- def __init__(self, config: configparser.ConfigParser) -> None:
- self._config = config
- def update(self, enabled: bool, reason: Literal["AUTO", "USER"]) -> None:
- """Update the config."""
- self._config[TRACE_SECTION_KEY][ENABLED_KEY] = str(enabled)
- self._config[TRACE_SECTION_KEY][ENABLED_REASON_KEY] = reason
- if enabled:
- self.gen_id()
- def gen_id(self, regen=False) -> None:
- """[Re]generate UUIDs."""
- if regen or self._uuid_stale():
- self._config[TRACE_SECTION_KEY][KEY_USER_UUID] = str(uuid.uuid4())
- self._config[TRACE_SECTION_KEY][KEY_USER_UUID_TIMESTAMP] = str(
- int(time.time()))
- @property
- def batch(self) -> bool:
- """Check if batch uploads are configured."""
- return self._config[TRACE_SECTION_KEY].getboolean(
- BATCH_PUBLISHING_ENABLED_KEY, False)
- @batch.setter
- def batch(self, enabled: bool) -> None:
- """Set or delete the batch flag."""
- self._config[TRACE_SECTION_KEY][BATCH_PUBLISHING_ENABLED_KEY] = str(
- enabled)
- def _uuid_stale(self):
- """Check if the UUID is stale or doesn't exist."""
- if (KEY_USER_UUID not in self._config[TRACE_SECTION_KEY] or
- KEY_USER_UUID_TIMESTAMP not in self._config[TRACE_SECTION_KEY]):
- return True
- # Regen the UUID once per week. Regen every Monday so the work week is
- # captured under a single ID.
- regen_ts = int(self._config[TRACE_SECTION_KEY][KEY_USER_UUID_TIMESTAMP])
- regen_dt = datetime.datetime.fromtimestamp(regen_ts)
- today = datetime.datetime.now().replace(hour=0,
- minute=0,
- second=0,
- microsecond=0)
- monday = today - datetime.timedelta(days=today.weekday())
- return regen_dt < monday
- def has_enabled(self) -> bool:
- """Checks if the enabled property exists in config."""
- return ENABLED_KEY in self._config[TRACE_SECTION_KEY]
- @property
- def enabled(self) -> bool:
- """Value of trace.enabled property in telemetry.cfg."""
- return self._config[TRACE_SECTION_KEY].getboolean(ENABLED_KEY, False)
- @property
- def enabled_reason(self) -> Literal["AUTO", "USER"]:
- """Value of trace.enabled_reason property in telemetry.cfg."""
- return self._config[TRACE_SECTION_KEY].get(ENABLED_REASON_KEY, "AUTO")
- @property
- def dev_flag(self):
- """Check the telemetry development flag."""
- return self._config[TRACE_SECTION_KEY].getboolean(KEY_DEV, False)
- @dev_flag.setter
- def dev_flag(self, enabled: bool) -> None:
- """Set or delete the development flag."""
- if enabled:
- self._config[TRACE_SECTION_KEY][KEY_DEV] = str(enabled)
- elif KEY_DEV in self._config[TRACE_SECTION_KEY]:
- del self._config[TRACE_SECTION_KEY][KEY_DEV]
- def user_uuid(self) -> str:
- """Get the user UUID value."""
- return self._config[TRACE_SECTION_KEY].get(KEY_USER_UUID, "")
- class RootConfig:
- """Root configs in Telemetry config."""
- def __init__(self, config) -> None:
- self._config = config
- def update(self, notice_countdown: int) -> None:
- """Update the config."""
- self._config[ROOT_SECTION_KEY][NOTICE_COUNTDOWN_KEY] = str(
- notice_countdown)
- @property
- def notice_countdown(self) -> int:
- """Value for root.notice_countdown property in telemetry.cfg."""
- return self._config[ROOT_SECTION_KEY].getint(NOTICE_COUNTDOWN_KEY, 10)
- class Config:
- """Telemetry configuration."""
- def __init__(self, path: os.PathLike) -> None:
- self._path = Path(path)
- self._config = configparser.ConfigParser()
- self._config.read_dict(DEFAULT_CONFIG)
- if not self._path.exists():
- self.flush()
- else:
- with self._path.open("r", encoding="utf-8") as configfile:
- self._config.read_file(configfile)
- self._trace_config = TraceConfig(self._config)
- self._root_config = RootConfig(self._config)
- def flush(self) -> None:
- """Flushes the current config to config file."""
- tmpfile = tempfile.NamedTemporaryFile()
- with open(tmpfile.name, "w", encoding="utf-8") as configfile:
- self._config.write(configfile)
- if not self._path.parent.exists():
- self._path.parent.mkdir(parents=True, exist_ok=True)
- shutil.copy(tmpfile.name, self._path)
- @property
- def root_config(self) -> RootConfig:
- """The root config in telemetry."""
- return self._root_config
- @property
- def trace_config(self) -> TraceConfig:
- """The trace config in telemetry."""
- return self._trace_config
|