mirror of
https://github.com/hanzoai/datastore.git
synced 2026-09-26 13:55:07 +00:00
The build_profile_hook telemetry upload contends for the shared LogCluster analytics DB: on a master push ~25 Build variants finish and upload at once, and the combined load crosses the cluster's per-user memory limit (Code 241 MEMORY_LIMIT_EXCEEDED). Restrict the upload to an explicit subset of two representative release builds (amd_release, arm_release) so the contention is removed at the source rather than its symptom suppressed. The other build names are listed commented-out so re-enabling one is a one-line change. input_format_parallel_parsing=0 on the INSERT is kept. With the workload reduced, drop the best-effort catch-and-log of upload failures: a genuine INSERT rejection now means a real lost upload and must stay visible (the hook fails loudly) instead of being reported as success. The empty-artifact skip (#84159, builds that produce no profile data) is unrelated to error handling and stays. Co-Authored-By: Claude Opus 4.8 <[email protected]>
126 lines
4.5 KiB
Python
126 lines
4.5 KiB
Python
import sys
|
|
import traceback
|
|
from pathlib import Path
|
|
|
|
from ci.jobs.scripts.log_cluster import LogClusterBuildProfileQueries
|
|
from ci.praktika.info import Info
|
|
from ci.praktika.result import Result
|
|
from ci.praktika.utils import Shell, Utils
|
|
|
|
temp_dir = "./ci/tmp"
|
|
build_dir = "./ci/tmp/build"
|
|
|
|
# Build profile telemetry is collected only for this explicit subset of builds.
|
|
# The analytics DB is shared by every Build variant; on a master push ~25
|
|
# variants finish and upload near-simultaneously, and the combined load crosses
|
|
# the cluster's per-user memory limit (Code 241 MEMORY_LIMIT_EXCEEDED). Two
|
|
# representative release builds keep the profile we actually look at (the
|
|
# production binaries) while removing that contention at the source. Uncomment a
|
|
# build below to profile it too; the full set of build names is BuildTypes in
|
|
# ci/defs/defs.py.
|
|
_PROFILED_BUILDS = (
|
|
"amd_release",
|
|
"arm_release",
|
|
# "amd_debug",
|
|
# "arm_debug",
|
|
# "amd_binary",
|
|
# "arm_binary",
|
|
# "amd_asan_ubsan",
|
|
# "arm_asan_ubsan",
|
|
# "amd_tsan",
|
|
# "arm_tsan",
|
|
# "amd_msan",
|
|
# "arm_msan",
|
|
# "arm_ubsan",
|
|
# "amd_darwin",
|
|
# "arm_darwin",
|
|
)
|
|
|
|
|
|
def _should_profile(build_type):
|
|
"""Whether build profile telemetry is collected for this build variant."""
|
|
return build_type in _PROFILED_BUILDS
|
|
|
|
|
|
def _has_data(file):
|
|
"""Whether prepare-time-trace.sh actually produced this artifact.
|
|
|
|
Not every build emits every artifact: binary_symbols.txt is skipped for
|
|
LTO builds (nm does not work with LTO) and cross-arch/non-Linux builds
|
|
produce no readable objects at all (#84159). prepare-time-trace.sh uses
|
|
`xargs -r`, so for those builds the artifact is left empty rather than
|
|
holding a junk row. An absent or empty file therefore means "this build
|
|
has no such profile data to upload", not that the job failed.
|
|
"""
|
|
file = Path(file)
|
|
return file.exists() and file.stat().st_size > 0
|
|
|
|
|
|
def _upload_profile_artifacts(build_type, start_time, artifacts):
|
|
"""Upload the profile artifacts this build produced.
|
|
|
|
Skips artifacts that are genuinely empty (no data for this build, see
|
|
_has_data). Upload failures are NOT swallowed: an INSERT rejection
|
|
propagates so the lost telemetry stays visible and the hook fails loudly.
|
|
"""
|
|
for insert, file in artifacts:
|
|
if not _has_data(file):
|
|
print(f"No build profile data in [{file}], skipping upload")
|
|
continue
|
|
insert(build_name=build_type, start_time=start_time, file=file)
|
|
|
|
|
|
def check():
|
|
build_type = Info().job_name.split("(")[1].rstrip(")")
|
|
assert build_type
|
|
if not _should_profile(build_type):
|
|
print(f"Build profile telemetry not collected for [{build_type}]")
|
|
return
|
|
print("Prepare build profile data")
|
|
profiles_dir = Path("./ci/tmp") / "profiles_source"
|
|
profiles_dir.mkdir(parents=True, exist_ok=True)
|
|
try:
|
|
Shell.check(
|
|
"./utils/prepare-time-trace/prepare-time-trace.sh "
|
|
f"{build_dir} {profiles_dir.absolute()}",
|
|
strict=True,
|
|
verbose=True,
|
|
)
|
|
profile_data_file = Path(temp_dir) / "profile.json"
|
|
build_size_file = profiles_dir / "binary_sizes.txt"
|
|
binary_symbol_file = profiles_dir / "binary_symbols.txt"
|
|
with open(profile_data_file, "wb") as profile_fd:
|
|
for profile_source in profiles_dir.iterdir():
|
|
if profile_source.name not in (
|
|
"binary_sizes.txt",
|
|
"binary_symbols.txt",
|
|
):
|
|
with open(profile_source, "rb") as ps_fd:
|
|
profile_fd.write(ps_fd.read())
|
|
check_start_time = Utils.timestamp_to_str(
|
|
Result.from_fs(Info().job_name).start_time
|
|
)
|
|
queries = LogClusterBuildProfileQueries()
|
|
_upload_profile_artifacts(
|
|
build_type,
|
|
check_start_time,
|
|
[
|
|
(queries.insert_profile_data, profile_data_file),
|
|
(queries.insert_build_size_data, build_size_file),
|
|
(queries.insert_binary_symbol_data, binary_symbol_file),
|
|
],
|
|
)
|
|
except Exception:
|
|
# Fail loudly on any error producing, assembling, or uploading the
|
|
# profile data: never let the post-hook pass silently.
|
|
print("ERROR: Failed to collect build profile data:")
|
|
traceback.print_exc()
|
|
sys.exit(1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
if Info().pr_number == 0:
|
|
check()
|
|
else:
|
|
print("Not applicable for PRs")
|