From e5029a79227411dd6038d4abeb477c19b470ed80 Mon Sep 17 00:00:00 2001 From: "shuohigh@gmail.com" Date: Tue, 27 May 2025 13:26:13 +0800 Subject: [PATCH] =?UTF-8?q?-=20=E8=A7=84=E8=8C=83sentry=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E6=94=B6=E9=9B=86=E6=A0=87=E7=AD=BE=20-=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=88=87=E5=89=B2=E6=97=B6=E9=97=B4=E7=82=B9=E7=BB=93=E6=9D=9F?= =?UTF-8?q?=E6=97=B6=E9=97=B4=E5=BF=85=E9=A1=BB=E5=A4=A7=E4=BA=8E=E5=BC=80?= =?UTF-8?q?=E5=A7=8B=E6=97=B6=E9=97=B4=E7=9A=84=E6=A3=80=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .runtime.env | 2 +- .../models/ffmpeg_worker_model.py | 8 ++++++- .../models/media_model.py | 18 ++++++++++++++-- src/BowongModalFunctions/utils/SentryUtils.py | 21 +++++++------------ src/cluster/ffmpeg_app.py | 8 +++---- 5 files changed, 36 insertions(+), 21 deletions(-) diff --git a/.runtime.env b/.runtime.env index dbc5e5b..6595d0c 100644 --- a/.runtime.env +++ b/.runtime.env @@ -1,4 +1,4 @@ -MODAL_ENVIRONMENT=dev +MODAL_ENVIRONMENT=test modal_app_name=bowong-ai-video S3_mount_dir=/mntS3 S3_bucket_name=modal-media-cache diff --git a/src/BowongModalFunctions/models/ffmpeg_worker_model.py b/src/BowongModalFunctions/models/ffmpeg_worker_model.py index b4d5ef7..287a66e 100644 --- a/src/BowongModalFunctions/models/ffmpeg_worker_model.py +++ b/src/BowongModalFunctions/models/ffmpeg_worker_model.py @@ -1,5 +1,5 @@ from typing import Union, Any -from pydantic import BaseModel, Field, computed_field, field_validator +from pydantic import BaseModel, Field, computed_field, field_validator, model_validator from pydantic.json_schema import JsonSchemaValue from ..utils.TimeUtils import TimeDelta @@ -43,6 +43,12 @@ class FFMpegSliceSegment(BaseModel): else: raise TypeError(v) + @model_validator(mode='after') + def validate_end_after_start(self) -> 'FFMpegSliceSegment': + if self.end <= self.start: + raise ValueError("end time must be greater than start time") + return self + @classmethod def __get_pydantic_json_schema__(cls, core_schema: Any, handler: Any) -> JsonSchemaValue: # Override the schema to represent it as a string diff --git a/src/BowongModalFunctions/models/media_model.py b/src/BowongModalFunctions/models/media_model.py index 48309a0..50e8934 100644 --- a/src/BowongModalFunctions/models/media_model.py +++ b/src/BowongModalFunctions/models/media_model.py @@ -201,7 +201,14 @@ class MediaSources(BaseModel): @classmethod def parse_inputs(cls, v: Union[str, MediaSource]) -> List[MediaSource]: if not v: - raise ValidationError("inputs为空") + raise ValidationError([ + { + 'loc': ('inputs',), + 'msg': "inputs为空", + 'type': 'value_error', + 'input': v + } + ], MediaSources) result = [] for item in v: if isinstance(item, str): @@ -209,7 +216,14 @@ class MediaSources(BaseModel): elif isinstance(item, MediaSource): result.append(item) else: - raise ValidationError("inputs元素类型错误: 必须是字符串") + raise ValidationError([ + { + 'loc': ('inputs',), + 'msg': "inputs元素类型错误: 必须是字符串", + 'type': 'value_error', + 'input': v + } + ], MediaSources) return result model_config = { diff --git a/src/BowongModalFunctions/utils/SentryUtils.py b/src/BowongModalFunctions/utils/SentryUtils.py index 1e941f8..397f683 100644 --- a/src/BowongModalFunctions/utils/SentryUtils.py +++ b/src/BowongModalFunctions/utils/SentryUtils.py @@ -32,7 +32,7 @@ class SentryUtils: op=op, name=name) else: - transaction = sentry_sdk.start_transaction(op='modal.function', name='Modal Function直接调用') + transaction = sentry_sdk.start_transaction(op=op, name=name) with transaction: with transaction.start_child(op=op, name=name) as span: cpu_freq, cpu_count, mem = SentryUtils.capture_hardware_info() @@ -43,23 +43,18 @@ class SentryUtils: 'x_trace.id': sentry_trace_id, 'x_trace.baggage': sentry_baggage, 'cpu.count': cpu_count, - 'cpu.frequency': cpu_freq.current / 1000, + 'cpu.frequency': f"{cpu_freq.current / 1000:.2f}", 'cpu.frequency.format': f"{cpu_freq.current / 1000:.2f} GHz", 'memory.available': total_mb, 'memory.available.format': f"{total_mb} Mb", - 'cloud_resource': { - "cloud.provider": 'Modal', - "type": "cloud_resource" - }, - 'modal': { - 'modal.region': os.environ.get('MODAL_REGION', 'unknown'), - 'modal.provider': os.environ.get('MODAL_CLOUD_PROVIDER', 'unknown'), - 'modal.task.id': os.environ.get('MODAL_TASK_ID', 'unknown'), - 'modal.identity.token': os.environ.get('MODAL_IDENTITY_TOKEN', 'unknown'), - 'modal.image.id': os.environ.get('MODAL_IMAGE_ID', 'unknown'), - } + 'modal.cloud.region': os.environ.get('MODAL_REGION', 'unknown'), + 'modal.cloud.provider': os.environ.get('MODAL_CLOUD_PROVIDER', 'unknown'), + 'modal.task.id': os.environ.get('MODAL_TASK_ID', 'unknown'), + 'modal.identity.token': os.environ.get('MODAL_IDENTITY_TOKEN', 'unknown'), + 'modal.image.id': os.environ.get('MODAL_IMAGE_ID', 'unknown'), }) result = func(*args, **kwargs) + span.set_status('ok') return result return wrapper diff --git a/src/cluster/ffmpeg_app.py b/src/cluster/ffmpeg_app.py index 698870a..337c6d3 100644 --- a/src/cluster/ffmpeg_app.py +++ b/src/cluster/ffmpeg_app.py @@ -156,7 +156,7 @@ with ffmpeg_worker_image.imports(): List[str], Optional[SentryTransactionInfo]]: fn_id = current_function_call_id() - @SentryUtils.sentry_tracker(name="视频切割任务", op="ffmpeg.slice", fn_id=fn_id, + @SentryUtils.sentry_tracker(name="视频文件切割任务", op="ffmpeg.slice.media", fn_id=fn_id, sentry_trace_id=sentry_trace.x_trace_id if sentry_trace else None, sentry_baggage=sentry_trace.x_baggage if sentry_trace else None) @SentryUtils.webhook_handler(webhook=webhook, func_id=fn_id) @@ -164,15 +164,15 @@ with ffmpeg_worker_image.imports(): fn_id: str) -> List[FFMPEGResult]: cache_filepath = f"{s3_mount}/{media_source.cache_filepath}" logger.info(f"从{media_source.urn}切割") - for marker in media_markers: - logger.info(f"{marker.start.toFormatStr()} --> {marker.end.toFormatStr()}") + for i, marker in enumerate(media_markers): + logger.info(f"[{i}] {marker.start.toFormatStr()} --> {marker.end.toFormatStr()}") segments = await VideoUtils.ffmpeg_slice_media(media_path=cache_filepath, media_markers=media_markers, output_path=f"{output_path_prefix}/{config.modal_environment}/slice/outputs/{fn_id}/output.mp4") return [FFMPEGResult(urn=local_copy_to_s3([segment[0]])[0], metadata=segment[1], content_length=os.path.getsize(segment[0])) for segment in segments] - @SentryUtils.sentry_tracker(name="视频切割任务", op="ffmpeg.slice", fn_id=fn_id, + @SentryUtils.sentry_tracker(name="直播视频切割任务", op="ffmpeg.slice.stream", fn_id=fn_id, sentry_trace_id=sentry_trace.x_trace_id if sentry_trace else None, sentry_baggage=sentry_trace.x_baggage if sentry_trace else None) @SentryUtils.webhook_handler(webhook=webhook, func_id=fn_id)