blob: 158290be55604a4b21ce3ec8113c57f7402bd11c [file] [log] [blame]
#!/usr/bin/env python3
# Copyright 2020 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""This script is used to run the error_calculator binary.
./error_calculator.py 1 657579885 14 1
This script assumes it is being run from a third_party embedding in a fuchsia
checkout. If you are running from the cobalt standalone repository, run
'cobaltb.py calculate_error' instead. If the build configuration doesn't match
the assumed fuchsia or cobalt checkout, use the --bin_dir flag to specify the
path to the error_calculator and config_parser binaries.
A new version of the registry is generated every time.
"""
import subprocess
import os
import argparse
import sys
THIS_DIR = os.path.abspath(os.path.dirname(__file__))
COBALT_ROOT_DIR = os.path.abspath(os.path.join(THIS_DIR, '..'))
PRIVACY_PARAMS = os.path.join(COBALT_ROOT_DIR, 'src', 'algorithms', 'privacy',
'data', 'privacy_encoding_params')
REGISTRY_PROTO_DEFAULT = os.path.join(COBALT_ROOT_DIR, 'out', 'registry.pb')
POPULATION_DEFAULT = 100000
def estimate_from_args(error_binary, args):
"""Unpacks and validates command line arguments before calling error calculator.
Args:
error_binary: The filepath of the error calculator binary.
args: Python arguments defined below.
"""
_validate_args(args)
estimate(error_binary, args.registry_proto, args.customer_id, args.project_id,
args.metric_id, args.report_id, args.population, args.epsilon,
args.min_denominator, args.min_value, args.max_value, args.max_count,
args.simple)
def estimate(error_binary,
registry_proto,
customer_id,
project_id,
metric_id,
report_id,
population,
epsilon=None,
min_denominator=None,
min_value=None,
max_value=None,
max_count=None,
simple=False):
"""Calls the error_calculator binary with the specified arguments.
Args:
error_binary: The string filepath to the error calculator binary.
registry_proto: The string filepath to the registry formatted as a
serialized proto.
customer_id: The integer customer id.
project_id: The integer project id.
metric_id: The integer metric id.
report_id: The integer report id.
population: Integer value estimating the number of reporting devices.
epsilon: Optional; Epsilon value for which to estimate error.
min_denominator: Optional; estimated minimum number of unique contributing
devices per day.
min_value: Optional; override the report's min_value.
max_value: Optional; override the report's max_value.
max_count: Optional; override the report's max_count.
simple: Outputs a single estimate.
"""
error_args = [
'-registry_proto', registry_proto, '--privacy_params', PRIVACY_PARAMS,
'--population',
str(population)
]
if epsilon:
error_args = error_args + ['--epsilon', str(epsilon)]
if min_denominator:
error_args = error_args + ['--min_denominator', str(min_denominator)]
if simple:
error_args = error_args + ['--simple']
if min_value:
error_args = error_args + ['--min_value', str(min_value)]
if max_value:
error_args = error_args + ['--max_value', str(max_value)]
if max_count:
error_args = error_args + ['--max_count', str(max_count)]
error_args = error_args + [
str(customer_id),
str(project_id),
str(metric_id),
str(report_id)
]
subprocess.check_call([error_binary] + error_args)
# TODO(jaredweinstein): Use the build system to generate the registry only when
# necessary.
def generate_registry(registry_proto, config_dir, config_parser):
"""Generates a binary encoding of the cobalt registry.
Args:
registry_proto: The filepath of the error calculator binary. This is the
output of the config_parser and the input for the error_calculator
config_dir: Location of the cobalt config.
config_parser: Location of the binary used to generate the registry_proto.
"""
if not config_parser:
sys.exit('Run \'config_parser --out_filename %s\' and try again.' %
registry_proto)
subprocess.check_call([
config_parser, '--output_file', registry_proto, '--config_dir',
config_dir, '--privacy_encoding_params_file', PRIVACY_PARAMS
])
print('Wrote binary encoding of registry to %s.\n' % registry_proto)
def add_parse_args(parser):
"""Adds the standard arguments required for the error_calculator.
This is used to set arguments for this script and the cobaltb.py script.
Args:
parser: An ArgumentParser to be augmented with error calculator arguments.
"""
parser.add_argument(
'--registry_proto',
help='Set a specific filepath for the binary encoding of the Cobalt Registry. Default: %s'
% REGISTRY_PROTO_DEFAULT,
default=REGISTRY_PROTO_DEFAULT)
parser.add_argument(
'--population',
help='Expected number of devices contributing to the report. Default: %s'
% POPULATION_DEFAULT,
default=POPULATION_DEFAULT)
parser.add_argument(
'--bin_dir',
help='Directory containing the error_calculator and config_parser binaries. '
'If unset, the script attempts to find binaries based on the default '
'build configuration for Fuchsia and Cobalt.',
default=None)
parser.add_argument(
'--epsilon',
help='If set, estimates the error using the specified epsilon value.')
parser.add_argument(
'--min_denominator',
help='Estimated minimum number of unique contributing devices per day.')
parser.add_argument(
'--min_value', help='Optionally overrides the report\'s MinValue field.')
parser.add_argument(
'--max_value', help='Optionally overrides the report\'s MaxValue field.')
parser.add_argument(
'--max_count', help='Optionally overrides the report\'s MaxCount field.')
parser.add_argument(
'--simple',
default=False,
action='store_true',
help='Output a single error estimate.')
parser.add_argument(
'customer_id', help='a report\'s parent customer id', type=int)
parser.add_argument(
'project_id', help='a report\'s parent project id.', type=int)
parser.add_argument(
'metric_id', help='a report\'s parent metric id.', type=int)
parser.add_argument(
'report_id', help='a report\'s parent report id.', type=int)
def _validate_args(args):
if args.registry_proto == None:
sys.exit('--registry_proto is required')
if args.population == None:
sys.exit('--population flag is required')
if not os.path.exists(args.registry_proto):
sys.exit(
'No serialized proto found. Try running \'config_parser --out_filename %s\' and try again.'
% args.registry_proto)
if __name__ == '__main__':
# Assumes the user is running from a Fuchsia checkout.
# If you're running this script directly from a cobalt checkout, use
# `cobaltb.py calculate_error` instead.
parser = argparse.ArgumentParser()
add_parse_args(parser)
args = parser.parse_args()
if args.bin_dir:
bin_dir = args.bin_dir
else:
out = subprocess.check_output(['fx', 'get-build-dir'])
out_dir = out.decode('utf-8').rstrip()
bin_dir = os.path.join(out_dir, 'host_x64')
error_binary = os.path.join(bin_dir, 'error_calculator')
config_parser = os.path.join(bin_dir, 'config_parser')
config_dir = os.path.join(COBALT_ROOT_DIR, '..', 'cobalt_config')
config_dir = os.path.abspath(config_dir)
if not os.path.exists(error_binary):
sys.exit(
'Error Calculator binary not found: %s.\nRun \'fx build\' and try again.'
% error_binary)
generate_registry(args.registry_proto, config_dir, config_parser)
estimate_from_args(error_binary, args)