xref: /aosp_15_r20/tools/asuite/atest/metrics/metrics_base.py (revision c2e18aaa1096c836b086f94603d04f4eb9cf37f5)
1*c2e18aaaSAndroid Build Coastguard Worker# Copyright 2018, The Android Open Source Project
2*c2e18aaaSAndroid Build Coastguard Worker#
3*c2e18aaaSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License");
4*c2e18aaaSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
5*c2e18aaaSAndroid Build Coastguard Worker# You may obtain a copy of the License at
6*c2e18aaaSAndroid Build Coastguard Worker#
7*c2e18aaaSAndroid Build Coastguard Worker#     http://www.apache.org/licenses/LICENSE-2.0
8*c2e18aaaSAndroid Build Coastguard Worker#
9*c2e18aaaSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
10*c2e18aaaSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS,
11*c2e18aaaSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*c2e18aaaSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and
13*c2e18aaaSAndroid Build Coastguard Worker# limitations under the License.
14*c2e18aaaSAndroid Build Coastguard Worker
15*c2e18aaaSAndroid Build Coastguard Worker"""Metrics base class."""
16*c2e18aaaSAndroid Build Coastguard Worker
17*c2e18aaaSAndroid Build Coastguard Workerfrom __future__ import print_function
18*c2e18aaaSAndroid Build Coastguard Worker
19*c2e18aaaSAndroid Build Coastguard Workerimport getpass
20*c2e18aaaSAndroid Build Coastguard Workerimport logging
21*c2e18aaaSAndroid Build Coastguard Workerimport random
22*c2e18aaaSAndroid Build Coastguard Workerimport socket
23*c2e18aaaSAndroid Build Coastguard Workerimport subprocess
24*c2e18aaaSAndroid Build Coastguard Workerimport time
25*c2e18aaaSAndroid Build Coastguard Workerimport uuid
26*c2e18aaaSAndroid Build Coastguard Worker
27*c2e18aaaSAndroid Build Coastguard Workerfrom atest import constants
28*c2e18aaaSAndroid Build Coastguard Workerfrom atest.metrics import clearcut_client
29*c2e18aaaSAndroid Build Coastguard Workerfrom atest.proto import clientanalytics_pb2
30*c2e18aaaSAndroid Build Coastguard Workerfrom atest.proto import external_user_log_pb2
31*c2e18aaaSAndroid Build Coastguard Workerfrom atest.proto import internal_user_log_pb2
32*c2e18aaaSAndroid Build Coastguard Worker
33*c2e18aaaSAndroid Build Coastguard WorkerINTERNAL_USER = 0
34*c2e18aaaSAndroid Build Coastguard WorkerEXTERNAL_USER = 1
35*c2e18aaaSAndroid Build Coastguard Worker
36*c2e18aaaSAndroid Build Coastguard WorkerATEST_EVENTS = {
37*c2e18aaaSAndroid Build Coastguard Worker    INTERNAL_USER: internal_user_log_pb2.AtestLogEventInternal,
38*c2e18aaaSAndroid Build Coastguard Worker    EXTERNAL_USER: external_user_log_pb2.AtestLogEventExternal,
39*c2e18aaaSAndroid Build Coastguard Worker}
40*c2e18aaaSAndroid Build Coastguard Worker# log source
41*c2e18aaaSAndroid Build Coastguard WorkerATEST_LOG_SOURCE = {INTERNAL_USER: 971, EXTERNAL_USER: 934}
42*c2e18aaaSAndroid Build Coastguard Worker
43*c2e18aaaSAndroid Build Coastguard Worker
44*c2e18aaaSAndroid Build Coastguard Workerdef get_user_email():
45*c2e18aaaSAndroid Build Coastguard Worker  """Get user mail in git config.
46*c2e18aaaSAndroid Build Coastguard Worker
47*c2e18aaaSAndroid Build Coastguard Worker  Returns:
48*c2e18aaaSAndroid Build Coastguard Worker      user's email.
49*c2e18aaaSAndroid Build Coastguard Worker  """
50*c2e18aaaSAndroid Build Coastguard Worker  try:
51*c2e18aaaSAndroid Build Coastguard Worker    output = subprocess.check_output(
52*c2e18aaaSAndroid Build Coastguard Worker        ['git', 'config', '--get', 'user.email'], universal_newlines=True
53*c2e18aaaSAndroid Build Coastguard Worker    )
54*c2e18aaaSAndroid Build Coastguard Worker    return output.strip() if output else ''
55*c2e18aaaSAndroid Build Coastguard Worker  except OSError:
56*c2e18aaaSAndroid Build Coastguard Worker    # OSError can be raised when running atest_unittests on a host
57*c2e18aaaSAndroid Build Coastguard Worker    # without git being set up.
58*c2e18aaaSAndroid Build Coastguard Worker    logging.debug(
59*c2e18aaaSAndroid Build Coastguard Worker        'Unable to determine if this is an external run, git is not found.'
60*c2e18aaaSAndroid Build Coastguard Worker    )
61*c2e18aaaSAndroid Build Coastguard Worker  except subprocess.CalledProcessError:
62*c2e18aaaSAndroid Build Coastguard Worker    logging.debug(
63*c2e18aaaSAndroid Build Coastguard Worker        'Unable to determine if this is an external run, email '
64*c2e18aaaSAndroid Build Coastguard Worker        'is not found in git config.'
65*c2e18aaaSAndroid Build Coastguard Worker    )
66*c2e18aaaSAndroid Build Coastguard Worker  return ''
67*c2e18aaaSAndroid Build Coastguard Worker
68*c2e18aaaSAndroid Build Coastguard Worker
69*c2e18aaaSAndroid Build Coastguard Workerdef get_user_type():
70*c2e18aaaSAndroid Build Coastguard Worker  """Get user type.
71*c2e18aaaSAndroid Build Coastguard Worker
72*c2e18aaaSAndroid Build Coastguard Worker  Determine the internal user by passing at least one check:
73*c2e18aaaSAndroid Build Coastguard Worker    - whose git mail domain is from google
74*c2e18aaaSAndroid Build Coastguard Worker    - whose hostname is from google
75*c2e18aaaSAndroid Build Coastguard Worker  Otherwise is external user.
76*c2e18aaaSAndroid Build Coastguard Worker
77*c2e18aaaSAndroid Build Coastguard Worker  Returns:
78*c2e18aaaSAndroid Build Coastguard Worker      INTERNAL_USER if user is internal, EXTERNAL_USER otherwise.
79*c2e18aaaSAndroid Build Coastguard Worker  """
80*c2e18aaaSAndroid Build Coastguard Worker  email = get_user_email()
81*c2e18aaaSAndroid Build Coastguard Worker  if email.endswith(constants.INTERNAL_EMAIL):
82*c2e18aaaSAndroid Build Coastguard Worker    return INTERNAL_USER
83*c2e18aaaSAndroid Build Coastguard Worker
84*c2e18aaaSAndroid Build Coastguard Worker  try:
85*c2e18aaaSAndroid Build Coastguard Worker    hostname = socket.getfqdn()
86*c2e18aaaSAndroid Build Coastguard Worker    if hostname and any((x in hostname) for x in constants.INTERNAL_HOSTNAME):
87*c2e18aaaSAndroid Build Coastguard Worker      return INTERNAL_USER
88*c2e18aaaSAndroid Build Coastguard Worker  except IOError:
89*c2e18aaaSAndroid Build Coastguard Worker    logging.debug(
90*c2e18aaaSAndroid Build Coastguard Worker        'Unable to determine if this is an external run, hostname is not found.'
91*c2e18aaaSAndroid Build Coastguard Worker    )
92*c2e18aaaSAndroid Build Coastguard Worker  return EXTERNAL_USER
93*c2e18aaaSAndroid Build Coastguard Worker
94*c2e18aaaSAndroid Build Coastguard Worker
95*c2e18aaaSAndroid Build Coastguard Workerdef get_user_key():
96*c2e18aaaSAndroid Build Coastguard Worker  """Get user key
97*c2e18aaaSAndroid Build Coastguard Worker
98*c2e18aaaSAndroid Build Coastguard Worker  Returns:
99*c2e18aaaSAndroid Build Coastguard Worker      A UUID.uuid5 keyed on the user's email
100*c2e18aaaSAndroid Build Coastguard Worker  """
101*c2e18aaaSAndroid Build Coastguard Worker  key = uuid.uuid5(uuid.NAMESPACE_DNS, get_user_email())
102*c2e18aaaSAndroid Build Coastguard Worker  return key
103*c2e18aaaSAndroid Build Coastguard Worker
104*c2e18aaaSAndroid Build Coastguard Worker
105*c2e18aaaSAndroid Build Coastguard Workerclass MetricsBase:
106*c2e18aaaSAndroid Build Coastguard Worker  """Class for separating allowed fields and sending metric."""
107*c2e18aaaSAndroid Build Coastguard Worker
108*c2e18aaaSAndroid Build Coastguard Worker  _run_id = str(uuid.uuid4())
109*c2e18aaaSAndroid Build Coastguard Worker  user_type = get_user_type()
110*c2e18aaaSAndroid Build Coastguard Worker  if user_type == INTERNAL_USER:
111*c2e18aaaSAndroid Build Coastguard Worker    _user_key = getpass.getuser()
112*c2e18aaaSAndroid Build Coastguard Worker  else:
113*c2e18aaaSAndroid Build Coastguard Worker    try:
114*c2e18aaaSAndroid Build Coastguard Worker      # pylint: disable=protected-access
115*c2e18aaaSAndroid Build Coastguard Worker      _user_key = str(get_user_key())
116*c2e18aaaSAndroid Build Coastguard Worker      # pylint: disable=broad-except
117*c2e18aaaSAndroid Build Coastguard Worker    except Exception:
118*c2e18aaaSAndroid Build Coastguard Worker      _user_key = str(uuid.UUID(int=0))
119*c2e18aaaSAndroid Build Coastguard Worker
120*c2e18aaaSAndroid Build Coastguard Worker  _log_source = ATEST_LOG_SOURCE[user_type]
121*c2e18aaaSAndroid Build Coastguard Worker  cc = clearcut_client.Clearcut(_log_source)
122*c2e18aaaSAndroid Build Coastguard Worker  tool_name = None
123*c2e18aaaSAndroid Build Coastguard Worker  sub_tool_name = ''
124*c2e18aaaSAndroid Build Coastguard Worker
125*c2e18aaaSAndroid Build Coastguard Worker  def __new__(cls, **kwargs):
126*c2e18aaaSAndroid Build Coastguard Worker    """Send metric event to clearcut.
127*c2e18aaaSAndroid Build Coastguard Worker
128*c2e18aaaSAndroid Build Coastguard Worker    Args:
129*c2e18aaaSAndroid Build Coastguard Worker        cls: this class object.
130*c2e18aaaSAndroid Build Coastguard Worker        **kwargs: A dict of named arguments.
131*c2e18aaaSAndroid Build Coastguard Worker
132*c2e18aaaSAndroid Build Coastguard Worker    Returns:
133*c2e18aaaSAndroid Build Coastguard Worker        A Clearcut instance.
134*c2e18aaaSAndroid Build Coastguard Worker    """
135*c2e18aaaSAndroid Build Coastguard Worker    # pylint: disable=no-member
136*c2e18aaaSAndroid Build Coastguard Worker    if not cls.tool_name:
137*c2e18aaaSAndroid Build Coastguard Worker      logging.debug('There is no tool_name, and metrics stops sending.')
138*c2e18aaaSAndroid Build Coastguard Worker      return None
139*c2e18aaaSAndroid Build Coastguard Worker    allowed = (
140*c2e18aaaSAndroid Build Coastguard Worker        {constants.EXTERNAL}
141*c2e18aaaSAndroid Build Coastguard Worker        if cls.user_type == EXTERNAL_USER
142*c2e18aaaSAndroid Build Coastguard Worker        else {constants.EXTERNAL, constants.INTERNAL}
143*c2e18aaaSAndroid Build Coastguard Worker    )
144*c2e18aaaSAndroid Build Coastguard Worker    fields = [
145*c2e18aaaSAndroid Build Coastguard Worker        k
146*c2e18aaaSAndroid Build Coastguard Worker        for k, v in vars(cls).items()
147*c2e18aaaSAndroid Build Coastguard Worker        if not k.startswith('_') and v in allowed
148*c2e18aaaSAndroid Build Coastguard Worker    ]
149*c2e18aaaSAndroid Build Coastguard Worker    fields_and_values = {}
150*c2e18aaaSAndroid Build Coastguard Worker    for field in fields:
151*c2e18aaaSAndroid Build Coastguard Worker      if field in kwargs:
152*c2e18aaaSAndroid Build Coastguard Worker        fields_and_values[field] = kwargs.pop(field)
153*c2e18aaaSAndroid Build Coastguard Worker    params = {
154*c2e18aaaSAndroid Build Coastguard Worker        'user_key': cls._user_key,
155*c2e18aaaSAndroid Build Coastguard Worker        'run_id': cls._run_id,
156*c2e18aaaSAndroid Build Coastguard Worker        'user_type': cls.user_type,
157*c2e18aaaSAndroid Build Coastguard Worker        'tool_name': cls.tool_name,
158*c2e18aaaSAndroid Build Coastguard Worker        'sub_tool_name': cls.sub_tool_name,
159*c2e18aaaSAndroid Build Coastguard Worker        cls._EVENT_NAME: fields_and_values,
160*c2e18aaaSAndroid Build Coastguard Worker    }
161*c2e18aaaSAndroid Build Coastguard Worker    log_event = cls._build_full_event(ATEST_EVENTS[cls.user_type](**params))
162*c2e18aaaSAndroid Build Coastguard Worker    cls.cc.log(log_event)
163*c2e18aaaSAndroid Build Coastguard Worker    return cls.cc
164*c2e18aaaSAndroid Build Coastguard Worker
165*c2e18aaaSAndroid Build Coastguard Worker  @classmethod
166*c2e18aaaSAndroid Build Coastguard Worker  def get_run_id(cls) -> str:
167*c2e18aaaSAndroid Build Coastguard Worker    """Returns the unique run id set for the current invocation."""
168*c2e18aaaSAndroid Build Coastguard Worker    return cls._run_id
169*c2e18aaaSAndroid Build Coastguard Worker
170*c2e18aaaSAndroid Build Coastguard Worker  @classmethod
171*c2e18aaaSAndroid Build Coastguard Worker  def _build_full_event(cls, atest_event):
172*c2e18aaaSAndroid Build Coastguard Worker    """This is all protobuf building you can ignore.
173*c2e18aaaSAndroid Build Coastguard Worker
174*c2e18aaaSAndroid Build Coastguard Worker    Args:
175*c2e18aaaSAndroid Build Coastguard Worker        cls: this class object.
176*c2e18aaaSAndroid Build Coastguard Worker        atest_event: A client_pb2.AtestLogEvent instance.
177*c2e18aaaSAndroid Build Coastguard Worker
178*c2e18aaaSAndroid Build Coastguard Worker    Returns:
179*c2e18aaaSAndroid Build Coastguard Worker        A clientanalytics_pb2.LogEvent instance.
180*c2e18aaaSAndroid Build Coastguard Worker    """
181*c2e18aaaSAndroid Build Coastguard Worker    log_event = clientanalytics_pb2.LogEvent()
182*c2e18aaaSAndroid Build Coastguard Worker    log_event.event_time_ms = int(time.time() * 1000)
183*c2e18aaaSAndroid Build Coastguard Worker    log_event.source_extension = atest_event.SerializeToString()
184*c2e18aaaSAndroid Build Coastguard Worker    return log_event
185