run_perf_db_test.py 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. #!/usr/bin/python
  2. #
  3. # Copyright 2015, Google Inc.
  4. # All rights reserved.
  5. #
  6. # Redistribution and use in source and binary forms, with or without
  7. # modification, are permitted provided that the following conditions are
  8. # met:
  9. #
  10. # * Redistributions of source code must retain the above copyright
  11. # notice, this list of conditions and the following disclaimer.
  12. # * Redistributions in binary form must reproduce the above
  13. # copyright notice, this list of conditions and the following disclaimer
  14. # in the documentation and/or other materials provided with the
  15. # distribution.
  16. # * Neither the name of Google Inc. nor the names of its
  17. # contributors may be used to endorse or promote products derived from
  18. # this software without specific prior written permission.
  19. #
  20. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  21. # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  22. # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  23. # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  24. # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  25. # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  26. # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  27. # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  28. # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  29. # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  30. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  31. #
  32. import os
  33. import sys
  34. import re
  35. import urllib2
  36. import urllib
  37. import json
  38. import time
  39. import subprocess
  40. import fnmatch
  41. import gflags
  42. CLIENT_ID = '1018396037782-tv81fshn76nemr24uuhuginceb9hni2m.apps.googleusercontent.com'
  43. CLIENT_SECRET = '_HGHXg4DAA59r4w4x8p6ARzD'
  44. GRANT_TYPE = 'http://oauth.net/grant_type/device/1.0'
  45. DEFAULT_ACCESS_TOKENS_DIR = '/usr/local/auth_access_tokens'
  46. AUTH_TOKEN_LINK = 'https://www.googleapis.com/oauth2/v3/token'
  47. GOOGLE_ACCOUNTS_LINK = 'https://accounts.google.com/o/oauth2/device/code'
  48. USER_INFO_LINK = 'https://www.googleapis.com/oauth2/v1/userinfo'
  49. FLAGS = gflags.FLAGS
  50. gflags.DEFINE_string('test', None, 'Name of the test')
  51. gflags.DEFINE_string('email', None, 'gmail id')
  52. gflags.DEFINE_string('server_address', 'localhost:50052', 'Address of the performance database server')
  53. gflags.DEFINE_string('tokens_dir', DEFAULT_ACCESS_TOKENS_DIR, 'Location of the access tokens')
  54. # Fetches JSON reply object, given a url and parameters
  55. def fetchJSON(url, paramDict):
  56. if len(paramDict) == 0:
  57. req = urllib2.Request(url)
  58. else:
  59. data = urllib.urlencode(paramDict)
  60. req = urllib2.Request(url, data)
  61. try:
  62. response = urllib2.urlopen(req)
  63. result = response.read()
  64. except urllib2.HTTPError, error:
  65. result = error.read()
  66. return result
  67. # Fetch user info; used to check if access token is valid
  68. def getUserInfo(accessToken):
  69. url = USER_INFO_LINK + '?access_token=' + accessToken
  70. paramDict = {}
  71. JSONBody = fetchJSON(url, paramDict)
  72. data = json.loads(JSONBody)
  73. return data
  74. # Returns true if stored access token is valid
  75. def isAccessTokenValid(accessToken):
  76. data = getUserInfo(accessToken);
  77. if 'id' in data:
  78. return True
  79. else:
  80. return False
  81. # Returns user id given a working access token
  82. def getUserId(accessToken):
  83. data = getUserInfo(accessToken)
  84. email = data['email']
  85. userId = getUserIdFromEmail(email)
  86. return userId
  87. # Extracts a unique user id from an email address
  88. def getUserIdFromEmail(email):
  89. email = email.split('@')[0].lower() # take username and convert to lower case
  90. userId = re.sub('[.]', '', email) # remove periods
  91. return userId
  92. # Use an existing access token
  93. def useAccessToken(userTokFile):
  94. with open(userTokFile, "r") as data_file:
  95. data = json.load(data_file) # load JSON data from file
  96. accessToken = data["access_token"]
  97. # If access token has gone stale, refresh it
  98. if not isAccessTokenValid(accessToken):
  99. return refreshAccessToken(data["refresh_token"], userTokFile)
  100. return accessToken
  101. # refresh stale access token
  102. def refreshAccessToken(refreshToken, userTokFile):
  103. # Parameters for request
  104. paramDict = {'refresh_token':refreshToken, 'client_id':CLIENT_ID, 'client_secret':CLIENT_SECRET, 'grant_type':'refresh_token'}
  105. # Fetch reply to request
  106. JSONBody = fetchJSON(AUTH_TOKEN_LINK, paramDict)
  107. data = json.loads(JSONBody)
  108. if not 'access_token' in data:
  109. # Refresh token has gone stale, re-authentication required
  110. return reauthenticate()
  111. else:
  112. # write fresh access token to tokens file
  113. tokenData = {}
  114. with open(userTokFile, "r") as data_file:
  115. tokenData = json.load(data_file)
  116. with open(userTokFile, "w") as data_file:
  117. tokenData['access_token'] = data['access_token']
  118. json.dump(tokenData, data_file)
  119. # return fresh access token
  120. return data['access_token']
  121. def reauthenticate(tokensDir):
  122. # Request parameters
  123. paramDict = {'client_id':CLIENT_ID, 'scope':'email profile'}
  124. JSONBody = fetchJSON(GOOGLE_ACCOUNTS_LINK, paramDict)
  125. data = json.loads(JSONBody)
  126. print 'User authorization required\n'
  127. print 'Please use the following code in you browser: ', data['user_code'] # Code to be entered by user in browser
  128. print 'Verification URL: ', data['verification_url'] # Authentication link
  129. print '\nAwaiting user authorization. May take a few more seconds after authorizing...\n'
  130. authData = {}
  131. while not 'access_token' in authData:
  132. # Request parameters
  133. authDict = {'client_id':CLIENT_ID, 'client_secret':CLIENT_SECRET, 'code':data['device_code'], 'grant_type':GRANT_TYPE}
  134. JSONBody = fetchJSON(AUTH_TOKEN_LINK, authDict)
  135. authData = json.loads(JSONBody)
  136. # If server pinged too quickly, will get slowdown message; need to wait for specified interval
  137. time.sleep(data['interval'])
  138. # File to write tokens
  139. newUserTokFile = tokensDir + '/' + getUserId(authData['access_token'])
  140. # Write tokens to file
  141. with open(newUserTokFile, "w") as data_file:
  142. json.dump(authData, data_file)
  143. # return working access token
  144. return authData['access_token']
  145. # Fetch a working access token given user entered email id; authntication may be required
  146. def getAccessToken(email, tokensDir):
  147. # Get unique user id from email address
  148. userId = getUserIdFromEmail(email)
  149. # Token file
  150. userTokFile = tokensDir + '/' + userId
  151. accessToken = ''
  152. if os.path.exists(userTokFile):
  153. # File containing access token exists; unless refresh token has expired, user authentication will not be required
  154. accessToken = useAccessToken(userTokFile)
  155. else:
  156. # User authentication required
  157. accessToken = reauthenticate(tokensDir)
  158. return accessToken
  159. # If user has not entered full path to test, recursively searches for given test in parent folders
  160. def findTestPath(test):
  161. # If user entered full path to test, return it
  162. if(os.path.isfile(test)):
  163. return test
  164. testName = test.split('/')[-1] # Extract just test name
  165. testPath = ''
  166. # Search for test
  167. for root, dirnames, filenames in os.walk('../../../'):
  168. for fileName in fnmatch.filter(filenames, testName):
  169. testPath = os.path.join(root, fileName)
  170. return testPath
  171. def getSysInfo():
  172. # Fetch system information
  173. sysInfo = os.popen('lscpu').readlines()
  174. NICs = os.popen('ifconfig | cut -c1-8 | sed \'/^\s*$/d\' | sort -u').readlines()
  175. nicAddrs = os.popen('ifconfig | grep -oE "inet addr:([0-9]{1,3}\.){3}[0-9]{1,3}"').readlines()
  176. nicInfo = []
  177. for i in range(0, len(NICs)):
  178. NIC = NICs[i]
  179. NIC = re.sub(r'[^\w]', '', NIC)
  180. ethtoolProcess = subprocess.Popen(["ethtool",NIC], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  181. ethtoolResult = ethtoolProcess.communicate()[0]
  182. ethtoolResultList = ethtoolResult.split('\n\t')
  183. for ethtoolString in ethtoolResultList:
  184. if ethtoolString.startswith('Speed'):
  185. ethtoolString = ethtoolString.split(':')[1]
  186. ethtoolString = ethtoolString.replace('Mb/s',' Mbps')
  187. nicInfo.append('NIC ' + NIC + ' speed: ' + ethtoolString + '\n')
  188. nicInfo.append(NIC + ' inet address: ' + nicAddrs[i].split(':')[1])
  189. print 'Obtaining network info....'
  190. tcp_rr_rate = str(os.popen('netperf -t TCP_RR -v 0').readlines()[1])
  191. print 'Network info obtained'
  192. nicInfo.append('TCP RR transmission rate per sec: ' + tcp_rr_rate + '\n')
  193. sysInfo = sysInfo + nicInfo
  194. return sysInfo
  195. def main(argv):
  196. try:
  197. argv = FLAGS(argv)
  198. except Exception, e:
  199. print '%s\\nUsage: %s ARGS\\n%s' % (e, sys.argv[0], FLAGS)
  200. sys.exit(1)
  201. tokensDir = FLAGS.tokens_dir
  202. # If tokens directory does not exist, creates it
  203. if not os.path.exists(tokensDir):
  204. os.makedirs(tokensDir)
  205. try:
  206. # Fetch working access token
  207. accessToken = getAccessToken(FLAGS.email, tokensDir)
  208. except AttributeError:
  209. print '\nError: Please provide email address as an argument\n'
  210. sys.exit(1)
  211. except:
  212. print '\nError in authentication\n'
  213. sys.exit(1)
  214. # Address of the performance database server
  215. serverAddress = FLAGS.server_address
  216. # Get path to test
  217. try:
  218. testPath = findTestPath(FLAGS.test)
  219. except TypeError:
  220. print '\nError: Please provide test name/path as argument\n'
  221. sys.exit(1)
  222. # Get name of the test
  223. testName = testPath.split('/')[-1]
  224. # Get the system information
  225. sysInfo = getSysInfo()
  226. try:
  227. print '\nBeginning test:\n'
  228. # Run the test
  229. subprocess.call([testPath, '--report_metrics_db=true', '--access_token='+accessToken, '--test_name='+testName, '--sys_info='+str(sysInfo).strip('[]'), '--server_address='+serverAddress])
  230. except OSError:
  231. print 'Could not execute the test'
  232. if __name__ == "__main__":
  233. main(sys.argv)