Skip to content

Commit

Permalink
Merge pull request pybamm-team#4441 from pybamm-team/telemetry
Browse files Browse the repository at this point in the history
Add telemetry
  • Loading branch information
valentinsulzer authored Nov 12, 2024
2 parents 9ac0b5b + 1022c6b commit 0eae1a4
Show file tree
Hide file tree
Showing 13 changed files with 389 additions and 6 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ input/*

# simulation outputs
out/
config.py
matplotlibrc
*.pickle
*.sav
Expand Down
3 changes: 3 additions & 0 deletions .lycheeignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@ file:///home/runner/work/PyBaMM/PyBaMM/docs/source/user_guide/fundamentals/pybam

# Errors in docs/source/user_guide/index.md
file:///home/runner/work/PyBaMM/PyBaMM/docs/source/user_guide/api_docs

# Telemetry
https://us.i.posthog.com
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
- Adds an option "voltage as a state" that can be "false" (default) or "true". If "true" adds an explicit algebraic equation for the voltage. ([#4507](https://github.com/pybamm-team/PyBaMM/pull/4507))
- Improved `QuickPlot` accuracy for simulations with Hermite interpolation. ([#4483](https://github.com/pybamm-team/PyBaMM/pull/4483))
- Added Hermite interpolation to the (`IDAKLUSolver`) that improves the accuracy and performance of post-processing variables. ([#4464](https://github.com/pybamm-team/PyBaMM/pull/4464))
- Added basic telemetry to record which functions are being run. See [Telemetry section in the User Guide](https://docs.pybamm.org/en/latest/source/user_guide/index.html#telemetry) for more information. ([#4441](https://github.com/pybamm-team/PyBaMM/pull/4441))
- Added `BasicDFN` model for sodium-ion batteries ([#4451](https://github.com/pybamm-team/PyBaMM/pull/4451))
- Added sensitivity calculation support for `pybamm.Simulation` and `pybamm.Experiment` ([#4415](https://github.com/pybamm-team/PyBaMM/pull/4415))
- Added OpenMP parallelization to IDAKLU solver for lists of input parameters ([#4449](https://github.com/pybamm-team/PyBaMM/pull/4449))
- Added phase-dependent particle options to LAM
([#4369](https://github.com/pybamm-team/PyBaMM/pull/4369))
- Added phase-dependent particle options to LAM ([#4369](https://github.com/pybamm-team/PyBaMM/pull/4369))
- Added a lithium ion equivalent circuit model with split open circuit voltages for each electrode (`SplitOCVR`). ([#4330](https://github.com/pybamm-team/PyBaMM/pull/4330))
- Added the `pybamm.DiscreteTimeSum` expression node to sum an expression over a sequence of data times, and accompanying `pybamm.DiscreteTimeData` class to store the data times and values ([#4501](https://github.com/pybamm-team/PyBaMM/pull/4501))

Expand Down
5 changes: 5 additions & 0 deletions conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,8 @@ def set_random_seed():
@pytest.fixture(autouse=True)
def set_debug_value():
pybamm.settings.debug_mode = True


@pytest.fixture(autouse=True)
def disable_telemetry():
pybamm.telemetry.disable()
9 changes: 9 additions & 0 deletions docs/source/user_guide/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,12 @@ glob:
../examples/notebooks/creating_models/5-half-cell-model.ipynb
../examples/notebooks/creating_models/6-a-simple-SEI-model.ipynb
```

# Telemetry

PyBaMM optionally collects anonymous usage data to help improve the library. This telemetry is opt-in and can be easily disabled. Here's what you need to know:

- **What is collected**: Basic usage information like PyBaMM version, Python version, and which functions are run.
- **Why**: To understand how PyBaMM is used and prioritize development efforts.
- **Opt-out**: To disable telemetry, set the environment variable `PYBAMM_DISABLE_TELEMETRY=true` (or any value other than `false`) or use `pybamm.telemetry.disable()` in your code.
- **Privacy**: No personal information (name, email, etc) or sensitive information (parameter values, simulation results, etc) is ever collected.
1 change: 1 addition & 0 deletions docs/source/user_guide/installation/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ Package Minimum supp
`typing-extensions <https://pypi.org/project/typing-extensions/>`__ 4.10.0
`pandas <https://pypi.org/project/pandas/>`__ 1.5.0
`pooch <https://www.fatiando.org/pooch/>`__ 1.8.1
`posthog <https://posthog.com/>`__ 3.6.5
=================================================================== ==========================

.. _install.optional_dependencies:
Expand Down
1 change: 1 addition & 0 deletions noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ def set_iree_state():
"IREE_INDEX_URL": os.getenv(
"IREE_INDEX_URL", "https://iree.dev/pip-release-links.html"
),
"PYBAMM_DISABLE_TELEMETRY": "true",
}
VENV_DIR = Path("./venv").resolve()

Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ dependencies = [
"typing-extensions>=4.10.0",
"pandas>=1.5.0",
"pooch>=1.8.1",
"posthog",
]

[project.urls]
Expand Down
11 changes: 8 additions & 3 deletions src/pybamm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,8 +194,8 @@
# Batch Study
from .batch_study import BatchStudy

# Callbacks
from . import callbacks
# Callbacks, telemetry, config
from . import callbacks, telemetry, config

# Pybamm Data manager using pooch
from .pybamm_data import DataLoader
Expand All @@ -204,12 +204,14 @@
import os
import pathlib
import sysconfig
os.environ["CASADIPATH"] = str(pathlib.Path(sysconfig.get_path('purelib')) / 'casadi')

os.environ["CASADIPATH"] = str(pathlib.Path(sysconfig.get_path("purelib")) / "casadi")

__all__ = [
"batch_study",
"callbacks",
"citations",
"config",
"discretisations",
"doc_utils",
"experiment",
Expand All @@ -225,8 +227,11 @@
"simulation",
"solvers",
"spatial_methods",
"telemetry",
"type_definitions",
"util",
"version",
"pybamm_data",
]

pybamm.config.generate()
163 changes: 163 additions & 0 deletions src/pybamm/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import uuid
import os
import platformdirs
from pathlib import Path
import pybamm
import sys
import threading
import time


def is_running_tests(): # pragma: no cover
"""
Detect if the code is being run as part of a test suite or building docs with Sphinx.
Returns:
bool: True if running tests or building docs, False otherwise.
"""
# Check if pytest or unittest is running
if any(
test_module in sys.modules for test_module in ["pytest", "unittest", "nose"]
):
return True

# Check for GitHub Actions environment variable
if "GITHUB_ACTIONS" in os.environ:
return True

# Check for other common CI environment variables
ci_env_vars = ["CI", "TRAVIS", "CIRCLECI", "JENKINS_URL", "GITLAB_CI"]
if any(var in os.environ for var in ci_env_vars):
return True

# Check for common test runner names in command-line arguments
test_runners = ["pytest", "unittest", "nose", "trial", "nox", "tox"]
if any(runner in arg.lower() for arg in sys.argv for runner in test_runners):
return True

# Check if building docs with Sphinx
if any(mod == "sphinx" or mod.startswith("sphinx.") for mod in sys.modules):
print(
f"Found Sphinx module: {[mod for mod in sys.modules if mod.startswith('sphinx')]}"
)
return True

return False


def ask_user_opt_in(timeout=10):
"""
Ask the user if they want to opt in to telemetry.
Parameters
----------
timeout : float, optional
The timeout for the user to respond to the prompt. Default is 10 seconds.
Returns
-------
bool
True if the user opts in, False otherwise.
"""
print(
"PyBaMM can collect usage data and send it to the PyBaMM team to "
"help us improve the software.\n"
"We do not collect any sensitive information such as models, parameters, "
"or simulation results - only information on which parts of the code are "
"being used and how frequently.\n"
"This is entirely optional and does not impact the functionality of PyBaMM.\n"
"For more information, see https://docs.pybamm.org/en/latest/source/user_guide/index.html#telemetry"
)

def get_input(): # pragma: no cover
try:
user_input = (
input("Do you want to enable telemetry? (Y/n): ").strip().lower()
)
answer.append(user_input)
except Exception:
# Handle any input errors
pass

time_start = time.time()

while True:
if time.time() - time_start > timeout:
print("\nTimeout reached. Defaulting to not enabling telemetry.")
return False

answer = []
# Create and start input thread
input_thread = threading.Thread(target=get_input)
input_thread.daemon = True
input_thread.start()

# Wait for either timeout or input
input_thread.join(timeout)

if answer:
if answer[0] in ["yes", "y", ""]:
print("\nTelemetry enabled.\n")
return True
elif answer[0] in ["no", "n"]:
print("\nTelemetry disabled.\n")
return False
else:
print("\nInvalid input. Please enter 'yes/y' for yes or 'no/n' for no.")
else:
print("\nTimeout reached. Defaulting to not enabling telemetry.")
return False


def generate():
if is_running_tests():
return

# Check if the config file already exists
if read() is not None:
return

# Ask the user if they want to opt in to telemetry
opt_in = ask_user_opt_in()
config_file = Path(platformdirs.user_config_dir("pybamm")) / "config.yml"
write_uuid_to_file(config_file, opt_in)

if opt_in:
pybamm.telemetry.capture("user-opted-in")


def read():
config_file = Path(platformdirs.user_config_dir("pybamm")) / "config.yml"
return read_uuid_from_file(config_file)


def write_uuid_to_file(config_file, opt_in):
# Create the directory if it doesn't exist
config_file.parent.mkdir(parents=True, exist_ok=True)

# Write the UUID to the config file in YAML format
with open(config_file, "w") as f:
f.write("pybamm:\n")
f.write(f" enable_telemetry: {opt_in}\n")
if opt_in:
unique_id = uuid.uuid4()
f.write(f" uuid: {unique_id}\n")


def read_uuid_from_file(config_file):
# Check if the config file exists
if not config_file.exists():
return None

# Read the UUID from the config file
with open(config_file) as f:
content = f.read().strip()

# Extract the UUID using YAML parsing
try:
import yaml

config = yaml.safe_load(content)
return config["pybamm"]
except (yaml.YAMLError, ValueError):
return None
3 changes: 3 additions & 0 deletions src/pybamm/simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import warnings
from functools import lru_cache
from datetime import timedelta
import pybamm.telemetry
from pybamm.util import import_optional_dependency

from pybamm.expression_tree.operations.serialise import Serialise
Expand Down Expand Up @@ -450,6 +451,8 @@ def solve(
Additional key-word arguments passed to `solver.solve`.
See :meth:`pybamm.BaseSolver.solve`.
"""
pybamm.telemetry.capture("simulation-solved")

# Setup
if solver is None:
solver = self._solver
Expand Down
36 changes: 36 additions & 0 deletions src/pybamm/telemetry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from posthog import Posthog
import os
import pybamm
import sys

_posthog = Posthog(
# this is the public, write only API key, so it's ok to include it here
project_api_key="phc_bLZKBW03XjgiRhbWnPsnKPr0iw0z03fA6ZZYjxgW7ej",
host="https://us.i.posthog.com",
)


def disable():
_posthog.disabled = True


_opt_out = os.getenv("PYBAMM_DISABLE_TELEMETRY", "false").lower()
if _opt_out != "false": # pragma: no cover
disable()


def capture(event): # pragma: no cover
# don't capture events in automated testing
if pybamm.config.is_running_tests() or _posthog.disabled:
return

properties = {
"python_version": sys.version,
"pybamm_version": pybamm.__version__,
}

config = pybamm.config.read()
if config:
if config["enable_telemetry"]:
user_id = config["uuid"]
_posthog.capture(user_id, event, properties=properties)
Loading

0 comments on commit 0eae1a4

Please sign in to comment.