xref: /aosp_15_r20/tools/asuite/atest/metrics/metrics_utils.py (revision c2e18aaa1096c836b086f94603d04f4eb9cf37f5)
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 sys
18import time
19import traceback
20
21from atest.metrics import metrics
22from atest.metrics import metrics_base
23
24
25CONTENT_LICENSES_URL = 'https://source.android.com/setup/start/licenses'
26CONTRIBUTOR_AGREEMENT_URL = {
27    'INTERNAL': 'https://cla.developers.google.com/',
28    'EXTERNAL': 'https://opensource.google.com/docs/cla/',
29}
30PRIVACY_POLICY_URL = 'https://policies.google.com/privacy'
31TERMS_SERVICE_URL = 'https://policies.google.com/terms'
32
33
34def static_var(varname, value):
35  """Decorator to cache static variable."""
36
37  def fun_var_decorate(func):
38    """Set the static variable in a function."""
39    setattr(func, varname, value)
40    return func
41
42  return fun_var_decorate
43
44
45@static_var('start_time', [])
46def get_start_time():
47  """Get start time.
48
49  Return:
50      start_time: Start time in seconds. Return cached start_time if exists,
51      time.time() otherwise.
52  """
53  if not get_start_time.start_time:
54    get_start_time.start_time = time.time()
55  return get_start_time.start_time
56
57
58def convert_duration(diff_time_sec):
59  """Compute duration from time difference.
60
61  A Duration represents a signed, fixed-length span of time represented
62  as a count of seconds and fractions of seconds at nanosecond
63  resolution.
64
65  Args:
66      diff_time_sec: The time in seconds as a floating point number.
67
68  Returns:
69      A dict of Duration.
70  """
71  seconds = int(diff_time_sec)
72  nanos = int((diff_time_sec - seconds) * 10**9)
73  return {'seconds': seconds, 'nanos': nanos}
74
75
76# pylint: disable=broad-except
77def handle_exc_and_send_exit_event(exit_code):
78  """handle exceptions and send exit event.
79
80  Args:
81      exit_code: An integer of exit code.
82  """
83  stacktrace = logs = ''
84  try:
85    exc_type, exc_msg, _ = sys.exc_info()
86    stacktrace = traceback.format_exc()
87    if exc_type:
88      logs = '{etype}: {value}'.format(etype=exc_type.__name__, value=exc_msg)
89  except Exception:
90    pass
91  send_exit_event(exit_code, stacktrace=stacktrace, logs=logs)
92
93
94def send_exit_event(exit_code, stacktrace='', logs=''):
95  """Log exit event and flush all events to clearcut.
96
97  Args:
98      exit_code: An integer of exit code.
99      stacktrace: A string of stacktrace.
100      logs: A string of logs.
101  """
102  clearcut = metrics.AtestExitEvent(
103      duration=convert_duration(time.time() - get_start_time()),
104      exit_code=exit_code,
105      stacktrace=stacktrace,
106      logs=str(logs),
107  )
108  # pylint: disable=no-member
109  if clearcut:
110    clearcut.flush_events()
111
112
113def send_start_event(
114    command_line,
115    test_references,
116    cwd,
117    operating_system,
118    source_root,
119    hostname,
120):
121  """Log start event of clearcut.
122
123  Args:
124      command_line: A string of the user input command.
125      test_references: A string of the input tests.
126      cwd: A string of current path.
127      operating_system: A string of user's operating system.
128      source_root: A string of the Android build source.
129      hostname: A string of the host workstation name.
130  """
131  get_start_time()
132  metrics.AtestStartEvent(
133      command_line=command_line,
134      test_references=test_references,
135      cwd=cwd,
136      os=operating_system,
137      source_root=source_root,
138      hostname=hostname,
139  )
140
141
142def print_data_collection_notice(colorful=True):
143  """Print the data collection notice."""
144  # Do not print notice for external users as we are not collecting any external
145  # data.
146  if metrics_base.get_user_type() == metrics_base.EXTERNAL_USER:
147    return
148
149  red = '31m'
150  green = '32m'
151  start = '\033[1;'
152  end = '\033[0m'
153  delimiter = '=' * 18
154  notice = (
155      'We collect usage statistics (including usernames) in accordance with our '
156      'Content Licenses (%s), Contributor License Agreement (%s), Privacy '
157      'Policy (%s) and Terms of Service (%s).'
158  ) % (
159      CONTENT_LICENSES_URL,
160      CONTRIBUTOR_AGREEMENT_URL['INTERNAL'],
161      PRIVACY_POLICY_URL,
162      TERMS_SERVICE_URL,
163  )
164  if colorful:
165    print(f'\n{delimiter}\n{start}{red}Notice:{end}')
166    print(f'{start}{green} {notice}{end}\n{delimiter}\n')
167  else:
168    print(f'\n{delimiter}\nNotice:')
169    print(f' {notice}\n{delimiter}\n')
170