Bug 1694646 - Remove old build telemetry. r=mhentges

Now that telemetry is gathered with glean, we can remove the old telemetry.

Differential Revision: https://phabricator.services.mozilla.com/D106735
This commit is contained in:
Alex Lopez 2021-03-02 15:04:27 +00:00
Родитель f4ebf61baf
Коммит 201a891006
10 изменённых файлов: 135 добавлений и 1053 удалений

Просмотреть файл

@ -13,354 +13,11 @@ You can opt-in to send telemetry to Mozilla during
``./mach bootstrap`` or by editing your ``.mozbuild/machrc``
file.
Telemetry
=========
The build telemetry schema can be found in-tree under
``python/mozbuild/mozbuild/telemetry.py`` in Voluptuous schema
format. You can use the ``export_telemetry_schema.py`` script in
that same directory to get the schema in JSON-schema format.
Details of the schema are specified below:
.. _telemetry.json#/:
:type: ``object``
:Required: :ref:`telemetry.json#/properties/argv`, :ref:`telemetry.json#/properties/build_opts`, :ref:`telemetry.json#/properties/client_id`, :ref:`telemetry.json#/properties/command`, :ref:`telemetry.json#/properties/duration_ms`, :ref:`telemetry.json#/properties/success`, :ref:`telemetry.json#/properties/system`, :ref:`telemetry.json#/properties/time`
**Properties:** :ref:`telemetry.json#/properties/argv`, :ref:`telemetry.json#/properties/build_opts`, :ref:`telemetry.json#/properties/client_id`, :ref:`telemetry.json#/properties/command`, :ref:`telemetry.json#/properties/duration_ms`, :ref:`telemetry.json#/properties/exception`, :ref:`telemetry.json#/properties/file_types_changed`, :ref:`telemetry.json#/properties/success`, :ref:`telemetry.json#/properties/system`, :ref:`telemetry.json#/properties/time`
.. _telemetry.json#/properties/argv:
argv
++++
Full mach commandline. If the commandline contains absolute paths they will be sanitized.
:type: ``array``
.. container:: sub-title
Every element of **argv** is:
:type: ``string``
.. _telemetry.json#/properties/build_opts:
build_opts
++++++++++
Selected build options
:type: ``object``
**Properties:** :ref:`telemetry.json#/properties/build_opts/properties/artifact`, :ref:`telemetry.json#/properties/build_opts/properties/ccache`, :ref:`telemetry.json#/properties/build_opts/properties/compiler`, :ref:`telemetry.json#/properties/build_opts/properties/debug`, :ref:`telemetry.json#/properties/build_opts/properties/icecream`, :ref:`telemetry.json#/properties/build_opts/properties/opt`, :ref:`telemetry.json#/properties/build_opts/properties/sccache`
.. _telemetry.json#/properties/build_opts/properties/artifact:
artifact
########
true if --enable-artifact-builds
:type: ``boolean``
.. _telemetry.json#/properties/build_opts/properties/ccache:
ccache
######
true if ccache is in use (--with-ccache)
:type: ``boolean``
.. _telemetry.json#/properties/build_opts/properties/compiler:
compiler
########
The compiler type in use (CC_TYPE)
**Allowed values:**
- clang
- clang-cl
- gcc
- msvc
.. _telemetry.json#/properties/build_opts/properties/debug:
debug
#####
true if build is debug (--enable-debug)
:type: ``boolean``
.. _telemetry.json#/properties/build_opts/properties/icecream:
icecream
########
true if icecream in use
:type: ``boolean``
.. _telemetry.json#/properties/build_opts/properties/opt:
opt
###
true if build is optimized (--enable-optimize)
:type: ``boolean``
.. _telemetry.json#/properties/build_opts/properties/sccache:
sccache
#######
true if ccache in use is sccache
:type: ``boolean``
.. _telemetry.json#/properties/build_attrs:
build_attrs
+++++++++++
Selected runtime attributes of the build
:type: ``object``
**Properties:** :ref:`telemetry.json#/properties/build_attrs/properties/cpu_percent`, :ref:`telemetry.json#/properties/build_attrs/properties/clobber`
.. _telemetry.json#/properties/build_attrs/properties/cpu_percent:
cpu_percent
###########
cpu utilization observed during the build
:type: ``number``
.. _telemetry.json#/properties/build_attrs/properties/clobber:
clobber
#######
true if the build was a clobber/full build
:type: ``boolean``
.. _telemetry.json#/properties/client_id:
client_id
+++++++++
A UUID to uniquely identify a client
:type: ``string``
.. _telemetry.json#/properties/command:
command
+++++++
The mach command that was invoked
:type: ``string``
.. _telemetry.json#/properties/duration_ms:
duration_ms
+++++++++++
Command duration in milliseconds
:type: ``number``
.. _telemetry.json#/properties/exception:
exception
+++++++++
If a Python exception was encountered during the execution of the command, this value contains the result of calling `repr` on the exception object.
:type: ``string``
.. _telemetry.json#/properties/file_types_changed:
file_types_changed
++++++++++++++++++
This array contains a list of objects with {ext, count} properties giving the count of files changed since the last invocation grouped by file type
:type: ``array``
.. container:: sub-title
Every element of **file_types_changed** is:
:type: ``object``
:Required: :ref:`telemetry.json#/properties/file_types_changed/items/properties/count`, :ref:`telemetry.json#/properties/file_types_changed/items/properties/ext`
**Properties:** :ref:`telemetry.json#/properties/file_types_changed/items/properties/count`, :ref:`telemetry.json#/properties/file_types_changed/items/properties/ext`
.. _telemetry.json#/properties/file_types_changed/items/properties/count:
count
#####
Count of changed files with this extension
:type: ``number``
.. _telemetry.json#/properties/file_types_changed/items/properties/ext:
ext
###
File extension
:type: ``string``
.. _telemetry.json#/properties/success:
success
+++++++
true if the command succeeded
:type: ``boolean``
.. _telemetry.json#/properties/system:
system
++++++
:type: ``object``
:Required: :ref:`telemetry.json#/properties/system/properties/os`
**Properties:** :ref:`telemetry.json#/properties/system/properties/cpu_brand`, :ref:`telemetry.json#/properties/system/properties/drive_is_ssd`, :ref:`telemetry.json#/properties/system/properties/logical_cores`, :ref:`telemetry.json#/properties/system/properties/memory_gb`, :ref:`telemetry.json#/properties/system/properties/os`, :ref:`telemetry.json#/properties/system/properties/physical_cores`, :ref:`telemetry.json#/properties/system/properties/virtual_machine`
.. _telemetry.json#/properties/system/properties/cpu_brand:
cpu_brand
#########
CPU brand string from CPUID
:type: ``string``
.. _telemetry.json#/properties/system/properties/drive_is_ssd:
drive_is_ssd
############
true if the source directory is on a solid-state disk
:type: ``boolean``
.. _telemetry.json#/properties/system/properties/logical_cores:
logical_cores
#############
Number of logical CPU cores present
:type: ``number``
.. _telemetry.json#/properties/system/properties/memory_gb:
memory_gb
#########
System memory in GB
:type: ``number``
.. _telemetry.json#/properties/system/properties/os:
os
##
Operating system
**Allowed values:**
- windows
- macos
- linux
- other
.. _telemetry.json#/properties/system/properties/physical_cores:
physical_cores
##############
Number of physical CPU cores present
:type: ``number``
.. _telemetry.json#/properties/system/properties/virtual_machine:
virtual_machine
###############
true if the OS appears to be running in a virtual machine
:type: ``boolean``
.. _telemetry.json#/properties/time:
time
++++
Time at which this event happened
:type: ``string``
:format: ``date-time``
Glean Telemetry
===============
In addition to the existing build-specific telemetry, Mozbuild is also reporting data using
`Glean <https://mozilla.github.io/glean/>`_ via :ref:`mach_telemetry`.
The metrics collected are documented :ref:`here<metrics>`.
As Python 2 is phased out, the old telemetry will be replaced by the new Glean implementation.
Mozbuild reports data using `Glean <https://mozilla.github.io/glean/>`_ via
:ref:`mach_telemetry`. The metrics collected are documented :ref:`here<metrics>`.
Error Reporting
===============

