Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 0 additions & 3 deletions 3rdparty/python/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@ python_requirements(
"python-gnupg": ["gnupg"],
"node-semver": ["nodesemver"],
},
overrides={
"humbug": {"dependencies": ["#setuptools"]},
},
)

python_requirements(
Expand Down
5 changes: 0 additions & 5 deletions 3rdparty/python/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,6 @@ ansicolors==1.1.8
chevron==0.14.0
fasteners==0.16.3
freezegun==1.2.1

# Note: we use humbug to report telemetry. When upgrading, ensure the new version maintains the
# anonymity promise we make here: https://www.pantsbuild.org/docs/anonymous-telemetry
humbug==0.2.7

importlib_resources==5.0.*
ijson==3.1.4
packaging==21.3
Expand Down
239 changes: 103 additions & 136 deletions 3rdparty/python/user_reqs.lock

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion pants.toml
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,6 @@ delegate_bootstrap = true
enabled = true
repo_id = "7775F8D5-FC58-4DBC-9302-D00AE4A1505F"


[cli.alias]
all-changed = "--changed-since=HEAD --changed-dependents=transitive"
run-pyupgrade = "--backend-packages=pants.backend.experimental.python.lint.pyupgrade fmt"
Expand Down
174 changes: 9 additions & 165 deletions src/python/pants/goal/anonymous_telemetry.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,26 @@

from __future__ import annotations

import json
import logging
import re
import uuid
from dataclasses import dataclass
from typing import cast

from humbug.consent import HumbugConsent
from humbug.report import HumbugReporter, Modes, Report

from pants.engine.internals.scheduler import Workunit
from pants.engine.rules import collect_rules, rule
from pants.engine.streaming_workunit_handler import (
StreamingWorkunitContext,
WorkunitsCallback,
WorkunitsCallbackFactory,
WorkunitsCallbackFactoryRequest,
)
from pants.engine.unions import UnionRule
from pants.option.option_types import BoolOption, StrOption
from pants.option.subsystem import Subsystem
from pants.util.docutil import doc_url
from pants.util.strutil import softwrap

logger = logging.getLogger(__name__)


_bugout_access_token = "974b1acc-e052-4e5f-a45e-bae928e47bb0"
_telemetry_docs_referral = f"See {doc_url('anonymous-telemetry')} for details"


# This subsystem is orphaned (but not quite deprecated/removed).
# We found that:
# - Most people disabled the telemetry
# - The telemetry we received wasn't being used for meaningful decisions
# - The implementation we had required humbug, which bloated Pants' environment with it and its dependencies
#
# The subsystem still exists, however, in hopes that a future implementation could be added seamlessly
# which addresses the above points.
class AnonymousTelemetry(Subsystem):
options_scope = "anonymous-telemetry"
help = "Options related to sending anonymous stats to the Pants project, to aid development."
Expand Down Expand Up @@ -71,150 +60,5 @@ class AnonymousTelemetry(Subsystem):
)


class AnonymousTelemetryCallback(WorkunitsCallback):
def __init__(self, unhashed_repo_id: str) -> None:
super().__init__()
self._unhashed_repo_id = unhashed_repo_id

# Broken out into a staticmethod for testing.
@staticmethod
def validate_repo_id(unhashed_repo_id: str) -> bool:
return re.match(r"^[a-zA-Z0-9-_]{30,60}$", unhashed_repo_id) is not None

@property
def can_finish_async(self) -> bool:
# Because we don't log anything, it's safe to finish in the background.
return True

def __call__(
self,
*,
started_workunits: tuple[Workunit, ...],
completed_workunits: tuple[Workunit, ...],
finished: bool,
context: StreamingWorkunitContext,
) -> None:
if not finished:
return

# Assemble and send the telemetry.
# Note that this method is called with finished=True only after the
# StreamingWorkunitHandler context ends, i.e., after end_run() has been called,
# so the RunTracker will have had a chance to finalize its state.
telemetry_data = context.run_tracker.get_anonymous_telemetry_data(self._unhashed_repo_id)
# TODO: Add information about any errors that occurred.

