fune/toolkit/components/telemetry/shared_telemetry_utils.py
Jan-Erik Rediger 55a00a13ea Bug 1452552 - Add "products" property to scalars. r=Dexter
The "products" property is optional and defaults to `firefox, fennec`.
Recording of scalars is disabled if the probe doesn't have the products
property for the current running product.

MozReview-Commit-ID: 4HVBrBw1lls

--HG--
extra : rebase_source : 5820a8bc8a9397db4ed728354cbb10984bb04946
2018-04-23 17:07:47 +02:00

203 lines
6.4 KiB
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/.
# This file contains utility functions shared by the scalars and the histogram generation
# scripts.
from __future__ import print_function
import re
import yaml
import sys
# This is a list of flags that determine which process a measurement is allowed
# to record from.
KNOWN_PROCESS_FLAGS = {
'all': 'All',
'all_children': 'AllChildren',
'main': 'Main',
'content': 'Content',
'gpu': 'Gpu',
# Historical Values
'all_childs': 'AllChildren', # Supporting files from before bug 1363725
}
SUPPORTED_PRODUCTS = {
'firefox': 'Firefox',
'fennec': 'Fennec',
'geckoview': 'Geckoview',
'all': 'All',
}
PROCESS_ENUM_PREFIX = "mozilla::Telemetry::Common::RecordedProcessType::"
PRODUCT_ENUM_PREFIX = "mozilla::Telemetry::Common::SupportedProduct::"
class ParserError(Exception):
"""Thrown by different probe parsers. Errors are partitioned into
'immediately fatal' and 'eventually fatal' so that the parser can print
multiple error messages at a time. See bug 1401612 ."""
eventual_errors = []
def __init__(self, *args):
Exception.__init__(self, *args)
def handle_later(self):
ParserError.eventual_errors.append(self)
def handle_now(self):
ParserError.print_eventuals()
print(self.message, file=sys.stderr)
sys.exit(1)
@classmethod
def print_eventuals(cls):
while cls.eventual_errors:
print(cls.eventual_errors.pop(0).message, file=sys.stderr)
@classmethod
def exit_func(cls):
if cls.eventual_errors:
cls("Some errors occurred").handle_now()
def is_valid_process_name(name):
return (name in KNOWN_PROCESS_FLAGS)
def process_name_to_enum(name):
return PROCESS_ENUM_PREFIX + KNOWN_PROCESS_FLAGS.get(name)
def is_valid_product(name):
return (name in SUPPORTED_PRODUCTS)
def product_name_to_enum(product):
return PRODUCT_ENUM_PREFIX + SUPPORTED_PRODUCTS.get(product)
class StringTable:
"""Manages a string table and allows C style serialization to a file."""
def __init__(self):
self.current_index = 0
self.table = {}
def c_strlen(self, string):
"""The length of a string including the null terminating character.
:param string: the input string.
"""
return len(string) + 1
def stringIndex(self, string):
"""Returns the index in the table of the provided string. Adds the string to
the table if it's not there.
:param string: the input string.
"""
if string in self.table:
return self.table[string]
else:
result = self.current_index
self.table[string] = result
self.current_index += self.c_strlen(string)
return result
def stringIndexes(self, strings):
""" Returns a list of indexes for the provided list of strings.
Adds the strings to the table if they are not in it yet.
:param strings: list of strings to put into the table.
"""
return [self.stringIndex(s) for s in strings]
def writeDefinition(self, f, name):
"""Writes the string table to a file as a C const char array.
This writes out the string table as one single C char array for memory
size reasons, separating the individual strings with '\0' characters.
This way we can index directly into the string array and avoid the additional
storage costs for the pointers to them (and potential extra relocations for those).
:param f: the output stream.
:param name: the name of the output array.
"""
entries = self.table.items()
entries.sort(key=lambda x: x[1])
# Avoid null-in-string warnings with GCC and potentially
# overlong string constants; write everything out the long way.
def explodeToCharArray(string):
def toCChar(s):
if s == "'":
return "'\\''"
else:
return "'%s'" % s
return ", ".join(map(toCChar, string))
f.write("const char %s[] = {\n" % name)
for (string, offset) in entries:
if "*/" in string:
raise ValueError("String in string table contains unexpected sequence '*/': %s" %
string)
e = explodeToCharArray(string)
if e:
f.write(" /* %5d - \"%s\" */ %s, '\\0',\n"
% (offset, string, explodeToCharArray(string)))
else:
f.write(" /* %5d - \"%s\" */ '\\0',\n" % (offset, string))
f.write("};\n\n")
def static_assert(output, expression, message):
"""Writes a C++ compile-time assertion expression to a file.
:param output: the output stream.
:param expression: the expression to check.
:param message: the string literal that will appear if the expression evaluates to
false.
"""
print("static_assert(%s, \"%s\");" % (expression, message), file=output)
def validate_expiration_version(expiration):
""" Makes sure the expiration version has the expected format.
Allowed examples: "10", "20", "60", "never"
Disallowed examples: "Never", "asd", "4000000", "60a1", "30.5a1"
:param expiration: the expiration version string.
:return: True if the expiration validates correctly, False otherwise.
"""
if expiration != 'never' and not re.match(r'^\d{1,3}$', expiration):
return False
return True
def add_expiration_postfix(expiration):
""" Formats the expiration version and adds a version postfix if needed.
:param expiration: the expiration version string.
:return: the modified expiration string.
"""
if re.match(r'^[1-9][0-9]*$', expiration):
return expiration + ".0a1"
if re.match(r'^[1-9][0-9]*\.0$', expiration):
return expiration + "a1"
return expiration
def load_yaml_file(filename):
""" Load a YAML file from disk, throw a ParserError on failure."""
try:
with open(filename, 'r') as f:
return yaml.safe_load(f)
except IOError, e:
raise ParserError('Error opening ' + filename + ': ' + e.message)
except ValueError, e:
raise ParserError('Error parsing processes in {}: {}'
.format(filename, e.message))