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""" 16Metrics base class. 17""" 18 19from __future__ import print_function 20 21import logging 22import random 23import socket 24import subprocess 25import time 26import uuid 27 28from atest import asuite_metrics 29from atest import constants 30 31from atest.proto import clientanalytics_pb2 32from atest.proto import external_user_log_pb2 33from atest.proto import internal_user_log_pb2 34 35from atest.metrics import clearcut_client 36 37INTERNAL_USER = 0 38EXTERNAL_USER = 1 39 40ATEST_EVENTS = { 41 INTERNAL_USER: internal_user_log_pb2.AtestLogEventInternal, 42 EXTERNAL_USER: external_user_log_pb2.AtestLogEventExternal 43} 44# log source 45ATEST_LOG_SOURCE = { 46 INTERNAL_USER: 971, 47 EXTERNAL_USER: 934 48} 49 50def get_user_email(): 51 """Get user mail in git config. 52 53 Returns: 54 user's email. 55 """ 56 try: 57 output = subprocess.check_output( 58 ['git', 'config', '--get', 'user.email'], universal_newlines=True) 59 return output.strip() if output else '' 60 except OSError: 61 # OSError can be raised when running atest_unittests on a host 62 # without git being set up. 63 logging.debug('Unable to determine if this is an external run, git is ' 64 'not found.') 65 except subprocess.CalledProcessError: 66 logging.debug('Unable to determine if this is an external run, email ' 67 'is not found in git config.') 68 return '' 69 70def get_user_type(): 71 """Get user type. 72 73 Determine the internal user by passing at least one check: 74 - whose git mail domain is from google 75 - whose hostname is from google 76 Otherwise is external user. 77 78 Returns: 79 INTERNAL_USER if user is internal, EXTERNAL_USER otherwise. 80 """ 81 email = get_user_email() 82 if email.endswith(constants.INTERNAL_EMAIL): 83 return INTERNAL_USER 84 85 try: 86 hostname = socket.getfqdn() 87 if (hostname and 88 any((x in hostname) for x in constants.INTERNAL_HOSTNAME)): 89 return INTERNAL_USER 90 except IOError: 91 logging.debug('Unable to determine if this is an external run, ' 92 'hostname is not found.') 93 return EXTERNAL_USER 94 95 96class MetricsBase: 97 """Class for separating allowed fields and sending metric.""" 98 99 _run_id = str(uuid.uuid4()) 100 try: 101 #pylint: disable=protected-access 102 _user_key = str(asuite_metrics._get_grouping_key()) 103 #pylint: disable=broad-except 104 except Exception: 105 _user_key = asuite_metrics.UNUSED_UUID 106 _user_type = get_user_type() 107 _log_source = ATEST_LOG_SOURCE[_user_type] 108 cc = clearcut_client.Clearcut(_log_source) 109 tool_name = None 110 sub_tool_name = '' 111 112 def __new__(cls, **kwargs): 113 """Send metric event to clearcut. 114 115 Args: 116 cls: this class object. 117 **kwargs: A dict of named arguments. 118 119 Returns: 120 A Clearcut instance. 121 """ 122 # pylint: disable=no-member 123 if not cls.tool_name: 124 logging.debug('There is no tool_name, and metrics stops sending.') 125 return None 126 allowed = ({constants.EXTERNAL} if cls._user_type == EXTERNAL_USER 127 else {constants.EXTERNAL, constants.INTERNAL}) 128 fields = [k for k, v in vars(cls).items() 129 if not k.startswith('_') and v in allowed] 130 fields_and_values = {} 131 for field in fields: 132 if field in kwargs: 133 fields_and_values[field] = kwargs.pop(field) 134 params = {'user_key': cls._user_key, 135 'run_id': cls._run_id, 136 'user_type': cls._user_type, 137 'tool_name': cls.tool_name, 138 'sub_tool_name': cls.sub_tool_name, 139 cls._EVENT_NAME: fields_and_values} 140 log_event = cls._build_full_event( 141 ATEST_EVENTS[cls._user_type](**params)) 142 cls.cc.log(log_event) 143 return cls.cc 144 145 @classmethod 146 def _build_full_event(cls, atest_event): 147 """This is all protobuf building you can ignore. 148 149 Args: 150 cls: this class object. 151 atest_event: A client_pb2.AtestLogEvent instance. 152 153 Returns: 154 A clientanalytics_pb2.LogEvent instance. 155 """ 156 log_event = clientanalytics_pb2.LogEvent() 157 log_event.event_time_ms = int( 158 (time.time() - random.randint(1, 600)) * 1000) 159 log_event.source_extension = atest_event.SerializeToString() 160 return log_event 161