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 telemetry #4441

Merged
merged 30 commits into from
Nov 12, 2024
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
6888833
add basic telemetry
valentinsulzer Sep 15, 2024
a643c7b
add posthog dependency
valentinsulzer Sep 15, 2024
c644876
ignore posthog url
valentinsulzer Sep 15, 2024
84a8a87
changelog
valentinsulzer Sep 15, 2024
cb2d075
use uuid to identify individual users
valentinsulzer Sep 15, 2024
a76aff7
reordering
valentinsulzer Sep 15, 2024
4bf6d80
add case where config is not found
valentinsulzer Sep 15, 2024
f7ec885
agriya comments and coverage
valentinsulzer Sep 16, 2024
abac35f
more no cover
valentinsulzer Sep 16, 2024
ad0faa1
merge develop, remove autoinstall
valentinsulzer Sep 26, 2024
025b019
merge develop
valentinsulzer Oct 3, 2024
a41d3ab
update telemetry to opt-in
valentinsulzer Oct 3, 2024
3939e7f
remove duplicate test
valentinsulzer Oct 3, 2024
b8f588b
coverage
valentinsulzer Oct 3, 2024
2e222c6
implement timeout
valentinsulzer Oct 5, 2024
bc53572
Merge branch 'develop' into telemetry
valentinsulzer Oct 6, 2024
a34ff4e
Merge branch 'develop' into telemetry
valentinsulzer Oct 11, 2024
aa9a66e
Merge branch 'develop' into telemetry
kratman Oct 11, 2024
d48f8c6
Update src/pybamm/config.py
valentinsulzer Oct 12, 2024
815bf91
10 second timeout
valentinsulzer Oct 18, 2024
47311e6
Merge branch 'telemetry' of github.com:pybamm-team/PyBaMM into telemetry
valentinsulzer Oct 18, 2024
fd567d2
Merge branch 'develop' into telemetry
valentinsulzer Oct 18, 2024
8d1e192
don't use inputimeout
valentinsulzer Oct 18, 2024
1a835a4
Merge branch 'develop' into telemetry
kratman Oct 18, 2024
75d403d
Merge branch 'develop' into telemetry
valentinsulzer Oct 28, 2024
1bf5b89
add more tests for config
valentinsulzer Oct 28, 2024
7df73c4
Merge branch 'develop' into telemetry
valentinsulzer Oct 30, 2024
215252b
Merge branch 'develop' into telemetry
valentinsulzer Nov 7, 2024
0183250
coverage
valentinsulzer Nov 7, 2024
1022c6b
Merge branch 'develop' into telemetry
valentinsulzer Nov 12, 2024
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
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():
try:
user_input = (

Check warning on line 74 in src/pybamm/config.py

View check run for this annotation

Codecov / codecov/patch

src/pybamm/config.py#L73-L74

Added lines #L73 - L74 were not covered by tests
input("Do you want to enable telemetry? (Y/n): ").strip().lower()
)
answer.append(user_input)
except Exception:

Check warning on line 78 in src/pybamm/config.py

View check run for this annotation

Codecov / codecov/patch

src/pybamm/config.py#L77-L78

Added lines #L77 - L78 were not covered by tests
# Handle any input errors
pass

Check warning on line 80 in src/pybamm/config.py

View check run for this annotation

Codecov / codecov/patch

src/pybamm/config.py#L80

Added line #L80 was not covered by tests

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
Loading