reporter = HumbugReporter(
name="pantsbuild/pants",
# We've already established consent at this point.
consent=HumbugConsent(True),
session_id=str(telemetry_data.get("run_id", uuid.uuid4())),
bugout_token=_bugout_access_token,
timeout_seconds=5,
# We don't want to spawn a thread in the engine, and we're
# already running in a background thread in pantsd.
mode=Modes.SYNCHRONOUS,
)

# This is copied from humbug code, to ensure that future changes to humbug
# don't add tags that inadvertently violate our anonymity promise.
system_tags = [
f"source:{reporter.name}",
f"os:{reporter.system_information.os}",
f"arch:{reporter.system_information.machine}",
f"python:{reporter.system_information.python_version_major}",
"python:{}.{}".format(
reporter.system_information.python_version_major,
reporter.system_information.python_version_minor,
),
f"python:{reporter.system_information.python_version}",
f"session:{reporter.session_id}",
]
tags = (
system_tags
+ [
f"pants_version:{telemetry_data.get('pants_version')}",
# This is hashed, unlike the contents of the unhashed_repo_id var.
f"repo:{telemetry_data.get('repo_id', 'UNKNOWN')}",
f"user:{telemetry_data.get('user_id', 'UNKNOWN')}",
f"machine:{telemetry_data.get('machine_id', 'UNKNOWN')}",
f"duration:{telemetry_data.get('duration', '0')}",
f"outcome:{telemetry_data.get('outcome', 'UNKNOWN')}",
]
+ [f"goal:{goal}" for goal in telemetry_data.get("standard_goals", [])]
)

report = Report(
title=f"pants run {reporter.session_id}",
tags=tags,
content=json.dumps(telemetry_data, sort_keys=True),
)
reporter.publish(report)


@dataclass(frozen=True)
class AnonymousTelemetryCallbackFactoryRequest:
"""A unique request type that is installed to trigger construction of the WorkunitsCallback."""


@rule
def construct_callback(
_: AnonymousTelemetryCallbackFactoryRequest, anonymous_telemetry: AnonymousTelemetry
) -> WorkunitsCallbackFactory:
enabled = anonymous_telemetry.enabled
unhashed_repo_id = anonymous_telemetry.repo_id

if anonymous_telemetry.options.is_default("enabled"):
logger.warning(
softwrap(
f"""
Please either set `enabled = true` in the [anonymous-telemetry] section of
pants.toml to enable sending anonymous stats to the Pants project to aid
development, or set `enabled = false` to disable it. No telemetry sent
for this run. An explicit setting will get rid of this message.
{_telemetry_docs_referral}.
"""
)
)
if enabled:
if unhashed_repo_id is None:
logger.error(
softwrap(
f"""
Please set `repo_id = "<uuid>"` in the [anonymous-telemetry] section
of pants.toml, where `<uuid>` is some fixed random identifier, such as
one generated by uuidgen.

Example (you may copy this example as is, it is already using randomly generated UUID):

[anonymous-telemetry]
repo_id = "{uuid.uuid4()}"

No telemetry will be sent for this run.
{_telemetry_docs_referral}.
"""
)
)
enabled = False
elif not AnonymousTelemetryCallback.validate_repo_id(unhashed_repo_id):
logger.error(
softwrap(
"""
The repo_id option in the [anonymous-telemetry] scope must be between 30 and
60 characters long, and consist of only alphanumeric characters, dashes
and underscores.
"""
)
)
enabled = False

return WorkunitsCallbackFactory(
lambda: AnonymousTelemetryCallback(cast(str, unhashed_repo_id)) if enabled else None
)


def rules():
return [
UnionRule(WorkunitsCallbackFactoryRequest, AnonymousTelemetryCallbackFactoryRequest),
*collect_rules(),
]
return AnonymousTelemetry.rules()
56 changes: 0 additions & 56 deletions src/python/pants/goal/anonymous_telemetry_integration_test.py

This file was deleted.

35 changes: 0 additions & 35 deletions src/python/pants/goal/anonymous_telemetry_test.py

This file was deleted.