Skip to content

Commit ba6f410

Browse files
authored
Add workflow definition for health metric tests (#3954)
1 parent 1196bc5 commit ba6f410

File tree

8 files changed

+175
-56
lines changed

8 files changed

+175
-56
lines changed

.github/workflows/health-metrics.yml

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
name: Health Metrics
2+
3+
on: [ pull_request, push ]
4+
5+
env:
6+
GITHUB_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }}
7+
8+
jobs:
9+
coverage:
10+
name: Coverage
11+
if: (github.repository == 'Firebase/firebase-android-sdk' && github.event_name == 'push') || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository)
12+
runs-on: ubuntu-latest
13+
steps:
14+
- uses: actions/checkout@v3
15+
with:
16+
fetch-depth: 2
17+
submodules: true
18+
- name: Set up JDK 11
19+
uses: actions/setup-java@v2
20+
with:
21+
java-version: 11
22+
distribution: temurin
23+
cache: gradle
24+
- name: Set up NDK 21.4.7075529
25+
run: |
26+
ANDROID_ROOT=/usr/local/lib/android
27+
ANDROID_SDK_ROOT=${ANDROID_ROOT}/sdk
28+
ANDROID_NDK_ROOT=${ANDROID_SDK_ROOT}/ndk-bundle
29+
SDKMANAGER=${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager
30+
echo "y" | $SDKMANAGER "ndk;21.4.7075529"
31+
ln -sfn ${ANDROID_SDK_ROOT}/ndk/21.4.7075529 ${ANDROID_NDK_ROOT}
32+
echo "ANDROID_NDK_HOME=${ANDROID_NDK_ROOT}" >> $GITHUB_ENV
33+
- name: Set up Python 3.10
34+
uses: actions/setup-python@v4
35+
with:
36+
python-version: '3.10'
37+
- uses: google-github-actions/auth@v0
38+
with:
39+
credentials_json: '${{ secrets.GCP_SERVICE_ACCOUNT }}'
40+
- uses: google-github-actions/setup-gcloud@v0
41+
- name: Set up fireci
42+
run: pip3 install -e ci/fireci
43+
- name: Run coverage tests (presubmit)
44+
if: ${{ github.event_name == 'pull_request' }}
45+
run: fireci coverage --pull-request
46+
- name: Run coverage tests (post-submit)
47+
if: ${{ github.event_name == 'push' }}
48+
run: fireci coverage
49+
50+
size:
51+
name: Size
52+
if: (github.repository == 'Firebase/firebase-android-sdk' && github.event_name == 'push') || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository)
53+
runs-on: ubuntu-latest
54+
steps:
55+
- uses: actions/checkout@v3
56+
with:
57+
fetch-depth: 2
58+
submodules: true
59+
- name: Set up JDK 11
60+
uses: actions/setup-java@v2
61+
with:
62+
java-version: 11
63+
distribution: temurin
64+
cache: gradle
65+
- name: Set up NDK 21.4.7075529
66+
run: |
67+
ANDROID_ROOT=/usr/local/lib/android
68+
ANDROID_SDK_ROOT=${ANDROID_ROOT}/sdk
69+
ANDROID_NDK_ROOT=${ANDROID_SDK_ROOT}/ndk-bundle
70+
SDKMANAGER=${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager
71+
echo "y" | $SDKMANAGER "ndk;21.4.7075529"
72+
ln -sfn ${ANDROID_SDK_ROOT}/ndk/21.4.7075529 ${ANDROID_NDK_ROOT}
73+
echo "ANDROID_NDK_HOME=${ANDROID_NDK_ROOT}" >> $GITHUB_ENV
74+
- name: Set up Python 3.10
75+
uses: actions/setup-python@v4
76+
with:
77+
python-version: '3.10'
78+
- uses: google-github-actions/auth@v0
79+
with:
80+
credentials_json: '${{ secrets.GCP_SERVICE_ACCOUNT }}'
81+
- uses: google-github-actions/setup-gcloud@v0
82+
- name: Set up fireci
83+
run: pip3 install -e ci/fireci
84+
- name: Run size tests (presubmit)
85+
if: ${{ github.event_name == 'pull_request' }}
86+
run: fireci binary_size --pull-request
87+
- name: Run size tests (post-submit)
88+
if: ${{ github.event_name == 'push' }}
89+
run: fireci binary_size

buildSrc/src/main/java/com/google/firebase/gradle/plugins/ci/AffectedProjectFinder.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,10 @@ Set<Project> find() {
6868

6969
private static Set<String> changedPaths(File workDir) {
7070
try {
71-
// works on Prow only.
71+
// works on CI only.
7272
Process process =
7373
Runtime.getRuntime()
74-
.exec("git diff --name-only --submodule=diff HEAD@{0} HEAD@{1}", null, workDir);
74+
.exec("git diff --name-only --submodule=diff HEAD^1 HEAD", null, workDir);
7575
try {
7676
return ImmutableSet.copyOf(
7777
CharStreams.readLines(new InputStreamReader(process.getInputStream())));

ci/fireci/fireci/prow_utils.py renamed to ci/fireci/fireci/ci_utils.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,25 @@
1616
import os
1717
import subprocess
1818

19-
_logger = logging.getLogger('fireci.prow_utils')
19+
_logger = logging.getLogger('fireci.ci_utils')
20+
21+
22+
def ci_log_link():
23+
"""Returns the link to the log of the current CI test."""
24+
25+
if os.getenv('GITHUB_ACTIONS'):
26+
return github_actions_run_log_link()
27+
elif os.getenv('PROW_JOB_ID'):
28+
return prow_job_log_link()
29+
30+
31+
def github_actions_run_log_link():
32+
"""Returns the link to the log of the current GitHub Actions run."""
33+
34+
repo = os.getenv('GITHUB_REPOSITORY')
35+
run_id = os.getenv('GITHUB_RUN_ID')
36+
37+
return f'https://github.com/{repo}/actions/runs/{run_id}'
2038

2139

2240
def prow_job_log_link():

ci/fireci/fireci/uploader.py

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,19 @@
1919
import subprocess
2020
import urllib.parse
2121

22-
from . import prow_utils
2322

2423
_logger = logging.getLogger('fireci.uploader')
2524

2625

27-
def post_report(test_report, metrics_service_url, access_token, metric='reports', note=''):
26+
def post_report(test_report, metrics_service_url, access_token, metric_type):
2827
"""Post a report to the metrics service backend."""
2928

30-
endpoint = _construct_request_endpoint(metric, note)
29+
endpoint = ''
30+
if os.getenv('GITHUB_ACTIONS'):
31+
endpoint = _construct_request_endpoint_for_github_actions(metric_type)
32+
elif os.getenv('PROW_JOB_ID'):
33+
endpoint = _construct_request_endpoint_for_prow(metric_type)
34+
3135
headers = {'Authorization': f'Bearer {access_token}', 'Content-Type': 'application/json'}
3236
data = json.dumps(test_report)
3337

@@ -41,25 +45,36 @@ def post_report(test_report, metrics_service_url, access_token, metric='reports'
4145
_logger.info(f'Response: {result.text}')
4246

4347

44-
def _construct_request_endpoint(metric, note):
48+
def _construct_request_endpoint_for_github_actions(metric_type):
49+
repo = os.getenv('GITHUB_REPOSITORY')
50+
commit = os.getenv('GITHUB_SHA')
51+
event_name = os.getenv('GITHUB_EVENT_NAME')
52+
53+
endpoint = f'/repos/{repo}/commits/{commit}/{metric_type}'
54+
if event_name == 'pull_request':
55+
pull_request = os.getenv('GITHUB_PULL_REQUEST_NUMBER')
56+
base_commit = _get_commit_hash('HEAD^1')
57+
head_commit = _get_commit_hash('HEAD^2')
58+
endpoint += f'?pull_request={pull_request}&base_commit={base_commit}&head_commit={head_commit}'
59+
else:
60+
branch = os.getenv('GITHUB_REF_NAME')
61+
endpoint += f'?branch={branch}'
62+
63+
return endpoint
64+
65+
def _construct_request_endpoint_for_prow(metric_type):
4566
repo_owner = os.getenv('REPO_OWNER')
4667
repo_name = os.getenv('REPO_NAME')
47-
branch = os.getenv('PULL_BASE_REF')
48-
pull_request = os.getenv('PULL_NUMBER')
49-
5068
commit = _get_commit_hash('HEAD@{0}')
51-
log = prow_utils.prow_job_log_link()
69+
pull_request = os.getenv('PULL_NUMBER')
5270

53-
endpoint = f'/repos/{repo_owner}/{repo_name}/commits/{commit}/{metric}?log={log}'
71+
endpoint = f'/repos/{repo_owner}/{repo_name}/commits/{commit}/{metric_type}'
5472
if pull_request:
5573
base_commit = os.getenv('PULL_BASE_SHA')
5674
head_commit = os.getenv('PULL_PULL_SHA')
5775
endpoint += f'&pull_request={pull_request}&base_commit={base_commit}&head_commit={head_commit}'
58-
59-
commit_note = _get_prow_commit_note('HEAD@{0}')
60-
note += f'\n{commit_note}\n'
61-
endpoint += f'&note={urllib.parse.quote(note)}'
6276
else:
77+
branch = os.getenv('PULL_BASE_REF')
6378
endpoint += f'&branch={branch}'
6479

6580
return endpoint
@@ -68,13 +83,3 @@ def _construct_request_endpoint(metric, note):
6883
def _get_commit_hash(revision):
6984
result = subprocess.run(['git', 'rev-parse', revision], stdout=subprocess.PIPE, check=True)
7085
return result.stdout.decode('utf-8').strip()
71-
72-
73-
def _get_prow_commit_note(revision):
74-
template = 'Head commit (%h) is created by Prow via merging commits: %p.'
75-
result = subprocess.run(
76-
['git', 'show', revision, f'--format={template}', '-s'],
77-
stdout=subprocess.PIPE,
78-
check=True,
79-
)
80-
return result.stdout.decode('utf-8').strip()

ci/fireci/fireciplugins/binary_size.py

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,10 @@
1717
import json
1818
import logging
1919
import os
20-
import re
2120

2221
from fireci import ci_command
22+
from fireci import ci_utils
2323
from fireci import gradle
24-
from fireci import prow_utils
2524
from fireci import uploader
2625

2726
_logger = logging.getLogger('fireci.binary_size')
@@ -34,17 +33,17 @@
3433
)
3534
@click.option(
3635
'--log',
37-
default=prow_utils.prow_job_log_link,
38-
help='The link to the log of the prow job, which runs this coverage check.'
36+
default=ci_utils.ci_log_link,
37+
help='The link to the log of the current job, which runs this size test.'
3938
)
4039
@click.option(
4140
'--metrics-service-url',
42-
envvar='METRICS_SERVICE_URL',
41+
default='https://api.firebase-sdk-health-metrics.com',
4342
help='The URL to the metrics service, which persists data and calculates diff.'
4443
)
4544
@click.option(
4645
'--access-token',
47-
default=prow_utils.gcloud_identity_token,
46+
default=ci_utils.gcloud_identity_token,
4847
help='The access token, used to authorize http requests to the metrics service.'
4948
)
5049
@ci_command()
@@ -58,12 +57,17 @@ def binary_size(pull_request, log, metrics_service_url, access_token):
5857
sdks = ','.join(artifacts)
5958

6059
workdir = 'health-metrics/apk-size'
61-
gradle.run('assemble', '--continue', gradle.P('sdks', sdks), workdir=workdir, check=False)
60+
process = gradle.run('assemble', '--continue', gradle.P('sdks', sdks), workdir=workdir, check=False)
6261

6362
test_results = _measure_aar_sizes(artifacts) + _measure_apk_sizes()
64-
test_report = {'metric': 'BinarySize', 'results': test_results, 'log': log}
63+
test_report = {'sizes': test_results, 'log': log}
6564

66-
uploader.post_report(test_report, metrics_service_url, access_token)
65+
access_token = ci_utils.gcloud_identity_token()
66+
uploader.post_report(test_report, metrics_service_url, access_token, 'size')
67+
68+
if process.returncode != 0:
69+
_logger.error(f'{process.args} failed with error code: {process.returncode}.')
70+
raise click.ClickException('Binary size test failed with above errors.')
6771

6872

6973
def _measure_aar_sizes(artifacts):
@@ -75,7 +79,7 @@ def _measure_aar_sizes(artifacts):
7579

7680
if aar_files:
7781
aar_size = os.path.getsize(aar_files[0])
78-
test_results.append({'sdk': artifact_id, 'type': 'aar', 'value': aar_size})
82+
test_results.append({'sdk': artifact_id, 'type': 'aar', 'size': aar_size})
7983

8084
return test_results
8185

@@ -91,7 +95,7 @@ def _measure_apk_sizes():
9195
apk_type = build_type if abi == 'universal' else f'{build_type} / {abi}'
9296
apk_size = os.path.getsize(apk_file)
9397

94-
test_results.append({'sdk': artifact, 'type': f'apk ({apk_type})', 'value': apk_size})
98+
test_results.append({'sdk': artifact, 'type': f'apk ({apk_type})', 'size': apk_size})
9599

96100
return test_results
97101

ci/fireci/fireciplugins/coverage.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@
1919
import xml.etree.ElementTree as ElementTree
2020

2121
from fireci import ci_command
22+
from fireci import ci_utils
2223
from fireci import gradle
23-
from fireci import prow_utils
2424
from fireci import uploader
2525

2626
_logger = logging.getLogger('fireci.coverage')
@@ -33,35 +33,35 @@
3333
)
3434
@click.option(
3535
'--log',
36-
default=prow_utils.prow_job_log_link,
37-
help='The link to the log of the prow job, which runs this coverage check.'
36+
default=ci_utils.ci_log_link,
37+
help='The link to the log of the current job, which runs this coverage check.'
3838
)
3939
@click.option(
4040
'--metrics-service-url',
41-
envvar='METRICS_SERVICE_URL',
41+
default='https://api.firebase-sdk-health-metrics.com',
4242
help='The URL to the metrics service, which persists data and calculates diff.'
4343
)
4444
@click.option(
4545
'--access-token',
46-
default=prow_utils.gcloud_identity_token,
46+
default=ci_utils.gcloud_identity_token,
4747
help='The access token, used to authorize http requests to the metrics service.'
4848
)
4949
@ci_command()
5050
def coverage(pull_request, log, metrics_service_url, access_token):
5151
"""Produces and uploads code coverage reports."""
5252

5353
coverage_task = 'checkCoverageChanged' if pull_request else 'checkCoverage'
54-
gradle.run(coverage_task, '--continue', check=False)
54+
process = gradle.run(coverage_task, '--continue', check=False)
5555

5656
test_results = _parse_xml_reports()
57-
test_report = {'metric': 'Coverage', 'results': test_results, 'log': log}
57+
test_report = {'coverages': test_results, 'log': log}
5858

59-
note = '''
60-
HTML coverage reports can be produced locally with `./gradlew <product>:checkCoverage`.
61-
Report files are located at `<product-build-dir>/reports/jacoco/`.
62-
'''
59+
access_token = ci_utils.gcloud_identity_token()
60+
uploader.post_report(test_report, metrics_service_url, access_token, 'coverage')
6361

64-
uploader.post_report(test_report, metrics_service_url, access_token, note=note)
62+
if process.returncode != 0:
63+
_logger.error(f'{process.args} failed with error code: {process.returncode}.')
64+
raise click.ClickException('Coverage test failed with above errors.')
6565

6666

6767
def _parse_xml_reports():
@@ -74,12 +74,12 @@ def _parse_xml_reports():
7474
sdk = re.search(r'([^/]*)\.xml', xml_report).group(1)
7575
report = ElementTree.parse(xml_report).getroot()
7676
sdk_coverage = _calculate_coverage(report)
77-
test_results.append({'sdk': sdk, 'type': '', 'value': sdk_coverage})
77+
test_results.append({'sdk': sdk, 'filename': '', 'coverage': sdk_coverage})
7878

7979
for source_file in report.findall('.//sourcefile'):
8080
file_name = source_file.attrib['name']
8181
file_coverage = _calculate_coverage(source_file)
82-
test_results.append({'sdk': sdk, 'type': file_name, 'value': file_coverage})
82+
test_results.append({'sdk': sdk, 'filename': file_name, 'coverage': file_coverage})
8383

8484
return test_results
8585

ci/fireci/fireciplugins/macrobenchmark.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@
3030
from google.cloud import storage
3131

3232
from fireci import ci_command
33+
from fireci import ci_utils
3334
from fireci.dir_utils import chdir
34-
from fireci import prow_utils
3535
from fireci import uploader
3636

3737
_logger = logging.getLogger('fireci.macrobenchmark')
@@ -113,9 +113,12 @@ async def _post_processing(results):
113113
if not isinstance(result, Exception):
114114
measurements.extend(result)
115115

116-
metrics_service_url = os.getenv('METRICS_SERVICE_URL')
117-
access_token = prow_utils.gcloud_identity_token()
118-
uploader.post_report(measurements, metrics_service_url, access_token, metric='macrobenchmark')
116+
log = ci_utils.ci_log_link()
117+
test_report = {'benchmarks': measurements, 'log': log}
118+
119+
metrics_service_url = 'https://api.firebase-sdk-health-metrics.com'
120+
access_token = ci_utils.gcloud_identity_token()
121+
uploader.post_report(test_report, metrics_service_url, access_token, 'macrobenchmark')
119122

120123
# Raise exceptions for failed measurements
121124
if any(map(lambda x: isinstance(x, Exception), results)):

health-metrics/apk-size/gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,4 @@
1515
android.useAndroidX=true
1616
android.enableJetifier=true
1717

18-
org.gradle.jvmargs=-Xmx8g -XX:MaxPermSize=8g
18+
org.gradle.jvmargs=-Xmx4g -XX:MaxPermSize=4g

0 commit comments

Comments
 (0)