зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
f4ebf61baf
Коммит
201a891006
|
@ -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()
|
Загрузка…
Ссылка в новой задаче