This is an automated email from the ASF dual-hosted git repository. fgerlits pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/nifi-minifi-cpp.git
commit 4356e902f41ea492ff88e72b0f4d2d69182a02e8 Author: Gabor Gyimesi <[email protected]> AuthorDate: Wed Jun 14 18:03:24 2023 +0200 MINIFICPP-2130 Custom cache eviction strategy for GitHub Actions Signed-off-by: Ferenc Gerlits <[email protected]> This closes #1591 --- .github/workflows/ci.yml | 66 +++++++++--- .github/workflows/clear-actions-cache.yml | 18 ++++ github_scripts/github_actions_cache_cleanup.py | 117 +++++++++++++++++++++ .../github_actions_cache_cleanup_tests.py | 80 ++++++++++++++ github_scripts/requirements.txt | 1 + run_flake8.sh | 2 +- 6 files changed, 268 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 95fb35f49..9480bf81c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,8 +16,8 @@ jobs: steps: - id: checkout uses: actions/checkout@v3 - - id: cache - uses: actions/cache@v3 + - name: cache restore + uses: actions/cache/restore@v3 with: path: ${{ env.CCACHE_DIR }} key: macos-xcode-ccache-${{github.ref}}-${{github.sha}} @@ -43,6 +43,12 @@ jobs: export CPPFLAGS="-I/usr/local/opt/flex/include" # CPPFLAGS are not recognized by cmake, so we have to force them to CFLAGS and CXXFLAGS to have flex 2.6 working ./bootstrap.sh -e -t && cd build && cmake -DCMAKE_BUILD_TYPE=Release -DCI_BUILD=ON -DCMAKE_C_FLAGS="${CPPFLAGS} ${CFLAGS}" -DCMAKE_CXX_FLAGS="${CPPFLAGS} ${CXXFLAGS}" -DENABLE_PYTHON_SCRIPTING=ON -DENABLE_LUA_SCRIPTING=ON -DENABLE_SQL=ON -DUSE_REAL_ODBC_TEST_DRIVER=ON -DENABLE_AZURE=ON -DENABLE_GCP=ON -DCMAKE_VERBOSE_MAKEFILE=ON -DCMAKE_RULE_MESSAGES=OFF -DSTRICT_GSL_CHECKS=AUDIT -DFAIL_ON_WARNINGS=ON .. && cmake --build . --parallel 4 + - name: cache save + uses: actions/cache/save@v3 + if: always() + with: + path: ${{ env.CCACHE_DIR }} + key: macos-xcode-ccache-${{github.ref}}-${{github.sha}} - name: test id: test run: | @@ -74,10 +80,8 @@ jobs: run: git config --system core.longpaths true - id: checkout uses: actions/checkout@v3 - - name: Run sccache-cache - uses: mozilla-actions/[email protected] - - name: sccache cache - uses: actions/cache@v3 + - name: sccache cache restore + uses: actions/cache/restore@v3 with: path: ~/AppData/Local/Mozilla/sccache/cache key: ${{ runner.os }}-sccache-${{ github.ref }}-${{ github.sha }} @@ -85,6 +89,8 @@ jobs: ${{ runner.os }}-sccache-${{ github.ref }} ${{ runner.os }}-sccache-refs/heads/main ${{ runner.os }}-sccache + - name: Run sccache-cache + uses: mozilla-actions/[email protected] - name: Install ninja-build tool uses: seanmiddleditch/gha-setup-ninja@v3 - name: Set up Python @@ -107,6 +113,12 @@ jobs: win_build_vs.bat ..\b /64 /CI /S /A /PDH /SPLUNK /GCP /ELASTIC /K /L /R /Z /N /RO /PR /PYTHON_SCRIPTING /LUA_SCRIPTING /MQTT /SCCACHE /NINJA sccache --show-stats shell: cmd + - name: sccache cache save + uses: actions/cache/save@v3 + if: always() + with: + path: ~/AppData/Local/Mozilla/sccache/cache + key: ${{ runner.os }}-sccache-${{ github.ref }}-${{ github.sha }} - name: test run: cd ..\b && ctest --timeout 300 --parallel %NUMBER_OF_PROCESSORS% -C Release --output-on-failure shell: cmd @@ -120,8 +132,8 @@ jobs: steps: - id: checkout uses: actions/checkout@v3 - - id: cache - uses: actions/cache@v3 + - name: cache restore + uses: actions/cache/restore@v3 with: path: ~/.ccache key: ubuntu-20.04-ccache-${{github.ref}}-${{github.sha}} @@ -144,6 +156,12 @@ jobs: -DENABLE_AZURE=OFF -DENABLE_SPLUNK=OFF -DENABLE_GCP=OFF -DENABLE_PROCFS=OFF -DENABLE_BUSTACHE=ON -DENABLE_PCAP=ON -DENABLE_JNI=ON -DENABLE_SFTP=ON \ -DENABLE_LUA_SCRIPTING=OFF -DENABLE_PYTHON_SCRIPTING=OFF -DENABLE_MQTT=OFF -DENABLE_ELASTICSEARCH=OFF -DENABLE_KUBERNETES=OFF -DENABLE_OPC=OFF .. make -j$(nproc) VERBOSE=1 + - name: cache save + uses: actions/cache/save@v3 + if: always() + with: + path: ~/.ccache + key: ubuntu-20.04-ccache-${{github.ref}}-${{github.sha}} - name: test id: test run: | @@ -171,8 +189,8 @@ jobs: steps: - id: checkout uses: actions/checkout@v3 - - id: cache - uses: actions/cache@v3 + - name: cache restore + uses: actions/cache/restore@v3 with: path: ~/.ccache key: ubuntu-20.04-all-clang-ccache-${{github.ref}}-${{github.sha}} @@ -204,6 +222,12 @@ jobs: -DENABLE_USB_CAMERA=ON -DENABLE_PYTHON_SCRIPTING=ON -DENABLE_LUA_SCRIPTING=ON -DENABLE_KUBERNETES=ON -DENABLE_GCP=ON -DENABLE_PROCFS=ON -DENABLE_PROMETHEUS=ON -DENABLE_ELASTICSEARCH=ON \ -DCMAKE_EXPORT_COMPILE_COMMANDS=ON .. cmake --build . --parallel $(nproc) + - id: cache_save + uses: actions/cache/save@v3 + if: always() + with: + path: ~/.ccache + key: ubuntu-20.04-all-clang-ccache-${{github.ref}}-${{github.sha}} - name: test id: test run: | @@ -254,8 +278,8 @@ jobs: steps: - id: checkout uses: actions/checkout@v3 - - id: cache - uses: actions/cache@v3 + - name: cache restore + uses: actions/cache/restore@v3 with: path: ~/.ccache key: centos-ccache-${{github.ref}}-${{github.sha}} @@ -276,6 +300,12 @@ jobs: -DENABLE_OPENCV=ON -DENABLE_OPENWSMAN=ON -DENABLE_OPS=ON -DENABLE_SENSORS=ON -DENABLE_SQL=ON -DENABLE_SYSTEMD=ON \ -DENABLE_USB_CAMERA=ON -DENABLE_PYTHON_SCRIPTING=ON -DENABLE_LUA_SCRIPTING=ON -DENABLE_KUBERNETES=ON -DENABLE_GCP=ON -DENABLE_PROCFS=ON -DENABLE_PROMETHEUS=ON \ -DENABLE_ELASTICSEARCH=ON -DDOCKER_SKIP_TESTS=OFF -DDOCKER_BUILD_ONLY=ON -DDOCKER_CCACHE_DUMP_LOCATION=$HOME/.ccache .. && make centos-test + - name: cache save + uses: actions/cache/save@v3 + if: always() + with: + path: ~/.ccache + key: centos-ccache-${{github.ref}}-${{github.sha}} - id: test run: | # Set core file size limit to 1GiB @@ -309,8 +339,8 @@ jobs: steps: - id: checkout uses: actions/checkout@v3 - - id: cache - uses: actions/cache@v3 + - name: cache restore + uses: actions/cache/restore@v3 with: path: ~/.ccache key: docker-ccache-${{github.ref}}-${{github.sha}} @@ -324,6 +354,12 @@ jobs: cd build cmake ${DOCKER_CMAKE_FLAGS} .. make docker + - name: cache save + uses: actions/cache/save@v3 + if: always() + with: + path: ~/.ccache + key: docker-ccache-${{github.ref}}-${{github.sha}} - name: Save docker image run: cd build && docker save -o minifi_docker.tar apacheminificpp:$(grep CMAKE_PROJECT_VERSION:STATIC CMakeCache.txt | cut -d "=" -f2) - name: Upload artifact @@ -331,7 +367,7 @@ jobs: with: name: minifi_docker path: build/minifi_docker.tar - docker_tests_q1: + docker_tests: name: "Docker integration tests" needs: docker_build runs-on: ubuntu-20.04 diff --git a/.github/workflows/clear-actions-cache.yml b/.github/workflows/clear-actions-cache.yml new file mode 100644 index 000000000..65eb3efbf --- /dev/null +++ b/.github/workflows/clear-actions-cache.yml @@ -0,0 +1,18 @@ +name: "MiNiFi-CPP Github Actions Cache Eviction" +on: + schedule: + - cron: '*/30 * * * *' + workflow_dispatch: +jobs: + ubuntu_20_04: + name: "ubuntu-20.04" + runs-on: ubuntu-20.04 + steps: + - id: checkout + uses: actions/checkout@v3 + - name: clear cache + run: | + python3 -m venv github_env + source github_env/bin/activate + pip install -r github_scripts/requirements.txt + python3 github_scripts/github_actions_cache_cleanup.py -t ${{github.token}} -r ${{github.repository}} diff --git a/github_scripts/github_actions_cache_cleanup.py b/github_scripts/github_actions_cache_cleanup.py new file mode 100755 index 000000000..0f4d67970 --- /dev/null +++ b/github_scripts/github_actions_cache_cleanup.py @@ -0,0 +1,117 @@ +#!/bin/python3 + +import requests +import re +import argparse +import logging +from typing import Dict, List + + +logging.basicConfig(level=logging.INFO) + + +class GithubRequestSender: + def __init__(self, token: str, repository: str): + self.headers = { + 'Accept': 'application/vnd.github+json', + 'Authorization': 'Bearer ' + token, + 'X-GitHub-Api-Version': '2022-11-28' + } + self.repository = repository + + def _send_get_request(self, url: str): + response = requests.get(url, headers=self.headers) + response.raise_for_status() + + return response.json() + + def _send_delete_request(self, url: str, params: Dict[str, str]): + response = requests.delete(url, headers=self.headers, params=params) + response.raise_for_status() + + def list_open_pull_requests(self): + url = "https://api.github.com/repos/" + self.repository + "/pulls?state=open" + return self._send_get_request(url) + + def list_caches(self): + url = "https://api.github.com/repos/" + self.repository + "/actions/caches" + return self._send_get_request(url) + + def delete_cache(self, key: str): + url = "https://api.github.com/repos/" + self.repository + "/actions/caches" + self._send_delete_request(url, params={"key": key}) + + +class CacheEntry: + def __init__(self, id: int, key: str): + self.id = id + self.key = key + + def __str__(self) -> str: + return "{id: " + str(self.id) + ", key: " + self.key + "}" + + def __repr__(self): + return str(self) + + +class GithubActionsCacheCleaner: + def __init__(self, token: str, repository: str): + self.github_request_sender = GithubRequestSender(token, repository) + + def _list_open_pr_ids(self) -> List[str]: + open_tickets = [] + for request in self.github_request_sender.list_open_pull_requests(): + open_tickets.append(request['number']) + return open_tickets + + def _get_cache_entries(self) -> List[CacheEntry]: + entries = [] + json_result = self.github_request_sender.list_caches() + for json_entry in json_result["actions_caches"]: + entries.append(CacheEntry(int(json_entry["id"]), json_entry["key"])) + return entries + + def _is_pr_already_closed(self, entry, open_tickets): + match = re.search(r'refs/pull/([\d]+)/merge', entry.key) + return match and match[1] not in open_tickets + + def _remove_non_latest_branch_caches(self, entry: CacheEntry, latest_branch_cache_map: Dict[str, CacheEntry], removable_entries: List[str]): + cache_mapping_key = "-".join(entry.key.split("-")[0:-1]) + if cache_mapping_key not in latest_branch_cache_map: + latest_branch_cache_map[cache_mapping_key] = entry + else: + if latest_branch_cache_map[cache_mapping_key].id < entry.id: + removable_entries.append(latest_branch_cache_map[cache_mapping_key].key) + latest_branch_cache_map[cache_mapping_key] = entry + else: + removable_entries.append(entry.key) + + def _get_removable_cache_entries(self) -> List[str]: + removable_entries = [] + latest_branch_cache_map = dict() + open_prs = self._list_open_pr_ids() + for entry in self._get_cache_entries(): + if self._is_pr_already_closed(entry, open_prs): + removable_entries.append(entry.key) + continue + + self._remove_non_latest_branch_caches(entry, latest_branch_cache_map, removable_entries) + + return removable_entries + + def _remove_cache_entries(self, entries_to_remove: List[str]): + for key in entries_to_remove: + logging.info('Removing cache entry: %s', key) + self.github_request_sender.delete_cache(key) + + def remove_obsolete_cache_entries(self): + self._remove_cache_entries(self._get_removable_cache_entries()) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(prog='Github Actions Cache Cleaner', description='Cleans up obsolete cache entries of github actions') + parser.add_argument('-r', '--repository', help='The name of the repository in <owner>/<repository> format', required=True) + parser.add_argument('-t', '--token', help='Github token for API access', required=True) + args = parser.parse_args() + cache_cleaner = GithubActionsCacheCleaner(args.token, args.repository) + cache_cleaner.remove_obsolete_cache_entries() diff --git a/github_scripts/github_actions_cache_cleanup_tests.py b/github_scripts/github_actions_cache_cleanup_tests.py new file mode 100755 index 000000000..6d33ececb --- /dev/null +++ b/github_scripts/github_actions_cache_cleanup_tests.py @@ -0,0 +1,80 @@ +#!/bin/python3 + +import unittest +from unittest.mock import MagicMock +from github_actions_cache_cleanup import GithubActionsCacheCleaner + + +class TestGithubActionsCacheCleaner(unittest.TestCase): + def create_mock_github_request_sender(self): + mock = MagicMock() + mock.list_open_pull_requests = MagicMock() + open_pull_requests = [ + { + "number": "227", + "title": "MINIFICPP-13712 TEST1", + }, + { + "number": "228", + "title": "MINIFICPP-9999 TEST2", + }, + { + "number": "229", + "title": "MINIFICPP-123 TEST3", + } + ] + mock.list_open_pull_requests.return_value = open_pull_requests + caches = { + "actions_caches": [ + { + "id": "999", + "key": "macos-xcode-ccache-refs/pull/226/merge-6c8d283f5bc894af8dfc295e5976a5f154753123", + }, + { + "id": "11111", + "key": "ubuntu-20.04-ccache-refs/pull/227/merge-9d6d283f5bc894af8dfc295e5976a5f1b46649c4", + }, + { + "id": "11112", + "key": "ubuntu-20.04-ccache-refs/pull/227/merge-1d6d283f5bc894af8dfc295e5976a5f154753487", + }, + { + "id": "12345", + "key": "macos-xcode-ccache-refs/pull/227/merge-2d6d283f5bc894af8dfc295e5976a5f154753536", + }, + { + "id": "22221", + "key": "macos-xcode-ccache-refs/heads/MINIFICPP-9999-9d5e183f5bc894af8dfc295e5976a5f1b4664456", + }, + { + "id": "22222", + "key": "macos-xcode-ccache-refs/heads/MINIFICPP-9999-8f4d283f5bc894af8dfc295e5976a5f1b4664123", + }, + { + "id": "44444", + "key": "ubuntu-20.04-all-clang-ccache-refs/heads/main-1d4d283f5bc894af8dfc295e5976a5f1b4664456", + }, + { + "id": "55555", + "key": "ubuntu-20.04-all-clang-ccache-refs/heads/main-2f4d283f5bc894af8dfc295e5976a5f1b4664567", + } + ] + } + mock.list_caches = MagicMock() + mock.list_caches.return_value = caches + mock.delete_cache = MagicMock() + return mock + + def test_cache_cleanup(self): + cleaner = GithubActionsCacheCleaner("mytoken", "githubuser/nifi-minifi-cpp") + cleaner.github_request_sender = self.create_mock_github_request_sender() + cleaner.remove_obsolete_cache_entries() + self.assertEqual(set([call[0][0] for call in cleaner.github_request_sender.delete_cache.call_args_list]), + {"macos-xcode-ccache-refs/pull/226/merge-6c8d283f5bc894af8dfc295e5976a5f154753123", + "macos-xcode-ccache-refs/heads/MINIFICPP-9999-9d5e183f5bc894af8dfc295e5976a5f1b4664456", + "ubuntu-20.04-ccache-refs/pull/227/merge-9d6d283f5bc894af8dfc295e5976a5f1b46649c4", + "ubuntu-20.04-all-clang-ccache-refs/heads/main-1d4d283f5bc894af8dfc295e5976a5f1b4664456"}) + + +if __name__ == '__main__': + unittest.main() diff --git a/github_scripts/requirements.txt b/github_scripts/requirements.txt new file mode 100644 index 000000000..2c24336eb --- /dev/null +++ b/github_scripts/requirements.txt @@ -0,0 +1 @@ +requests==2.31.0 diff --git a/run_flake8.sh b/run_flake8.sh index b0577c428..e1a409f09 100755 --- a/run_flake8.sh +++ b/run_flake8.sh @@ -19,4 +19,4 @@ set -euo pipefail directory=${1:-.} -flake8 --exclude venv,thirdparty,build,cmake-build-* --builtins log,REL_SUCCESS,REL_FAILURE,raw_input --ignore E501,W503 --per-file-ignores="steps.py:F811" "${directory}" +flake8 --exclude venv,thirdparty,build,cmake-build-*,github_env --builtins log,REL_SUCCESS,REL_FAILURE,raw_input --ignore E501,W503 --per-file-ignores="steps.py:F811" "${directory}"
