1# Copyright 2018, The Android Open Source Project 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15"""Utility functions for metrics.""" 16 17import os 18import platform 19import sys 20import time 21import traceback 22 23from atest.metrics import metrics 24from atest.metrics import metrics_base 25 26CONTENT_LICENSES_URL = 'https://source.android.com/setup/start/licenses' 27CONTRIBUTOR_AGREEMENT_URL = { 28 'INTERNAL': 'https://cla.developers.google.com/', 29 'EXTERNAL': 'https://opensource.google.com/docs/cla/', 30} 31PRIVACY_POLICY_URL = 'https://policies.google.com/privacy' 32TERMS_SERVICE_URL = 'https://policies.google.com/terms' 33 34 35def static_var(varname, value): 36 """Decorator to cache static variable.""" 37 38 def fun_var_decorate(func): 39 """Set the static variable in a function.""" 40 setattr(func, varname, value) 41 return func 42 43 return fun_var_decorate 44 45 46@static_var('start_time', []) 47def get_start_time(): 48 """Get start time. 49 50 Return: 51 start_time: Start time in seconds. Return cached start_time if exists, 52 time.time() otherwise. 53 """ 54 if not get_start_time.start_time: 55 get_start_time.start_time = time.time() 56 return get_start_time.start_time 57 58 59def convert_duration(diff_time_sec): 60 """Compute duration from time difference. 61 62 A Duration represents a signed, fixed-length span of time represented 63 as a count of seconds and fractions of seconds at nanosecond 64 resolution. 65 66 Args: 67 diff_time_sec: The time in seconds as a floating point number. 68 69 Returns: 70 A dict of Duration. 71 """ 72 seconds = int(diff_time_sec) 73 nanos = int((diff_time_sec - seconds) * 10**9) 74 return {'seconds': seconds, 'nanos': nanos} 75 76 77# pylint: disable=broad-except 78def handle_exc_and_send_exit_event(exit_code): 79 """handle exceptions and send exit event. 80 81 Args: 82 exit_code: An integer of exit code. 83 """ 84 stacktrace = logs = '' 85 try: 86 exc_type, exc_msg, _ = sys.exc_info() 87 stacktrace = traceback.format_exc() 88 if exc_type: 89 logs = '{etype}: {value}'.format(etype=exc_type.__name__, value=exc_msg) 90 except Exception: 91 pass 92 send_exit_event(exit_code, stacktrace=stacktrace, logs=logs) 93 94 95def send_exit_event(exit_code, stacktrace='', logs=''): 96 """Log exit event and flush all events to clearcut. 97 98 Args: 99 exit_code: An integer of exit code. 100 stacktrace: A string of stacktrace. 101 logs: A string of logs. 102 """ 103 clearcut = metrics.AtestExitEvent( 104 duration=convert_duration(time.time() - get_start_time()), 105 exit_code=exit_code, 106 stacktrace=stacktrace, 107 logs=str(logs), 108 ) 109 # pylint: disable=no-member 110 if clearcut: 111 clearcut.flush_events() 112 113 114def send_start_event( 115 tool_name, 116 command_line='', 117 test_references='', 118 cwd=None, 119 operating_system=None, 120): 121 """Log start event of clearcut. 122 123 Args: 124 tool_name: A string of the asuite product name. 125 command_line: A string of the user input command. 126 test_references: A string of the input tests. 127 cwd: A string of current path. 128 operating_system: A string of user's operating system. 129 """ 130 if not cwd: 131 cwd = os.getcwd() 132 if not operating_system: 133 operating_system = platform.platform() 134 # Without tool_name information, asuite's clearcut client will not send 135 # event to server. 136 metrics_base.MetricsBase.tool_name = tool_name 137 get_start_time() 138 metrics.AtestStartEvent( 139 command_line=command_line, 140 test_references=test_references, 141 cwd=cwd, 142 os=operating_system, 143 ) 144 145 146def print_data_collection_notice(colorful=True): 147 """Print the data collection notice.""" 148 # Do not print notice for external users as we are not collecting any external 149 # data. 150 if metrics_base.get_user_type() == metrics_base.EXTERNAL_USER: 151 return 152 153 red = '31m' 154 green = '32m' 155 start = '\033[1;' 156 end = '\033[0m' 157 delimiter = '=' * 18 158 notice = ( 159 'We collect usage statistics (including usernames) in accordance with our ' 160 'Content Licenses (%s), Contributor License Agreement (%s), Privacy ' 161 'Policy (%s) and Terms of Service (%s).' 162 ) % ( 163 CONTENT_LICENSES_URL, 164 CONTRIBUTOR_AGREEMENT_URL['INTERNAL'], 165 PRIVACY_POLICY_URL, 166 TERMS_SERVICE_URL, 167 ) 168 if colorful: 169 print(f'\n{delimiter}\n{start}{red}Notice:{end}') 170 print(f'{start}{green} {notice}{end}\n{delimiter}\n') 171 else: 172 print(f'\n{delimiter}\nNotice:') 173 print(f' {notice}\n{delimiter}\n') 174