Просмотреть файл

@ -4,15 +4,11 @@
from __future__ import division, print_function, unicode_literals
import errno
import json
import math
import os
import platform
import shutil
import subprocess
import sys
import uuid
if sys.version_info[0] < 3:
import __builtin__ as builtins
@ -318,9 +314,6 @@ def bootstrap(topsrcdir, mozilla_dir=None):
_finalize_telemetry_glean(
context.telemetry, handler.name == "bootstrap", success
)
_finalize_telemetry_legacy(
context, instance, handler, success, start_time, end_time, topsrcdir
)
def populate_context(key=None):
if key is None:
@ -396,96 +389,6 @@ def bootstrap(topsrcdir, mozilla_dir=None):
return driver
def _finalize_telemetry_legacy(
context, instance, handler, success, start_time, end_time, topsrcdir
):
"""Record and submit legacy telemetry.
Parameterized by the raw gathered telemetry, this function handles persisting and
submission of the data.
This has been designated as "legacy" telemetry because modern telemetry is being
submitted with "Glean".
"""
from mozboot.util import get_state_dir
from mozbuild.base import MozbuildObject
from mozbuild.telemetry import gather_telemetry
from mach.telemetry import is_telemetry_enabled, is_applicable_telemetry_environment
if not (
is_applicable_telemetry_environment() and is_telemetry_enabled(context.settings)
):
return
if not isinstance(instance, MozbuildObject):
instance = MozbuildObject.from_environment()
command_attrs = getattr(context, "command_attrs", {})
# We gather telemetry for every operation.
data = gather_telemetry(
command=handler.name,
success=success,
start_time=start_time,
end_time=end_time,
mach_context=context,
instance=instance,
command_attrs=command_attrs,
)
if data:
telemetry_dir = os.path.join(get_state_dir(), "telemetry")
try:
os.mkdir(telemetry_dir)
except OSError as e:
if e.errno != errno.EEXIST:
raise
outgoing_dir = os.path.join(telemetry_dir, "outgoing")
try:
os.mkdir(outgoing_dir)
except OSError as e:
if e.errno != errno.EEXIST:
raise
with open(os.path.join(outgoing_dir, str(uuid.uuid4()) + ".json"), "w") as f:
json.dump(data, f, sort_keys=True)
# The user is performing a maintenance command, skip the upload
if handler.name in (
"bootstrap",
"doctor",
"mach-commands",
"vcs-setup",
"create-mach-environment",
"install-moz-phab",
# We call mach environment in client.mk which would cause the
# data submission to block the forward progress of make.
"environment",
):
return False
if "TEST_MACH_TELEMETRY_NO_SUBMIT" in os.environ:
# In our telemetry tests, we want telemetry to be collected for analysis, but
# we don't want it submitted.
return False
state_dir = get_state_dir()
machpath = os.path.join(instance.topsrcdir, "mach")
with open(os.devnull, "wb") as devnull:
subprocess.Popen(
[
sys.executable,
machpath,
"python",
"--no-virtualenv",
os.path.join(topsrcdir, "build", "submit_telemetry_data.py"),
state_dir,
],
stdout=devnull,
stderr=devnull,
)
def _finalize_telemetry_glean(telemetry, is_bootstrap, success):
"""Submit telemetry collected by Glean.

Просмотреть файл

@ -1,153 +0,0 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from __future__ import print_function
import datetime
import json
import logging
import os
import sys
import requests
import voluptuous
import voluptuous.humanize
from mozbuild.telemetry import (
schema as build_telemetry_schema,
verify_statedir,
)
BUILD_TELEMETRY_URL = "https://incoming.telemetry.mozilla.org/{endpoint}"
SUBMIT_ENDPOINT = "submit/eng-workflow/build/1/{ping_uuid}"
STATUS_ENDPOINT = "status"
def delete_expired_files(directory, days=30):
"""Discards files in a directory older than a specified number
of days
"""
now = datetime.datetime.now()
for filename in os.listdir(directory):
filepath = os.path.join(directory, filename)
ctime = os.path.getctime(filepath)
then = datetime.datetime.fromtimestamp(ctime)
if (now - then) > datetime.timedelta(days=days):
os.remove(filepath)
return
def check_edge_server_status(session):
"""Returns True if the Telemetry Edge Server
is ready to accept data
"""
status_url = BUILD_TELEMETRY_URL.format(endpoint=STATUS_ENDPOINT)
response = session.get(status_url)
if response.status_code != 200:
return False
return True
def send_telemetry_ping(session, data, ping_uuid):
"""Sends a single build telemetry ping to the
edge server, returning the response object
"""
resource_url = SUBMIT_ENDPOINT.format(ping_uuid=str(ping_uuid))
url = BUILD_TELEMETRY_URL.format(endpoint=resource_url)
response = session.post(url, json=data)
return response
def submit_telemetry_data(outgoing, submitted):
"""Sends information about `./mach build` invocations to
the Telemetry pipeline
"""
with requests.Session() as session:
# Confirm the server is OK
if not check_edge_server_status(session):
logging.error('Error posting to telemetry: server status is not "200 OK"')
return 1
for filename in os.listdir(outgoing):
path = os.path.join(outgoing, filename)
if os.path.isdir(path) or not path.endswith(".json"):
logging.info("skipping item {}".format(path))
continue
ping_uuid = os.path.splitext(filename)[0] # strip ".json" to get ping UUID
try:
with open(path, "r") as f:
data = json.load(f)
# Verify the data matches the schema
voluptuous.humanize.validate_with_humanized_errors(
data, build_telemetry_schema
)
response = send_telemetry_ping(session, data, ping_uuid)
if response.status_code != 200:
msg = "response code {code} sending {uuid} to telemetry: {body}".format(
body=response.content,
code=response.status_code,
uuid=ping_uuid,
)
logging.error(msg)
continue
# Move from "outgoing" to "submitted"
os.rename(
os.path.join(outgoing, filename), os.path.join(submitted, filename)
)
logging.info("successfully posted {} to telemetry".format(ping_uuid))
except ValueError as ve:
# ValueError is thrown if JSON cannot be decoded
logging.exception("exception parsing JSON at %s: %s" % (path, str(ve)))
os.remove(path)
except voluptuous.Error as e:
# Invalid is thrown if some data does not fit
# the correct Schema
logging.exception("invalid data found at %s: %s" % (path, e.message))
os.remove(path)
except Exception as e:
logging.error("exception posting to telemetry " "server: %s" % str(e))
break
delete_expired_files(submitted)
return 0
if __name__ == "__main__":
if len(sys.argv) != 2:
print("usage: python submit_telemetry_data.py <statedir>")
sys.exit(1)
statedir = sys.argv[1]
try:
outgoing, submitted, telemetry_log = verify_statedir(statedir)
# Configure logging
logging.basicConfig(
filename=telemetry_log,
format="%(asctime)s %(message)s",
level=logging.DEBUG,
)
sys.exit(submit_telemetry_data(outgoing, submitted))
except Exception as e:
# Handle and print messages from `statedir` verification
print(e.message)
sys.exit(1)

Просмотреть файл

@ -122,7 +122,9 @@ def report_invocation_metrics(telemetry, command):
# Without this information, we're unable to filter argv paths, so
# we skip submitting them to telemetry.
return
metrics.mach.argv.set(filter_args(command, sys.argv, instance))
metrics.mach.argv.set(
filter_args(command, sys.argv, instance.topsrcdir, instance.topobjdir)
)
def is_applicable_telemetry_environment():

Просмотреть файл

@ -11,5 +11,3 @@ skip-if = python == 3
skip-if = python == 3
[test_logger.py]
[test_mach.py]
[test_telemetry.py]
skip-if = python == 2 && os == "mac"

Просмотреть файл

@ -1,198 +0,0 @@
# Any copyright is dedicated to the Public Domain.
# http://creativecommons.org/publicdomain/zero/1.0/
from __future__ import absolute_import, print_function
import json
import os
import platform
import subprocess
import sys
import buildconfig
import mozunit
import pytest
from six import text_type, PY3
from mozboot.bootstrap import update_or_create_build_telemetry_config
TELEMETRY_LOAD_ERROR = """
Error loading telemetry. mach output:
=========================================================
%s
=========================================================
"""
@pytest.fixture
def run_mach(tmpdir):
"""Return a function that runs mach with the provided arguments and then returns
a list of the data contained within any telemetry entries generated during the command.
"""
# Use tmpdir as the mozbuild state path, and enable telemetry in
# a machrc there.
if PY3:
update_or_create_build_telemetry_config(str(tmpdir.join("machrc")))
else:
update_or_create_build_telemetry_config(text_type(tmpdir.join("machrc")))
env = dict(os.environ)
env["MOZBUILD_STATE_PATH"] = str(tmpdir)
env["TEST_MACH_TELEMETRY_NO_SUBMIT"] = "1"
mach = os.path.join(buildconfig.topsrcdir, "mach")
def run(*args, **kwargs):
# Let whatever mach command we invoke from tests believe it's the main command.
mach_main_pid = env.pop("MACH_MAIN_PID")
moz_automation = env.pop("MOZ_AUTOMATION", None)
task_id = env.pop("TASK_ID", None)
# Run mach with the provided arguments
out = subprocess.check_output(
[sys.executable, mach] + list(args),
stderr=subprocess.STDOUT,
env=env,
**kwargs
)
env["MACH_MAIN_PID"] = mach_main_pid
env["MOZ_AUTOMATION"] = moz_automation
env["TASK_ID"] = task_id
# Load any telemetry data that was written
path = tmpdir.join("telemetry", "outgoing")
try:
if PY3:
read_mode = "r"
else:
read_mode = "rb"
return [json.load(f.open(read_mode)) for f in path.listdir()]
except EnvironmentError:
print(TELEMETRY_LOAD_ERROR % out, file=sys.stderr)
for p in path.parts(reverse=True):
if not p.check(dir=1):
print('Path does not exist: "%s"' % p, file=sys.stderr)
raise
return run
def test_simple(run_mach, tmpdir):
data = run_mach("python", "-c", "pass")
assert len(data) == 1
d = data[0]
assert d["command"] == "python"
assert d["argv"] == ["-c", "pass"]
if PY3:
read_mode = "r"
else:
read_mode = "rb"
client_id_data = json.load(tmpdir.join("telemetry_client_id.json").open(read_mode))
assert "client_id" in client_id_data
assert client_id_data["client_id"] == d["client_id"]
@pytest.mark.xfail(
platform.system() == "Windows" and PY3,
reason="Windows and Python3 mozpath filtering issues",
)
def test_path_filtering(run_mach, tmpdir):
srcdir_path = os.path.join(buildconfig.topsrcdir, "a")
srcdir_path_2 = os.path.join(buildconfig.topsrcdir, "a/b/c")
objdir_path = os.path.join(buildconfig.topobjdir, "x")
objdir_path_2 = os.path.join(buildconfig.topobjdir, "x/y/z")
home_path = os.path.join(os.path.expanduser("~"), "something_in_home")
other_path = str(tmpdir.join("other"))
data = run_mach(
"python",
"-c",
"pass",
srcdir_path,
srcdir_path_2,
objdir_path,
objdir_path_2,
home_path,
other_path,
cwd=buildconfig.topsrcdir,
)
assert len(data) == 1
d = data[0]
expected = [
"-c",
"pass",
"a",
"a/b/c",
"$topobjdir/x",
"$topobjdir/x/y/z",
"$HOME/something_in_home",
"<path omitted>",
]
assert d["argv"] == expected
@pytest.mark.xfail(
platform.system() == "Windows" and PY3,
reason="Windows and Python3 mozpath filtering issues",
)
def test_path_filtering_in_objdir(run_mach, tmpdir):
srcdir_path = os.path.join(buildconfig.topsrcdir, "a")
srcdir_path_2 = os.path.join(buildconfig.topsrcdir, "a/b/c")
objdir_path = os.path.join(buildconfig.topobjdir, "x")
objdir_path_2 = os.path.join(buildconfig.topobjdir, "x/y/z")
other_path = str(tmpdir.join("other"))
data = run_mach(
"python",
"-c",
"pass",
srcdir_path,
srcdir_path_2,
objdir_path,
objdir_path_2,
other_path,
cwd=buildconfig.topobjdir,
)
assert len(data) == 1
d = data[0]
expected = [
"-c",
"pass",
"$topsrcdir/a",
"$topsrcdir/a/b/c",
"x",
"x/y/z",
"<path omitted>",
]
assert d["argv"] == expected
def test_path_filtering_other_cwd(run_mach, tmpdir):
srcdir_path = os.path.join(buildconfig.topsrcdir, "a")
srcdir_path_2 = os.path.join(buildconfig.topsrcdir, "a/b/c")
other_path = str(tmpdir.join("other"))
data = run_mach(
"python", "-c", "pass", srcdir_path, srcdir_path_2, other_path, cwd=str(tmpdir)
)
assert len(data) == 1
d = data[0]
expected = [
# non-path arguments should escape unscathed
"-c",
"pass",
"$topsrcdir/a",
"$topsrcdir/a/b/c",
# cwd-relative paths should be relativized
"other",
]
assert d["argv"] == expected
def test_zero_microseconds(run_mach):
data = run_mach(
"python",
"--exec-file",
os.path.join(os.path.dirname(__file__), "zero_microseconds.py"),
)
d = data[0]
assert d["command"] == "python"
if __name__ == "__main__":
mozunit.main()

Просмотреть файл

@ -1,50 +0,0 @@
#!/usr/bin/env python
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from __future__ import absolute_import, print_function, unicode_literals
"""
This script converts the build system telemetry schema from voluptuous format to json-schema.
You should run it with `mach python`.
"""
import argparse
from mozbuild.base import MozbuildObject
def ensure_luscious(virtualenv_manager):
virtualenv_manager.ensure()
virtualenv_manager.activate()
try:
import luscious # noqa: F401
except (ImportError, AttributeError):
# Ted's fork of luscious, on the `fixes` branch.
virtualenv_manager.install_pip_package(
"git+git://github.com/luser/luscious.git@cfc9b7a402e750d008c0255cd23ecbb3c401c053"
)
def main():
config = MozbuildObject.from_environment()
ensure_luscious(config.virtualenv_manager)
import mozbuild.telemetry
import luscious
import json
parser = argparse.ArgumentParser(
description="Output build system telemetry schema in json-schema format"
)
parser.add_argument("output", help="JSON output destination")
args = parser.parse_args()
schema = luscious.get_jsonschema(mozbuild.telemetry.schema)
with open(args.output, "wb") as f:
json.dump(schema, f, indent=2, separators=(",", ": "), sort_keys=True)
if __name__ == "__main__":
main()

Просмотреть файл

@ -5,147 +5,17 @@
from __future__ import division, absolute_import, print_function, unicode_literals
"""
This file contains a voluptuous schema definition for build system telemetry, and functions
to fill an instance of that schema for a single mach invocation.
This file contains functions used for telemetry.
"""
import distro
import json
import os
import math
import platform
import pprint
import sys
from datetime import datetime
from six import string_types, PY3
from voluptuous import (
Any,
Optional,
MultipleInvalid,
Required,
Schema,
)
from voluptuous.validators import Datetime
import mozpack.path as mozpath
from .base import BuildEnvironmentNotFoundException
from .configure.constants import CompilerType
schema = Schema(
{
Required("client_id", description="A UUID to uniquely identify a client"): Any(
*string_types
),
Required("time", description="Time at which this event happened"): Datetime(),
Required("command", description="The mach command that was invoked"): Any(
*string_types
),
Required(
"argv",
description=(
"Full mach commandline. "
+ "If the commandline contains "
+ "absolute paths they will be sanitized."
),
): [Any(*string_types)],
Required("success", description="true if the command succeeded"): bool,
Optional(
"exception",
description=(
"If a Python exception was encountered during the execution "
+ "of the command, this value contains the result of calling `repr` "
+ "on the exception object."
),
): Any(*string_types),
Optional(
"file_types_changed",
description=(
"This array contains a list of objects with {ext, count} properties giving the "
+ "count of files changed since the last invocation grouped by file type"
),
): [
{
Required("ext", description="File extension"): Any(*string_types),
Required(
"count", description="Count of changed files with this extension"
): int,
}
],
Required("duration_ms", description="Command duration in milliseconds"): int,
Required("build_opts", description="Selected build options"): {
Optional("compiler", description="The compiler type in use (CC_TYPE)"): Any(
*CompilerType.POSSIBLE_VALUES
),
Optional("artifact", description="true if --enable-artifact-builds"): bool,
Optional(
"debug", description="true if build is debug (--enable-debug)"
): bool,
Optional(
"opt", description="true if build is optimized (--enable-optimize)"
): bool,
Optional(
"ccache", description="true if ccache is in use (--with-ccache)"
): bool,
Optional("sccache", description="true if ccache in use is sccache"): bool,
Optional("icecream", description="true if icecream in use"): bool,
},
Optional("build_attrs", description="Attributes characterizing a build"): {
Optional(
"cpu_percent", description="cpu utilization observed during a build"
): int,
Optional(
"clobber", description="true if the build was a clobber/full build"
): bool,
},
Required("system"): {
# We don't need perfect granularity here.
Required("os", description="Operating system"): Any(
"windows", "macos", "linux", "other"
),
Optional("cpu_brand", description="CPU brand string from CPUID"): Any(
*string_types
),
Optional(
"logical_cores", description="Number of logical CPU cores present"
): int,
Optional(
"physical_cores", description="Number of physical CPU cores present"
): int,
Optional("memory_gb", description="System memory in GB"): int,
Optional(
"drive_is_ssd",
description="true if the source directory is on a solid-state disk",
): bool,
Optional(
"virtual_machine",
description="true if the OS appears to be running in a virtual machine",
): bool,
},
}
)
def get_client_id(state_dir):
"""
Get a client id, which is a UUID, from a file in the state directory. If the file doesn't
exist, generate a UUID and save it to a file.
"""
path = os.path.join(state_dir, "telemetry_client_id.json")
if os.path.exists(path):
with open(path, "r") as f:
return json.load(f)["client_id"]
import uuid
# uuid4 is random, other uuid types may include identifiers from the local system.
client_id = str(uuid.uuid4())
if PY3:
file_mode = "w"
else:
file_mode = "wb"
with open(path, file_mode) as f:
json.dump({"client_id": client_id}, f)
return client_id
def cpu_brand_linux():
@ -314,21 +184,23 @@ def get_build_attrs(attrs):
return res
def filter_args(command, argv, instance):
def filter_args(command, argv, topsrcdir, topobjdir, cwd=None):
"""
Given the full list of command-line arguments, remove anything up to and including `command`,
and attempt to filter absolute pathnames out of any arguments after that.
"""
if cwd is None:
cwd = os.getcwd()
# Each key is a pathname and the values are replacement sigils
paths = {
instance.topsrcdir: "$topsrcdir/",
instance.topobjdir: "$topobjdir/",
topsrcdir: "$topsrcdir/",
topobjdir: "$topobjdir/",
mozpath.normpath(os.path.expanduser("~")): "$HOME/",
# This might override one of the existing entries, that's OK.
# We don't use a sigil here because we treat all arguments as potentially relative
# paths, so we'd like to get them back as they were specified.
mozpath.normpath(os.getcwd()): "",
mozpath.normpath(cwd): "",
}
args = list(argv)
@ -348,78 +220,6 @@ def filter_args(command, argv, instance):
return [filter_path(arg) for arg in args]
def gather_telemetry(
command, success, start_time, end_time, mach_context, instance, command_attrs
):
"""
Gather telemetry about the build and the user's system and pass it to the telemetry
handler to be stored for later submission.
Any absolute paths on the command line will be made relative to a relevant base path
or replaced with a placeholder to avoid including paths from developer's machines.
"""
try:
substs = instance.substs
except BuildEnvironmentNotFoundException:
substs = {}
data = {
"client_id": get_client_id(mach_context.state_dir),
# Get an rfc3339 datetime string.
"time": datetime.utcfromtimestamp(start_time).strftime("%Y-%m-%dT%H:%M:%S.%fZ"),
"command": command,
"argv": filter_args(command, sys.argv, instance),
"success": success,
# TODO: use a monotonic clock: https://bugzilla.mozilla.org/show_bug.cgi?id=1481624
"duration_ms": int((end_time - start_time) * 1000),
"build_opts": get_build_opts(substs),
"build_attrs": get_build_attrs(command_attrs),
"system": get_system_info(),
# TODO: exception: https://bugzilla.mozilla.org/show_bug.cgi?id=1481617
# TODO: file_types_changed: https://bugzilla.mozilla.org/show_bug.cgi?id=1481774
}
try:
# Validate against the schema.
schema(data)
return data
except MultipleInvalid as exc:
msg = ["Build telemetry is invalid:"]
for error in exc.errors:
msg.append(str(error))
print("\n".join(msg) + "\n" + pprint.pformat(data))
return None
def verify_statedir(statedir):
"""
Verifies the statedir is structured correctly. Returns the outgoing,
submitted and log paths.
Requires presence of the following directories; will raise if absent:
- statedir/telemetry
- statedir/telemetry/outgoing
Creates the following directories and files if absent (first submission):
- statedir/telemetry/submitted
"""
telemetry_dir = os.path.join(statedir, "telemetry")
outgoing = os.path.join(telemetry_dir, "outgoing")
submitted = os.path.join(telemetry_dir, "submitted")
telemetry_log = os.path.join(telemetry_dir, "telemetry.log")
if not os.path.isdir(telemetry_dir):
raise Exception("{} does not exist".format(telemetry_dir))
if not os.path.isdir(outgoing):
raise Exception("{} does not exist".format(outgoing))
if not os.path.isdir(submitted):
os.mkdir(submitted)
return outgoing, submitted, telemetry_log
def get_distro_and_version():
if sys.platform.startswith("linux"):
dist, version, _ = distro.linux_distribution(full_distribution_name=False)

Просмотреть файл

@ -56,5 +56,7 @@ skip-if = python == 2
[test_mozinfo.py]
[test_preprocessor.py]
[test_pythonutil.py]
[test_telemetry.py]
skip-if = python == 2 && os == "mac"
[test_util.py]
[test_util_fileavoidwrite.py]

Просмотреть файл

@ -0,0 +1,121 @@
# Any copyright is dedicated to the Public Domain.
# http://creativecommons.org/publicdomain/zero/1.0/
from __future__ import absolute_import, print_function
import os
import platform
import buildconfig
import mozunit
import pytest
from mozbuild.telemetry import filter_args
from six import PY3
TELEMETRY_LOAD_ERROR = """
Error loading telemetry. mach output:
=========================================================
%s
=========================================================
"""
@pytest.mark.xfail(
platform.system() == "Windows" and PY3,
reason="Windows and Python3 mozpath filtering issues",
)
def test_path_filtering(tmpdir):
srcdir_path = os.path.join(buildconfig.topsrcdir, "a")
srcdir_path_2 = os.path.join(buildconfig.topsrcdir, "a/b/c")
objdir_path = os.path.join(buildconfig.topobjdir, "x")
objdir_path_2 = os.path.join(buildconfig.topobjdir, "x/y/z")
home_path = os.path.join(os.path.expanduser("~"), "something_in_home")
other_path = str(tmpdir.join("other"))
args = filter_args(
"pass",
[
"python",
"-c",
"pass",
srcdir_path,
srcdir_path_2,
objdir_path,
objdir_path_2,
home_path,
other_path,
],
buildconfig.topsrcdir,
buildconfig.topobjdir,
cwd=buildconfig.topsrcdir,
)
expected = [
"a",
"a/b/c",
"$topobjdir/x",
"$topobjdir/x/y/z",
"$HOME/something_in_home",
"<path omitted>",
]
assert args == expected
@pytest.mark.xfail(
platform.system() == "Windows" and PY3,
reason="Windows and Python3 mozpath filtering issues",
)
def test_path_filtering_in_objdir(tmpdir):
srcdir_path = os.path.join(buildconfig.topsrcdir, "a")
srcdir_path_2 = os.path.join(buildconfig.topsrcdir, "a/b/c")
objdir_path = os.path.join(buildconfig.topobjdir, "x")
objdir_path_2 = os.path.join(buildconfig.topobjdir, "x/y/z")
other_path = str(tmpdir.join("other"))
args = filter_args(
"pass",
[
"python",
"-c",
"pass",
srcdir_path,
srcdir_path_2,
objdir_path,
objdir_path_2,
other_path,
],
buildconfig.topsrcdir,
buildconfig.topobjdir,
cwd=buildconfig.topobjdir,
)
expected = [
"$topsrcdir/a",
"$topsrcdir/a/b/c",
"x",
"x/y/z",
"<path omitted>",
]
assert args == expected
def test_path_filtering_other_cwd(tmpdir):
srcdir_path = os.path.join(buildconfig.topsrcdir, "a")
srcdir_path_2 = os.path.join(buildconfig.topsrcdir, "a/b/c")
other_path = str(tmpdir.join("other"))
args = filter_args(
"pass",
["python", "-c", "pass", srcdir_path, srcdir_path_2, other_path],
buildconfig.topsrcdir,
buildconfig.topobjdir,
cwd=str(tmpdir),
)
expected = [
"$topsrcdir/a",
"$topsrcdir/a/b/c",
# cwd-relative paths should be relativized
"other",
]
assert args == expected
if __name__ == "__main__":
mozunit.main()