#!/usr/bin/python # # Copyright 2015, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # import os import sys import re import urllib2 import urllib import json import time import subprocess import fnmatch import argparse CLIENT_ID = '1018396037782-tv81fshn76nemr24uuhuginceb9hni2m.apps.googleusercontent.com' CLIENT_SECRET = '_HGHXg4DAA59r4w4x8p6ARzD' GRANT_TYPE = 'http://oauth.net/grant_type/device/1.0' AUTH_TOKEN_LINK = 'https://www.googleapis.com/oauth2/v3/token' GOOGLE_ACCOUNTS_LINK = 'https://accounts.google.com/o/oauth2/device/code' USER_INFO_LINK = 'https://www.googleapis.com/oauth2/v1/userinfo' parser = argparse.ArgumentParser(description='Report metrics to performance database') parser.add_argument('--test', type=str, help='Name of the test to be executed') parser.add_argument('--email', type=str, help='Gmail address of the user') parser.add_argument('--server_address', type=str, default='localhost:50052', help='Address of the performance database server') parser.add_argument('--tokens_dir', type=str, default=os.path.expanduser('~')+'/.grpc/access_tokens', help='Path to the access tokens directory') # Fetches JSON reply object, given a url and parameters def fetchJSON(url, paramDict): if len(paramDict) == 0: req = urllib2.Request(url) else: data = urllib.urlencode(paramDict) req = urllib2.Request(url, data) try: response = urllib2.urlopen(req) result = response.read() except urllib2.HTTPError, error: result = error.read() return result # Fetch user info; used to check if access token is valid def getUserInfo(accessToken): url = USER_INFO_LINK + '?access_token=' + accessToken paramDict = {} JSONBody = fetchJSON(url, paramDict) data = json.loads(JSONBody) return data # Returns true if stored access token is valid def isAccessTokenValid(accessToken): data = getUserInfo(accessToken); if 'id' in data: return True else: return False # Returns user id given a working access token def getUserId(accessToken): data = getUserInfo(accessToken) email = data['email'] userId = getUserIdFromEmail(email) return userId # Extracts a unique user id from an email address def getUserIdFromEmail(email): email = email.split('@')[0].lower() # take username and convert to lower case userId = re.sub('[.]', '', email) # remove periods return userId # Use an existing access token def useAccessToken(userTokFile): with open(userTokFile, "r") as data_file: data = json.load(data_file) # load JSON data from file accessToken = data["access_token"] # If access token has gone stale, refresh it if not isAccessTokenValid(accessToken): return refreshAccessToken(data["refresh_token"], userTokFile) return accessToken # refresh stale access token def refreshAccessToken(refreshToken, userTokFile): # Parameters for request paramDict = {'refresh_token':refreshToken, 'client_id':CLIENT_ID, 'client_secret':CLIENT_SECRET, 'grant_type':'refresh_token'} # Fetch reply to request JSONBody = fetchJSON(AUTH_TOKEN_LINK, paramDict) data = json.loads(JSONBody) if not 'access_token' in data: # Refresh token has gone stale, re-authentication required return reauthenticate() else: # write fresh access token to tokens file tokenData = {} with open(userTokFile, "r") as data_file: tokenData = json.load(data_file) with open(userTokFile, "w") as data_file: tokenData['access_token'] = data['access_token'] json.dump(tokenData, data_file) # return fresh access token return data['access_token'] def reauthenticate(tokensDir): # Create folder if not created already if not os.path.exists(tokensDir): os.makedirs(tokensDir) os.chmod(tokensDir, 0700) # Request parameters paramDict = {'client_id':CLIENT_ID, 'scope':'email profile'} JSONBody = fetchJSON(GOOGLE_ACCOUNTS_LINK, paramDict) data = json.loads(JSONBody) print 'User authorization required\n' print 'Please use the following code in you browser: ', data['user_code'] # Code to be entered by user in browser print 'Verification URL: ', data['verification_url'] # Authentication link print '\nAwaiting user authorization. May take a few more seconds after authorizing...\n' authData = {} while not 'access_token' in authData: # Request parameters authDict = {'client_id':CLIENT_ID, 'client_secret':CLIENT_SECRET, 'code':data['device_code'], 'grant_type':GRANT_TYPE} JSONBody = fetchJSON(AUTH_TOKEN_LINK, authDict) authData = json.loads(JSONBody) # If server pinged too quickly, will get slowdown message; need to wait for specified interval time.sleep(data['interval']) # File to write tokens newUserTokFile = tokensDir + '/' + getUserId(authData['access_token']) # Write tokens to file with open(newUserTokFile, "w") as data_file: os.chmod(newUserTokFile, 0600) json.dump(authData, data_file) # return working access token return authData['access_token'] # Fetch a working access token given user entered email id; authntication may be required def getAccessToken(email, tokensDir): # Get unique user id from email address userId = getUserIdFromEmail(email) # Token file userTokFile = tokensDir + '/' + userId accessToken = '' if os.path.exists(userTokFile): # File containing access token exists; unless refresh token has expired, user authentication will not be required accessToken = useAccessToken(userTokFile) else: # User authentication required accessToken = reauthenticate(tokensDir) return accessToken # If user has not entered full path to test, recursively searches for given test in parent folders def findTestPath(test): # If user entered full path to test, return it if(os.path.isfile(test)): return test testName = test.split('/')[-1] # Extract just test name testPath = '' # Search for test for root, dirnames, filenames in os.walk('../../../'): for fileName in fnmatch.filter(filenames, testName): testPath = os.path.join(root, fileName) return testPath def getSysInfo(): # Fetch system information sysInfo = os.popen('lscpu').readlines() NICs = os.popen('ifconfig | cut -c1-8 | sed \'/^\s*$/d\' | sort -u').readlines() nicAddrs = os.popen('ifconfig | grep -oE "inet addr:([0-9]{1,3}\.){3}[0-9]{1,3}"').readlines() nicInfo = [] for i in range(0, len(NICs)): NIC = NICs[i] NIC = re.sub(r'[^\w]', '', NIC) ethtoolProcess = subprocess.Popen(["ethtool",NIC], stdout=subprocess.PIPE, stderr=subprocess.PIPE) ethtoolResult = ethtoolProcess.communicate()[0] ethtoolResultList = ethtoolResult.split('\n\t') for ethtoolString in ethtoolResultList: if ethtoolString.startswith('Speed'): ethtoolString = ethtoolString.split(':')[1] ethtoolString = ethtoolString.replace('Mb/s',' Mbps') nicInfo.append('NIC ' + NIC + ' speed: ' + ethtoolString + '\n') nicInfo.append(NIC + ' inet address: ' + nicAddrs[i].split(':')[1]) print 'Obtaining network info....' tcp_rr_rate = str(os.popen('netperf -t TCP_RR -v 0').readlines()[1]) print 'Network info obtained' nicInfo.append('TCP RR transmission rate per sec: ' + tcp_rr_rate + '\n') sysInfo = sysInfo + nicInfo return sysInfo def main(argv): args = parser.parse_args() tokensDir = args.tokens_dir try: # Fetch working access token accessToken = getAccessToken(args.email, tokensDir) except AttributeError: print '\nError: Please provide email address as an argument\n' sys.exit(1) except Exception, e: print e, ' \nError in authentication\n' sys.exit(1) # Address of the performance database server serverAddress = args.server_address # Get path to test try: testPath = findTestPath(args.test) except TypeError: print '\nError: Please provide test name/path as argument\n' sys.exit(1) # Get name of the test testName = testPath.split('/')[-1] # Get the system information sysInfo = getSysInfo() try: print '\nBeginning test:\n' # Run the test subprocess.call([testPath, '--report_metrics_db=true', '--access_token='+accessToken, '--test_name='+testName, '--sys_info='+str(sysInfo).strip('[]'), '--server_address='+serverAddress]) except OSError: print 'Could not execute the test' if __name__ == "__main__": main(sys.argv)