upload_test_results.py 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. #!/usr/bin/env python
  2. # Copyright 2017 gRPC authors.
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. """Helper to upload Jenkins test results to BQ"""
  16. from __future__ import print_function
  17. import os
  18. import sys
  19. import time
  20. import uuid
  21. import six
  22. gcp_utils_dir = os.path.abspath(
  23. os.path.join(os.path.dirname(__file__), '../../gcp/utils'))
  24. sys.path.append(gcp_utils_dir)
  25. import big_query_utils
  26. _DATASET_ID = 'jenkins_test_results'
  27. _DESCRIPTION = 'Test results from master job run on Jenkins'
  28. # 365 days in milliseconds
  29. _EXPIRATION_MS = 365 * 24 * 60 * 60 * 1000
  30. _PARTITION_TYPE = 'DAY'
  31. _PROJECT_ID = 'grpc-testing'
  32. _RESULTS_SCHEMA = [
  33. ('job_name', 'STRING', 'Name of Jenkins job'),
  34. ('build_id', 'INTEGER', 'Build ID of Jenkins job'),
  35. ('build_url', 'STRING', 'URL of Jenkins job'),
  36. ('test_name', 'STRING', 'Individual test name'),
  37. ('language', 'STRING', 'Language of test'),
  38. ('platform', 'STRING', 'Platform used for test'),
  39. ('config', 'STRING', 'Config used for test'),
  40. ('compiler', 'STRING', 'Compiler used for test'),
  41. ('iomgr_platform', 'STRING', 'Iomgr used for test'),
  42. ('result', 'STRING', 'Test result: PASSED, TIMEOUT, FAILED, or SKIPPED'),
  43. ('timestamp', 'TIMESTAMP', 'Timestamp of test run'),
  44. ('elapsed_time', 'FLOAT', 'How long test took to run'),
  45. ('cpu_estimated', 'FLOAT', 'Estimated CPU usage of test'),
  46. ('cpu_measured', 'FLOAT', 'Actual CPU usage of test'),
  47. ('return_code', 'INTEGER', 'Exit code of test'),
  48. ]
  49. _INTEROP_RESULTS_SCHEMA = [
  50. ('job_name', 'STRING', 'Name of Jenkins/Kokoro job'),
  51. ('build_id', 'INTEGER', 'Build ID of Jenkins/Kokoro job'),
  52. ('build_url', 'STRING', 'URL of Jenkins/Kokoro job'),
  53. ('test_name', 'STRING',
  54. 'Unique test name combining client, server, and test_name'),
  55. ('suite', 'STRING',
  56. 'Test suite: cloud_to_cloud, cloud_to_prod, or cloud_to_prod_auth'),
  57. ('client', 'STRING', 'Client language'),
  58. ('server', 'STRING', 'Server host name'),
  59. ('test_case', 'STRING', 'Name of test case'),
  60. ('result', 'STRING', 'Test result: PASSED, TIMEOUT, FAILED, or SKIPPED'),
  61. ('timestamp', 'TIMESTAMP', 'Timestamp of test run'),
  62. ('elapsed_time', 'FLOAT', 'How long test took to run'),
  63. ]
  64. def _get_build_metadata(test_results):
  65. """Add Kokoro build metadata to test_results based on environment
  66. variables set by Kokoro.
  67. """
  68. build_id = os.getenv('KOKORO_BUILD_NUMBER')
  69. build_url = 'https://source.cloud.google.com/results/invocations/%s' % os.getenv(
  70. 'KOKORO_BUILD_ID')
  71. job_name = os.getenv('KOKORO_JOB_NAME')
  72. if build_id:
  73. test_results['build_id'] = build_id
  74. if build_url:
  75. test_results['build_url'] = build_url
  76. if job_name:
  77. test_results['job_name'] = job_name
  78. def _insert_rows_with_retries(bq, bq_table, bq_rows):
  79. """Insert rows to bq table. Retry on error."""
  80. # BigQuery sometimes fails with large uploads, so batch 1,000 rows at a time.
  81. for i in range((len(bq_rows) // 1000) + 1):
  82. max_retries = 3
  83. for attempt in range(max_retries):
  84. if big_query_utils.insert_rows(bq, _PROJECT_ID, _DATASET_ID,
  85. bq_table,
  86. bq_rows[i * 1000:(i + 1) * 1000]):
  87. break
  88. else:
  89. if attempt < max_retries - 1:
  90. print('Error uploading result to bigquery, will retry.')
  91. else:
  92. print(
  93. 'Error uploading result to bigquery, all attempts failed.'
  94. )
  95. sys.exit(1)
  96. def upload_results_to_bq(resultset, bq_table, extra_fields):
  97. """Upload test results to a BQ table.
  98. Args:
  99. resultset: dictionary generated by jobset.run
  100. bq_table: string name of table to create/upload results to in BQ
  101. extra_fields: dict with extra values that will be uploaded along with the results
  102. """
  103. bq = big_query_utils.create_big_query()
  104. big_query_utils.create_partitioned_table(bq,
  105. _PROJECT_ID,
  106. _DATASET_ID,
  107. bq_table,
  108. _RESULTS_SCHEMA,
  109. _DESCRIPTION,
  110. partition_type=_PARTITION_TYPE,
  111. expiration_ms=_EXPIRATION_MS)
  112. bq_rows = []
  113. for shortname, results in six.iteritems(resultset):
  114. for result in results:
  115. test_results = {}
  116. _get_build_metadata(test_results)
  117. test_results['cpu_estimated'] = result.cpu_estimated
  118. test_results['cpu_measured'] = result.cpu_measured
  119. test_results['elapsed_time'] = '%.2f' % result.elapsed_time
  120. test_results['result'] = result.state
  121. test_results['return_code'] = result.returncode
  122. test_results['test_name'] = shortname
  123. test_results['timestamp'] = time.strftime('%Y-%m-%d %H:%M:%S')
  124. for field_name, field_value in six.iteritems(extra_fields):
  125. test_results[field_name] = field_value
  126. row = big_query_utils.make_row(str(uuid.uuid4()), test_results)
  127. bq_rows.append(row)
  128. _insert_rows_with_retries(bq, bq_table, bq_rows)
  129. def upload_interop_results_to_bq(resultset, bq_table):
  130. """Upload interop test results to a BQ table.
  131. Args:
  132. resultset: dictionary generated by jobset.run
  133. bq_table: string name of table to create/upload results to in BQ
  134. """
  135. bq = big_query_utils.create_big_query()
  136. big_query_utils.create_partitioned_table(bq,
  137. _PROJECT_ID,
  138. _DATASET_ID,
  139. bq_table,
  140. _INTEROP_RESULTS_SCHEMA,
  141. _DESCRIPTION,
  142. partition_type=_PARTITION_TYPE,
  143. expiration_ms=_EXPIRATION_MS)
  144. bq_rows = []
  145. for shortname, results in six.iteritems(resultset):
  146. for result in results:
  147. test_results = {}
  148. _get_build_metadata(test_results)
  149. test_results['elapsed_time'] = '%.2f' % result.elapsed_time
  150. test_results['result'] = result.state
  151. test_results['test_name'] = shortname
  152. test_results['suite'] = shortname.split(':')[0]
  153. test_results['client'] = shortname.split(':')[1]
  154. test_results['server'] = shortname.split(':')[2]
  155. test_results['test_case'] = shortname.split(':')[3]
  156. test_results['timestamp'] = time.strftime('%Y-%m-%d %H:%M:%S')
  157. row = big_query_utils.make_row(str(uuid.uuid4()), test_results)
  158. bq_rows.append(row)
  159. _insert_rows_with_retries(bq, bq_table, bq_rows)