Skip to content

Commit 0934d62

Browse files
authored
Merge branch 'master' into fix-negative-unsigned
2 parents 39d0f05 + 77f747b commit 0934d62

File tree

232 files changed

+9645
-1460
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

232 files changed

+9645
-1460
lines changed

.github/actions/install_ov_wheels/action.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ runs:
3333
Write-Host "Looking for free-threaded Python wheel: $wheel-*cp$pyVersion*t*.whl"
3434
$wheelPath = Get-ChildItem -Path ${{ inputs.wheels-dir-path }} -Filter "$wheel-*cp$pyVersion*t*.whl" | Select-Object -First 1
3535
} else {
36-
$wheelPath = Get-ChildItem -Path ${{ inputs.wheels-dir-path }} -Filter "$wheel-*cp$pyVersion*.whl" | Where-Object { $_.Name -notlike "*cp$pyVersion*t.whl" } | Select-Object -First 1
36+
Write-Host "Looking for non-free-threaded Python wheel: $wheel-*cp$pyVersion*.whl"
37+
$wheelPath = Get-ChildItem -Path ${{ inputs.wheels-dir-path }} -Filter "$wheel-*cp$pyVersion*.whl" | Where-Object { $_.Name -notlike "*cp$pyVersion*t*.whl" } | Select-Object -First 1
3738
}
3839
3940
Write-Host "Wheel path: $($wheelPath)"
@@ -64,8 +65,10 @@ runs:
6465
6566
# free-threading Python wheels have 'cpXYt' in their names
6667
if [ "${{ inputs.free-threaded-python }}" == "true" ]; then
68+
echo "Looking for free-threaded Python wheel: $wheel-*cp$pyVersion*t*.whl"
6769
wheel_path=$(find ${{ inputs.wheels-dir-path }} -name "$wheel-*cp${py_version}t*.whl")
6870
else
71+
echo "Looking for non-free-threaded Python wheel: $wheel-*cp$pyVersion*.whl"
6972
wheel_path=$(find ${{ inputs.wheels-dir-path }} -name "$wheel-*cp$py_version*.whl" ! -name "*cp${py_version}t*.whl")
7073
fi
7174

.github/scripts/workflow_rerun/log_analyzer.py

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,14 @@ class ErrorData(TypedDict):
2323

2424
class LogAnalyzer:
2525
def __init__(self,
26-
path_to_log_archive: Path,
26+
path_to_logs: Path,
2727
path_to_errors_file: Path) -> None:
28-
self._path_to_log_archive = path_to_log_archive
2928
self._path_to_errors_file = path_to_errors_file
3029

3130
self._errors_to_look_for: list[ErrorData] = []
3231
self._collect_errors_to_look_for()
3332

34-
self._log_dir = tempfile.TemporaryDirectory().name
33+
self._log_dir = path_to_logs
3534

