Przeglądaj źródła

Add telemetry initialization and opt out utility

This includes initializing the module which will handle printing the
notice and enabling/disabling telemetry based on config. The __main__
file also allows for the banner to have a consistent entry point for
disabling telemetry so instrumenting programs don't need to worry
about it if they are using the same default config file.

Bug: 326277821
Change-Id: I861d6b9311ed48c2e1effcac22b314b73e2641e3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/5901465
Reviewed-by: Terrence Reilly <treilly@google.com>
Commit-Queue: Struan Shrimpton <sshrimp@google.com>
Struan Shrimpton 10 miesięcy temu
rodzic
commit
d495580f42

+ 122 - 0
infra_lib/telemetry/__init__.py

@@ -0,0 +1,122 @@
+# 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.
+from typing import Optional
+import os
+import socket
+import sys
+import pathlib
+
+from opentelemetry import context as otel_context_api
+from opentelemetry import trace as otel_trace_api
+from opentelemetry.sdk import (
+    resources as otel_resources,
+    trace as otel_trace_sdk,
+)
+from opentelemetry.sdk.trace import export as otel_export
+from opentelemetry.util import types as otel_types
+
+from . import config
+from . import clearcut_span_exporter
+from . import detector
+
+DEFAULT_BANNER = """
+===============================================================================
+To help improve the quality of this product, we collect usage data and
+stacktraces from googlers. This includes a uuid generated weekly to identify
+invocation from the same users as well as metrics as described in
+go/chrome-infra-telemetry-readme. You may choose to opt out of this collection
+at any time by setting the flag `enabled = False` under [trace] section in
+{config_file}
+or by executing from your depot_tools checkout:
+
+vpython3 third_party/depot_tools/infra_lib/telemetry --disable
+
+This notice will be displayed {run_count} more times.
+===============================================================================
+"""
+
+# This does not include Googlers' physical machines/laptops
+_GOOGLE_HOSTNAME_SUFFIX = ('.google.com', '.googler.com', '.googlers.com')
+
+# The version keeps track of telemetry changes.
+_TELEMETRY_VERSION = '3'
+
+
+def get_host_name(fully_qualified: bool = False) -> str:
+    """Return hostname of current machine, with domain if |fully_qualified|."""
+    hostname = socket.gethostname()
+    try:
+        hostname = socket.gethostbyaddr(hostname)[0]
+    except (socket.gaierror, socket.herror) as e:
+        logging.warning(
+            'please check your /etc/hosts file; resolving your hostname'
+            ' (%s) failed: %s',
+            hostname,
+            e,
+        )
+
+    if fully_qualified:
+        return hostname
+    return hostname.partition('.')[0]
+
+
+def is_google_host() -> bool:
+    """Checks if the code is running on google host."""
+
+    hostname = get_host_name(fully_qualified=True)
+    return hostname.endswith(_GOOGLE_HOSTNAME_SUFFIX)
+
+
+def initialize(service_name,
+               notice=DEFAULT_BANNER,
+               cfg_file=config.DEFAULT_CONFIG_FILE):
+    if not is_google_host():
+        return
+
+    # TODO(326277821): Add support for other platforms
+    if not sys.platform == 'linux':
+        return
+
+    cfg = config.Config(cfg_file)
+
+    if not cfg.trace_config.has_enabled():
+        if cfg.root_config.notice_countdown > -1:
+            print(notice.format(run_count=cfg.root_config.notice_countdown,
+                                config_file=cfg_file),
+                  file=sys.stderr)
+            cfg.root_config.update(
+                notice_countdown=cfg.root_config.notice_countdown - 1)
+        else:
+            cfg.trace_config.update(enabled=True, reason='AUTO')
+
+        cfg.flush()
+
+    if not cfg.trace_config.enabled:
+        return
+
+    default_resource = otel_resources.Resource.create({
+        otel_resources.SERVICE_NAME:
+        service_name,
+        'telemetry.version':
+        _TELEMETRY_VERSION,
+    })
+
+    detected_resource = otel_resources.get_aggregated_resources([
+        otel_resources.ProcessResourceDetector(),
+        otel_resources.OTELResourceDetector(),
+        detector.ProcessDetector(),
+        detector.SystemDetector(),
+    ])
+
+    resource = detected_resource.merge(default_resource)
+    trace_provider = otel_trace_sdk.TracerProvider(resource=resource)
+    otel_trace_api.set_tracer_provider(trace_provider)
+    trace_provider.add_span_processor(
+        otel_export.BatchSpanProcessor(
+            # Replace with ConsoleSpanExporter() to debug spans on the console
+            clearcut_span_exporter.ClearcutSpanExporter()))
+
+
+def get_tracer(name: str, version: Optional[str] = None):
+    return otel_trace_api.get_tracer(name, version)

+ 41 - 0
infra_lib/telemetry/__main__.py

@@ -0,0 +1,41 @@
+#!/bin/env vpython3
+# 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.
+"""Utility for opting in or out of metrics collection"""
+import argparse
+import sys
+import pathlib
+
+import config
+
+
+def main():
+    parser = argparse.ArgumentParser(description=__doc__)
+
+    parser.add_argument('--disable',
+                        '-d',
+                        dest='enable',
+                        action='store_false',
+                        default=None,
+                        help='Disable telemetry collection.')
+
+    parser.add_argument('--enable',
+                        '-e',
+                        dest='enable',
+                        action='store_true',
+                        default=None,
+                        help='Enable telemetry collection.')
+
+    args = parser.parse_args()
+
+    if args.enable is not None:
+        cfg = config.Config(config.DEFAULT_CONFIG_FILE)
+        cfg.trace_config.update(args.enable, 'USER')
+        cfg.flush()
+    else:
+        print('Error: --enable or --disable flag is required.')
+
+
+if __name__ == '__main__':
+    sys.exit(main())

+ 3 - 0
infra_lib/telemetry/config.py

@@ -13,6 +13,9 @@ 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"