3635
self._log_files: list[LogFile] = []
3736
self._collect_log_files()
@@ -48,7 +47,7 @@ def _collect_errors_to_look_for(self) -> None:
4847
errors_data = json.load(errors_file)
4948
for error_data in errors_data:
5049
self._errors_to_look_for.append(
51-
ErrorData(error_text=error_data['error_text'],
50+
ErrorData(error_text=error_data['error_text'],
5251
ticket=error_data['ticket'])
5352
)
5453

@@ -68,14 +67,10 @@ def _collect_log_files(self) -> None:
6867
> Job_name_2
6968
...
7069
...
71-
70+
7271
We need to only analyze the `*.txt` files
7372
"""
7473

75-
with ZipFile(file=self._path_to_log_archive,
76-
mode='r') as zip_file:
77-
zip_file.extractall(self._log_dir)
78-
7974
for _file in Path(self._log_dir).iterdir():
8075
if _file.is_dir():
8176
for log_file in _file.iterdir():
@@ -107,9 +102,9 @@ def _clean_up_string(string: str) -> str:
107102
"""
108103
Replaces special characters with spaces in the string, strips it from leading and following spaces,
109104
and lowers it
110-
105+
111106
for "Could not resolve host: github.com" returns "could not resolve host github com"
112-
107+
113108
This cleanup is applied to both errors to look for and logs themselves for matching
114109
"""
115110
return re.sub(r'[^A-Za-z0-9]+', ' ', string).lower().strip()

.github/scripts/workflow_rerun/log_collector.py

Lines changed: 91 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,103 @@
22
# SPDX-License-Identifier: Apache-2.0
33

44
from pathlib import Path
5+
from zipfile import ZipFile
6+
import tempfile
57

68
import requests
79
from github.WorkflowRun import WorkflowRun
810
from workflow_rerun.constants import GITHUB_TOKEN, LOGGER
911

1012

1113
def collect_logs_for_run(run: WorkflowRun,
12-
log_archive_path: Path) -> Path:
14+
logs_dir: Path) -> Path:
1315
"""
14-
Collects log archive for a pipeline
16+
Downloads logs of a given Workflow Run,
17+
saves them to a specified path, and returns that path.
18+
19+
We don't need successful job logs, so we remove them.
20+
We could've just downloaded logs for failed jobs only,
21+
but when you download all logs from a workflow run,
22+
GitHub includes "system.txt" files for each job, which can also
23+
contain errors on which we might want to trigger rerun.
24+
25+
Example log archive structure:
26+
.
27+
├── 10_Pytorch Layer Tests _ PyTorch Layer Tests.txt
28+
├── 11_CPU functional tests _ CPU functional tests.txt
29+
├── 12_C++ unit tests _ C++ unit tests.txt
30+
├── 13_OpenVINO tokenizers extension _ OpenVINO tokenizers extension.txt
31+
├── C++ unit tests _ C++ unit tests
32+
│ └── system.txt
33+
├── CPU functional tests _ CPU functional tests
34+
│ └── system.txt
35+
├── OpenVINO tokenizers extension _ OpenVINO tokenizers extension
36+
│ └── system.txt
37+
├── Pytorch Layer Tests _ PyTorch Layer Tests
38+
└── system.txt
39+
40+
Sometimes though, directories contain log files for each individual step,
41+
IN ADDITION to the full log in root of the directory:
42+
.
43+
├── 1_Build.txt
44+
└── Build
45+
├── 13_Upload build logs.txt
46+
├── 1_Set up job.txt
47+
├── 24_Post Clone vcpkg.txt
48+
├── 25_Post Clone OpenVINO.txt
49+
├── 26_Stop containers.txt
50+
├── 27_Complete job.txt
51+
├── 2_Initialize containers.txt
52+
├── 3_Clone OpenVINO.txt
53+
├── 4_Get VCPKG version and put it into GitHub ENV.txt
54+
├── 5_Init submodules for non vcpkg dependencies.txt
55+
├── 6_Clone vcpkg.txt
56+
├── 7_System info.txt
57+
├── 8_Build vcpkg.txt
58+
├── 9_CMake - configure.txt
59+
└── system.txt
60+
61+
In that case, we need only 'system.txt' file from each directory
1562
"""
16-
with open(file=log_archive_path,
17-
mode='wb') as log_archive:
18-
LOGGER.info(f'STARTED LOG COLLECTION FOR {run.id} IN {log_archive_path}')
19-
# PyGitHub does not expose the "/repos/{owner}/{repo}/actions/runs/{run_id}/logs" endpoint so we have to use requests
20-
log_archive.write(requests.get(url=run.logs_url,
21-
headers={'Authorization': f'Bearer {GITHUB_TOKEN}'}).content)
22-
LOGGER.info(f'COLLECTED LOGS FOR {run.id} IN {log_archive_path}')
23-
24-
return log_archive_path
63+
# Get failed jobs
64+
failed_jobs = [job for job in run.jobs() if job.conclusion == 'failure']
65+
LOGGER.info(f'FAILED JOBS: {[job.name for job in failed_jobs]}')
66+
67+
with tempfile.NamedTemporaryFile(suffix='.zip') as temp_file:
68+
log_archive_path = Path(temp_file.name)
69+
70+
# Download logs archive
71+
with open(file=log_archive_path,
72+
mode='wb') as log_archive:
73+
LOGGER.info(f'DOWNLOADING LOGS FOR RUN ID {run.id}')
74+
# PyGitHub does not expose the "/repos/{owner}/{repo}/actions/runs/{run_id}/logs" endpoint so we have to use requests
75+
LOGGER.debug(f'Downloading logs from {run.logs_url}')
76+
response = requests.get(url=run.logs_url,
77+
headers={'Authorization': f'Bearer {GITHUB_TOKEN}'})
78+
response.raise_for_status()
79+
log_archive.write(response.content)
80+
81+
# Unpack it
82+
with tempfile.TemporaryDirectory() as temp_dir:
83+
logs_temp_dir = Path(temp_dir)
84+
85+
with ZipFile(file=log_archive_path,
86+
mode='r') as zip_file:
87+
zip_file.extractall(logs_temp_dir)
88+
89+
# Traverse the unpacked logs to find the ones of failed jobs
90+
for job in failed_jobs:
91+
job_filename = job.name.replace('/', '_')
92+
LOGGER.debug(f'Looking for failed job logs with filename: {job_filename}')
93+
94+
for p in logs_temp_dir.iterdir():
95+
# Move failed jobs' logs to the final destination
96+
if p.is_dir() and p.name == job_filename:
97+
LOGGER.debug(f'Keeping system.txt from directory {p} for failed job {job.name}')
98+
(p / 'system.txt').rename(logs_dir / f'{job_filename}__system.txt')
99+
elif p.is_file() and p.name.endswith(f'{job_filename}.txt'):
100+
LOGGER.debug(f'Keeping file {p} for failed job {job.name}')
101+
p.rename(logs_dir / p.name)
102+
103+
LOGGER.info(f'COLLECTED LOGS FOR {run.id} IN {logs_dir}')
104+
return logs_dir
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
PyGithub==2.8.1
2+
requests==2.32.4
3+
psycopg2-binary==2.9.9

.github/scripts/workflow_rerun/rerunner.py

Lines changed: 35 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -77,39 +77,39 @@ def record_rerun_to_db(repository_full_name: str, run_id: int, ticket_number: in
7777
LOGGER.info(f'THERE ARE {run.run_attempt} ATTEMPTS ALREADY. NOT CHECKING LOGS AND NOT RETRIGGERING. EXITING')
7878
sys.exit(0)
7979

80-
log_archive_path = Path(tempfile.NamedTemporaryFile(suffix='.zip').name)
81-
82-
collect_logs_for_run(
83-
run=run,
84-
log_archive_path=log_archive_path,
85-
)
86-
87-
log_analyzer = LogAnalyzer(
88-
path_to_log_archive=log_archive_path,
89-
path_to_errors_file=errors_file
90-
)
91-
log_analyzer.analyze()
92-
93-
if log_analyzer.found_matching_error:
94-
LOGGER.info(f'FOUND MATCHING ERROR, RETRIGGERING {run.html_url}')
95-
if is_dry_run:
96-
LOGGER.info(f'RUNNING IN DRY RUN MODE, NOT RETRIGGERING, EXITING')
97-
sys.exit(0)
98-
99-
# PyGitHub does not expose the "/repos/{owner}/{repo}/actions/runs/RUN_ID/rerun-failed-jobs" endpoint
100-
# so we have to use requests
101-
response = requests.post(url=f'https://api.github.com/repos/{repository_name}/actions/runs/{run_id}/rerun-failed-jobs',
102-
headers={'Authorization': f'Bearer {GITHUB_TOKEN}'})
103-
status = response.status_code == 201
104-
105-
if status:
106-
LOGGER.info(f'RUN RETRIGGERED SUCCESSFULLY: {run.html_url}')
107-
record_rerun_to_db(repository_name, run_id,
108-
log_analyzer.found_error_ticket)
80+
with tempfile.TemporaryDirectory() as temp_dir:
81+
logs_dir = Path(temp_dir)
82+
collect_logs_for_run(
83+
run=run,
84+
logs_dir=logs_dir,
85+
)
86+
87+
log_analyzer = LogAnalyzer(
88+
path_to_logs=logs_dir,
89+
path_to_errors_file=errors_file
90+
)
91+
log_analyzer.analyze()
92+
93+
if log_analyzer.found_matching_error:
94+
LOGGER.info(f'FOUND MATCHING ERROR, RETRIGGERING {run.html_url}')
95+
if is_dry_run:
96+
LOGGER.info(f'RUNNING IN DRY RUN MODE, NOT RETRIGGERING, EXITING')
97+
sys.exit(0)
98+
99+
# PyGitHub does not expose the "/repos/{owner}/{repo}/actions/runs/RUN_ID/rerun-failed-jobs" endpoint
100+
# so we have to use requests
101+
response = requests.post(url=f'https://api.github.com/repos/{repository_name}/actions/runs/{run_id}/rerun-failed-jobs',
102+
headers={'Authorization': f'Bearer {GITHUB_TOKEN}'})
103+
status = response.status_code == 201
104+
105+
if status:
106+
LOGGER.info(f'RUN RETRIGGERED SUCCESSFULLY: {run.html_url}')
107+
record_rerun_to_db(repository_name, run_id,
108+
log_analyzer.found_error_ticket)
109+
else:
110+
LOGGER.info(f'RUN WAS NOT RETRIGGERED, SEE ABOVE')
111+
112+
# "status" is True (which is 1) if everything is ok, False (which is 0) otherwise
113+
sys.exit(not status)
109114
else:
110-
LOGGER.info(f'RUN WAS NOT RETRIGGERED, SEE ABOVE')
111-
112-
# "status" is True (which is 1) if everything is ok, False (which is 0) otherwise
113-
sys.exit(not status)
114-
else:
115-
LOGGER.info(f'NO ERROR WAS FOUND, NOT RETRIGGERING')
115+
LOGGER.info(f'NO ERROR WAS FOUND, NOT RETRIGGERING')

.github/scripts/workflow_rerun/tests/data/log_archive_with_error.zip

Lines changed: 0 additions & 3 deletions
This file was deleted.

.github/scripts/workflow_rerun/tests/data/log_archive_wo_error.zip

Lines changed: 0 additions & 3 deletions
This file was deleted.
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
<log_stub_with_example_error>
2+
2026-01-12T09:16:22.1413136Z [setupvars.sh] OpenVINO environment initialized
3+
2026-01-12T09:16:22.1423345Z Python detected LC_CTYPE=C: LC_CTYPE coerced to C.UTF-8 (set another locale or PYTHONCOERCECLOCALE=0 to disable this locale coercion behavior).
4+
2026-01-12T09:16:22.6461484Z ============================= test session starts ==============================
5+
2026-01-12T09:16:22.6462288Z platform linux -- Python 3.11.14, pytest-9.0.2, pluggy-1.6.0
6+
2026-01-12T09:16:22.6462837Z rootdir: /__w/openvino/openvino
7+
2026-01-12T09:16:22.6463228Z plugins: xdist-3.8.0
8+
2026-01-12T09:16:22.6463600Z collected 175 items
9+
2026-01-12T09:16:22.6463808Z
10+
2026-01-12T09:16:56.4433071Z install/tests/smoke_tests/test_benchmark_app.py ........................ [ 13%]
11+
2026-01-12T09:18:38.0985810Z ............................................F........................... [ 54%]
12+
2026-01-12T09:19:12.5821675Z ...................................... [ 76%]
13+
2026-01-12T09:19:17.9814654Z install/tests/smoke_tests/test_classification_sample_async.py .......... [ 82%]
14+
2026-01-12T09:19:19.2133678Z .. [ 83%]
15+
2026-01-12T09:19:24.4414972Z install/tests/smoke_tests/test_hello_classification.py ......... [ 88%]
16+
2026-01-12T09:19:26.7063811Z install/tests/smoke_tests/test_hello_nv12_input_classification.py ...... [ 92%]
17+
2026-01-12T09:19:26.7068957Z [ 92%]
18+
2026-01-12T09:19:26.9441675Z install/tests/smoke_tests/test_hello_query_device.py .. [ 93%]
19+
2026-01-12T09:19:28.7302791Z install/tests/smoke_tests/test_hello_reshape_ssd.py .. [ 94%]
20+
2026-01-12T09:19:29.6995193Z install/tests/smoke_tests/test_model_creation_sample.py ...... [ 97%]
21+
2026-01-12T09:19:50.6909298Z install/tests/smoke_tests/test_sync_benchmark.py .. [ 98%]
22+
2026-01-12T09:20:11.8235515Z install/tests/smoke_tests/test_throughput_benchmark.py .. [100%]
23+
2026-01-12T09:20:11.8236263Z
24+
2026-01-12T09:20:11.8236476Z =================================== FAILURES ===================================
25+
2026-01-12T09:20:11.8237183Z _________________________ test_dynamic_shape[CPU-C++] __________________________
26+
2026-01-12T09:20:11.8237631Z
27+
2026-01-12T09:20:11.8238280Z self = <HTTPSConnection(host='media.githubusercontent.com', port=443) at 0x7feca05d1960>
28+
2026-01-12T09:20:11.8238875Z
29+
2026-01-12T09:20:11.8239077Z def _new_conn(self) -> socket.socket:
30+
2026-01-12T09:20:11.8239720Z """Establish a socket connection and set nodelay settings on it.
31+
2026-01-12T09:20:11.8240343Z
32+
2026-01-12T09:20:11.8240679Z :return: New socket connection.
33+
2026-01-12T09:20:11.8241106Z """
34+
2026-01-12T09:20:11.8241424Z try:
35+
2026-01-12T09:20:11.8241786Z > sock = connection.create_connection(
36+
2026-01-12T09:20:11.8242327Z (self._dns_host, self.port),
37+
2026-01-12T09:20:11.8242817Z self.timeout,
38+
2026-01-12T09:20:11.8243277Z source_address=self.source_address,
39+
2026-01-12T09:20:11.8243799Z socket_options=self.socket_options,
40+
2026-01-12T09:20:11.8244286Z )
41+
2026-01-12T09:20:11.8244484Z
42+
2026-01-12T09:20:11.8244793Z /venv/lib/python3.11/site-packages/urllib3/connection.py:204:
43+
2026-01-12T09:20:11.8245502Z _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
44+
2026-01-12T09:20:11.8246401Z /venv/lib/python3.11/site-packages/urllib3/util/connection.py:85: in create_connection
45+
2026-01-12T09:20:11.8347779Z raise err
46+
2026-01-12T09:20:11.8348255Z _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
47+
2026-01-12T09:20:11.8348630Z
48+
2026-01-12T09:20:11.8348897Z address = ('media.githubusercontent.com', 443), timeout = None
49+
2026-01-12T09:20:11.8349947Z source_address = None, socket_options = [(6, 1, 1)]
50+
2026-01-12T09:20:11.8350330Z
51+
2026-01-12T09:20:11.8350466Z def create_connection(
52+
2026-01-12T09:20:11.8350817Z address: tuple[str, int],
53+
2026-01-12T09:20:11.8351231Z timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT,
54+
2026-01-12T09:20:11.8351765Z source_address: tuple[str, int] | None = None,
55+
2026-01-12T09:20:11.8352331Z socket_options: _TYPE_SOCKET_OPTIONS | None = None,
56+
2026-01-12T09:20:11.8352858Z ) -> socket.socket:
57+
2026-01-12T09:20:11.8353311Z """Connect to *address* and return the socket object.
58+
2026-01-12T09:20:11.8353778Z
59+
2026-01-12T09:20:11.8354208Z Convenience function. Connect to *address* (a 2-tuple ``(host,
60+
2026-01-12T09:20:11.8354907Z port)``) and return the socket object. Passing the optional
61+
2026-01-12T09:20:11.8355534Z *timeout* parameter will set the timeout on the socket instance
62+
2026-01-12T09:20:11.8356230Z before attempting to connect. If no *timeout* is supplied, the
63+
2026-01-12T09:20:11.8357015Z global default timeout setting returned by :func:`socket.getdefaulttimeout`
64+
2026-01-12T09:20:11.8357946Z is used. If *source_address* is set it must be a tuple of (host, port)
65+
2026-01-12T09:20:11.8358650Z for the socket to bind as a source address before making the connection.
66+
2026-01-12T09:20:11.8359273Z An host of '' or port 0 tells the OS to use the default.
67+
2026-01-12T09:20:11.8359720Z """
68+
2026-01-12T09:20:11.8359973Z
69+
2026-01-12T09:20:11.8360241Z host, port = address
70+
2026-01-12T09:20:11.8360599Z if host.startswith("["):
71+
2026-01-12T09:20:11.8360976Z host = host.strip("[]")
72+
2026-01-12T09:20:11.8361339Z err = None
73+
2026-01-12T09:20:11.8361618Z
74+
2026-01-12T09:20:11.8362081Z # Using the value from allowed_gai_family() in the context of getaddrinfo lets
75+
2026-01-12T09:20:11.8362795Z # us select whether to work with IPv4 DNS records, IPv6 records, or both.
76+
2026-01-12T09:20:11.8363470Z # The original create_connection function always returns all records.
77+
2026-01-12T09:20:11.8364027Z family = allowed_gai_family()
78+
2026-01-12T09:20:11.8364401Z
79+
2026-01-12T09:20:11.8364650Z try:
80+
2026-01-12T09:20:11.8364934Z host.encode("idna")
81+
2026-01-12T09:20:11.8365277Z except UnicodeError:
82+
2026-01-12T09:20:11.8365789Z raise LocationParseError(f"'{host}', label empty or too long") from None
83+
2026-01-12T09:20:11.8366345Z
84+
2026-01-12T09:20:11.8366754Z for res in socket.getaddrinfo(host, port, family, socket.SOCK_STREAM):
85+
2026-01-12T09:20:11.8367337Z af, socktype, proto, canonname, sa = res
86+
2026-01-12T09:20:11.8367740Z sock = None
87+
2026-01-12T09:20:11.8368051Z try:
88+
2026-01-12T09:20:11.8368394Z sock = socket.socket(af, socktype, proto)
89+
2026-01-12T09:20:11.8368801Z
90+
2026-01-12T09:20:11.8369193Z # If provided, set socket level options before connecting.
91+
2026-01-12T09:20:11.8369736Z _set_socket_options(sock, socket_options)
92+
2026-01-12T09:20:11.8370132Z
93+
2026-01-12T09:20:11.8370418Z if timeout is not _DEFAULT_TIMEOUT:
94+
2026-01-12T09:20:11.8370846Z sock.settimeout(timeout)
95+
2026-01-12T09:20:11.8371243Z if source_address:
96+
2026-01-12T09:20:11.8371642Z sock.bind(source_address)
97+
2026-01-12T09:20:11.8372022Z > sock.connect(sa)
98+
2026-01-12T09:20:11.8372425Z E OSError: [Errno 101] Network is unreachable
99+
2026-01-12T09:20:11.8372743Z
100+
2026-01-12T09:20:11.8373030Z /venv/lib/python3.11/site-packages/urllib3/util/connection.py:73: OSError
101+
2026-01-12T09:20:11.8373471Z
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<log_stub>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<log_stub>

0 commit comments

Comments
 (0)