daniel 작업본 추가
This commit is contained in:
@@ -2,6 +2,7 @@ package com.kamco.cd.training.train;
|
||||
|
||||
import com.kamco.cd.training.config.api.ApiResponseDto;
|
||||
import com.kamco.cd.training.train.dto.TrainingMetricsDto;
|
||||
import com.kamco.cd.training.train.dto.TrainingProgressDto;
|
||||
import com.kamco.cd.training.train.service.DataSetCountersService;
|
||||
import com.kamco.cd.training.train.service.TestJobService;
|
||||
import com.kamco.cd.training.train.service.TrainJobService;
|
||||
@@ -298,4 +299,26 @@ public class TrainApiController {
|
||||
trainingMetricsService.getTrainingMetricsByModelUuid(modelUuid);
|
||||
return ApiResponseDto.ok(response);
|
||||
}
|
||||
|
||||
@Operation(
|
||||
summary = "학습 진행률 조회",
|
||||
description = "UUID로 학습 진행률을 실시간 조회합니다. 기존 DB 구조를 활용하여 진행률을 계산합니다.")
|
||||
@ApiResponses(
|
||||
value = {
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "조회 성공",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = "application/json",
|
||||
schema = @Schema(implementation = TrainingProgressDto.class))),
|
||||
@ApiResponse(responseCode = "404", description = "모델을 찾을 수 없음", content = @Content),
|
||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||
})
|
||||
@GetMapping("/progress/{uuid}")
|
||||
public ApiResponseDto<TrainingProgressDto> getTrainingProgress(
|
||||
@Parameter(description = "모델 UUID", required = true) @PathVariable UUID uuid) {
|
||||
TrainingProgressDto progress = trainJobService.getTrainingProgress(uuid);
|
||||
return ApiResponseDto.ok(progress);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.kamco.cd.training.train.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.UUID;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
|
||||
/** 학습 진행률 조회 응답 DTO */
|
||||
@Getter
|
||||
@Builder
|
||||
public class TrainingProgressDto {
|
||||
|
||||
// 기본 정보
|
||||
private Long jobId;
|
||||
private UUID modelUuid;
|
||||
private String statusCd; // QUEUED/RUNNING/SUCCESS/FAILED
|
||||
private String currentPhase; // 현재 단계 (계산된 값)
|
||||
private Double progressPercent; // 진행률 (0.00 ~ 100.00, 계산된 값)
|
||||
|
||||
// Epoch 정보 (기존 DB 컬럼)
|
||||
private Integer currentEpoch; // 현재 Epoch
|
||||
private Integer totalEpoch; // 전체 Epoch
|
||||
|
||||
// 시간 정보 (기존 DB 컬럼) - ISO 8601 형식으로 직렬화
|
||||
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX")
|
||||
private ZonedDateTime queuedDttm; // 큐 등록 시각
|
||||
|
||||
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX")
|
||||
private ZonedDateTime startedDttm; // 시작 시각
|
||||
|
||||
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX")
|
||||
private ZonedDateTime finishedDttm; // 종료 시각 (완료/실패 시)
|
||||
|
||||
// 계산된 시간 정보
|
||||
private Long elapsedSeconds; // 경과 시간 (초)
|
||||
private Long estimatedRemainingSeconds; // 예상 남은 시간 (초)
|
||||
|
||||
// 상태 메시지
|
||||
private String message; // 현재 상태 메시지 (계산된 값)
|
||||
|
||||
// 에러 정보 (실패 시)
|
||||
private String errorMessage; // 에러 메시지 (기존 DB 컬럼)
|
||||
}
|
||||
@@ -158,12 +158,42 @@ public class DockerTrainService {
|
||||
lastEpoch.set(epoch);
|
||||
lastIter.set(iter);
|
||||
|
||||
// Step 구분 (컨테이너 이름으로 판별)
|
||||
boolean isStep1 = containerName.startsWith("train-");
|
||||
boolean isStep2 = containerName.startsWith("eval-");
|
||||
|
||||
// 진행률 계산 (Step별 구분)
|
||||
double progress = 0.0;
|
||||
if (maxEpochs > 0) {
|
||||
// Epoch + Iteration 기반 정밀 진행률 계산
|
||||
double epochProgress =
|
||||
((double) (epoch - 1) + ((double) iter / totalIter)) / maxEpochs;
|
||||
|
||||
// Step1 (학습): 7.5% ~ 42.5% (0% ~ 50% 중 15% ~ 85%)
|
||||
// Step2 (테스트): 57.5% ~ 92.5% (50% ~ 100% 중 15% ~ 85%)
|
||||
if (isStep1) {
|
||||
// Step1: 0% ~ 50% 범위, 그 중 15% ~ 85% = 7.5% ~ 42.5%
|
||||
progress = 7.5 + (epochProgress * 35.0);
|
||||
} else if (isStep2) {
|
||||
// Step2: 50% ~ 100% 범위, 그 중 15% ~ 85% = 57.5% ~ 92.5%
|
||||
progress = 57.5 + (epochProgress * 35.0);
|
||||
} else {
|
||||
// 기본값 (하위 호환)
|
||||
progress = 15.0 + (epochProgress * 70.0);
|
||||
}
|
||||
}
|
||||
|
||||
String stepLabel = isStep1 ? "STEP1" : isStep2 ? "STEP2" : "UNKNOWN";
|
||||
|
||||
log.info(
|
||||
"[TRAIN] container={} epoch={} iter={}/{}",
|
||||
"[TRAIN] {} container={} epoch={}/{} iter={}/{} | Progress: {}%",
|
||||
stepLabel,
|
||||
containerName,
|
||||
epoch,
|
||||
maxEpochs,
|
||||
iter,
|
||||
totalIter);
|
||||
totalIter,
|
||||
String.format("%.2f", progress));
|
||||
|
||||
modelTrainJobCoreService.updateEpoch(containerName, epoch);
|
||||
}
|
||||
|
||||
@@ -300,7 +300,7 @@ public class ModelTestMetricsJobService {
|
||||
zipFiles(zipFileList, individualZipPath);
|
||||
|
||||
log.info(
|
||||
"✅ 개별 ZIP 생성 완료: fileName={}, pthFile={}, size={} bytes",
|
||||
"개별 ZIP 생성 완료: fileName={}, pthFile={}, size={} bytes",
|
||||
individualZipName,
|
||||
pthFileName,
|
||||
Files.size(individualZipPath));
|
||||
|
||||
@@ -13,6 +13,8 @@ import com.kamco.cd.training.train.dto.ModelTrainJobQueuedEvent;
|
||||
import com.kamco.cd.training.train.dto.ModelTrainLinkDto;
|
||||
import com.kamco.cd.training.train.dto.OutputResult;
|
||||
import com.kamco.cd.training.train.dto.TrainRunRequest;
|
||||
import com.kamco.cd.training.train.dto.TrainingProgressDto;
|
||||
import com.kamco.cd.training.train.util.TrainingProgressCalculator;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@@ -466,4 +468,136 @@ public class TrainJobService {
|
||||
modelTrainMngCoreService.markStep1Stop(job.getModelId(), msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* UUID로 학습 진행률 조회 기존 DB 컬럼만을 활용하여 실시간으로 진행률 계산
|
||||
*
|
||||
* @param uuid 모델 UUID
|
||||
* @return 학습 진행률 정보
|
||||
*/
|
||||
@Transactional(readOnly = true)
|
||||
public TrainingProgressDto getTrainingProgress(UUID uuid) {
|
||||
// 1. UUID로 모델 조회
|
||||
Long modelId = getModelIdByUuid(uuid);
|
||||
ModelTrainMngDto.Basic model = modelTrainMngCoreService.findModelById(modelId);
|
||||
|
||||
// 2. Step 상태 확인 (step1Status, step2Status)
|
||||
String step1Status = model.getStep1Status(); // READY/IN_PROGRESS/COMPLETED/STOPPED/ERROR
|
||||
String step2Status = model.getStep2Status(); // READY/IN_PROGRESS/COMPLETED/STOPPED/ERROR
|
||||
|
||||
// 3. 현재 실행중인 Job ID 확인
|
||||
Long jobId = model.getCurrentAttemptId();
|
||||
|
||||
if (jobId == null) {
|
||||
// Job이 없는 경우 모델 상태만 반환
|
||||
// Step1 완료 여부에 따라 진행률 결정
|
||||
double progressPercent = 0.0;
|
||||
String currentPhase = "NOT_STARTED";
|
||||
|
||||
if ("COMPLETED".equals(step1Status) && "COMPLETED".equals(step2Status)) {
|
||||
progressPercent = 100.0;
|
||||
currentPhase = "COMPLETED";
|
||||
} else if ("COMPLETED".equals(step1Status)) {
|
||||
progressPercent = 50.0;
|
||||
currentPhase = "STEP1_COMPLETED";
|
||||
}
|
||||
|
||||
return TrainingProgressDto.builder()
|
||||
.modelUuid(uuid)
|
||||
.statusCd(model.getStatusCd())
|
||||
.currentPhase(currentPhase)
|
||||
.progressPercent(progressPercent)
|
||||
.message(getMessageForPhase(currentPhase, null, null))
|
||||
.build();
|
||||
}
|
||||
|
||||
// 4. Job 정보 조회 (기존 컬럼만 사용)
|
||||
ModelTrainJobDto job =
|
||||
modelTrainJobCoreService
|
||||
.findById(jobId)
|
||||
.orElseThrow(() -> new IllegalStateException("Job을 찾을 수 없습니다: " + jobId));
|
||||
|
||||
// 5. totalEpoch 추출 (DB 컬럼 우선, 없으면 params_json에서 추출)
|
||||
Integer totalEpoch = job.getTotalEpoch();
|
||||
if (totalEpoch == null && job.getParamsJson() != null) {
|
||||
Object totalEpochObj = job.getParamsJson().get("totalEpoch");
|
||||
if (totalEpochObj != null) {
|
||||
totalEpoch = Integer.valueOf(String.valueOf(totalEpochObj));
|
||||
}
|
||||
}
|
||||
|
||||
// 6. jobType 추출 (TRAIN/EVAL)
|
||||
String jobType = "TRAIN"; // 기본값
|
||||
if (job.getParamsJson() != null) {
|
||||
Object jobTypeObj = job.getParamsJson().get("jobType");
|
||||
if (jobTypeObj != null) {
|
||||
jobType = String.valueOf(jobTypeObj);
|
||||
}
|
||||
}
|
||||
|
||||
// 7. 진행률 계산 (Step 구분하여 계산)
|
||||
double progressPercent =
|
||||
TrainingProgressCalculator.calculateProgress(
|
||||
job.getStatusCd(),
|
||||
job.getCurrentEpoch(),
|
||||
totalEpoch,
|
||||
job.getStartedDttm(),
|
||||
job.getFinishedDttm(),
|
||||
jobType,
|
||||
step1Status,
|
||||
step2Status);
|
||||
|
||||
// 8. 현재 Phase 추정
|
||||
String currentPhase =
|
||||
TrainingProgressCalculator.estimateCurrentPhase(
|
||||
job.getStatusCd(), job.getCurrentEpoch(), totalEpoch, job.getStartedDttm(), jobType);
|
||||
|
||||
// 9. 경과 시간 계산
|
||||
Long elapsedSeconds = TrainingProgressCalculator.calculateElapsedSeconds(job.getStartedDttm());
|
||||
|
||||
// 10. 예상 남은 시간 계산
|
||||
Long estimatedRemaining =
|
||||
TrainingProgressCalculator.estimateRemainingSeconds(progressPercent, elapsedSeconds);
|
||||
|
||||
// 11. 상태 메시지 생성
|
||||
String message =
|
||||
TrainingProgressCalculator.generateProgressMessage(
|
||||
currentPhase, job.getCurrentEpoch(), totalEpoch);
|
||||
|
||||
// 12. DTO 생성 및 반환
|
||||
return TrainingProgressDto.builder()
|
||||
.jobId(job.getId())
|
||||
.modelUuid(uuid)
|
||||
.statusCd(job.getStatusCd())
|
||||
.currentPhase(currentPhase)
|
||||
.progressPercent(progressPercent)
|
||||
.currentEpoch(job.getCurrentEpoch())
|
||||
.totalEpoch(totalEpoch)
|
||||
.queuedDttm(job.getQueuedDttm())
|
||||
.startedDttm(job.getStartedDttm())
|
||||
.finishedDttm(job.getFinishedDttm())
|
||||
.elapsedSeconds(elapsedSeconds)
|
||||
.estimatedRemainingSeconds(estimatedRemaining)
|
||||
.message(message)
|
||||
.errorMessage(job.getErrorMessage())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Phase에 맞는 메시지 반환 (Job이 없는 경우)
|
||||
*
|
||||
* @param phase 현재 Phase
|
||||
* @param currentEpoch 현재 Epoch
|
||||
* @param totalEpoch 전체 Epoch
|
||||
* @return 상태 메시지
|
||||
*/
|
||||
private String getMessageForPhase(String phase, Integer currentEpoch, Integer totalEpoch) {
|
||||
if ("COMPLETED".equals(phase)) {
|
||||
return "모든 학습 및 테스트가 완료되었습니다.";
|
||||
} else if ("STEP1_COMPLETED".equals(phase)) {
|
||||
return "학습이 완료되었습니다. 테스트를 시작할 수 있습니다.";
|
||||
} else {
|
||||
return "학습 작업이 시작되지 않았습니다.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@ public class TrainJobWorker {
|
||||
(isEval ? "eval-" : "train-") + jobId + "-" + params.get("uuid").toString().substring(0, 8);
|
||||
|
||||
String type = isEval ? "TEST" : "TRAIN";
|
||||
String step = isEval ? "STEP2" : "STEP1";
|
||||
|
||||
Integer totalEpoch = null;
|
||||
if (params.containsKey("totalEpoch")) {
|
||||
@@ -65,6 +66,21 @@ public class TrainJobWorker {
|
||||
totalEpoch = Integer.parseInt(params.get("totalEpoch").toString());
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 1: 준비 단계
|
||||
// Step1: 0% ~ 2.5%, Step2: 50% ~ 52.5%
|
||||
double preparingProgress = isEval ? 50.0 : 0.0;
|
||||
log.info(
|
||||
"[JOB] {} jobId={} | Phase: PREPARING | Progress: {}%",
|
||||
step, jobId, String.format("%.1f", preparingProgress + 2.5));
|
||||
|
||||
// Phase 2: 컨테이너 시작
|
||||
// Step1: 2.5% ~ 5%, Step2: 52.5% ~ 55%
|
||||
double containerStartProgress = isEval ? 52.5 : 2.5;
|
||||
log.info(
|
||||
"[JOB] {} jobId={} | Phase: CONTAINER_STARTING | Progress: {}%",
|
||||
step, jobId, String.format("%.1f", containerStartProgress + 2.5));
|
||||
|
||||
log.info("[JOB] markRunning start jobId={}, containerName={}", jobId, containerName);
|
||||
// 실행 시작 처리
|
||||
modelTrainJobCoreService.markRunning(
|
||||
@@ -90,12 +106,21 @@ public class TrainJobWorker {
|
||||
evalReq.setReqTmpYn((Boolean) params.get("reqTmpYn"));
|
||||
log.info("[JOB] selected test epoch={}", epoch);
|
||||
|
||||
// Phase 3: 테스트 시작
|
||||
// Step2: 55% ~ 57.5%
|
||||
log.info("[JOB] STEP2 jobId={} | Phase: EVAL_STARTED | Progress: 57.5%", jobId);
|
||||
|
||||
// 도커 실행 후 로그 수집
|
||||
result = dockerTrainService.runEvalSync(containerName, evalReq);
|
||||
} else {
|
||||
// step1 진행중 처리
|
||||
modelTrainMngCoreService.markStep1InProgress(modelId, jobId);
|
||||
TrainRunRequest trainReq = toTrainRunRequest(params);
|
||||
|
||||
// Phase 3: 학습 시작
|
||||
// Step1: 5% ~ 7.5%
|
||||
log.info("[JOB] STEP1 jobId={} | Phase: TRAINING_STARTED | Progress: 7.5%", jobId);
|
||||
|
||||
// 도커 실행 후 로그 수집
|
||||
result = dockerTrainService.runTrainSync(trainReq, containerName);
|
||||
}
|
||||
@@ -109,11 +134,25 @@ public class TrainJobWorker {
|
||||
return;
|
||||
}
|
||||
|
||||
// Phase 4: 학습/테스트 완료
|
||||
// Step1: 42.5%, Step2: 92.5%
|
||||
double completedProgress = isEval ? 92.5 : 42.5;
|
||||
log.info(
|
||||
"[JOB] {} jobId={} | Phase: TRAINING_COMPLETED | Progress: {}%",
|
||||
step, jobId, String.format("%.1f", completedProgress));
|
||||
|
||||
/**
|
||||
* 0 정상 종료 SUCCESS 1~125 학습 코드 에러 FAILED 137 OOMKill FAILED 143 SIGTERM (stop) STOP -1 우리 내부
|
||||
* 강제 중단 STOP
|
||||
*/
|
||||
if (result.getExitCode() == 0) {
|
||||
// Phase 5: 결과 처리 중
|
||||
// Step1: 45%, Step2: 95%
|
||||
double processingProgress = isEval ? 95.0 : 45.0;
|
||||
log.info(
|
||||
"[JOB] {} jobId={} | Phase: PROCESSING_RESULTS | Progress: {}%",
|
||||
step, jobId, String.format("%.1f", processingProgress));
|
||||
|
||||
// 성공 처리
|
||||
modelTrainJobCoreService.markSuccess(jobId, result.getExitCode());
|
||||
|
||||
@@ -128,6 +167,13 @@ public class TrainJobWorker {
|
||||
modelTrainMetricsJobService.findTrainValidMetricCsvFiles();
|
||||
}
|
||||
|
||||
// Phase 6: 완료
|
||||
// Step1: 50%, Step2: 100%
|
||||
double finalProgress = isEval ? 100.0 : 50.0;
|
||||
log.info(
|
||||
"[JOB] {} jobId={} | Phase: COMPLETED | Progress: {}%",
|
||||
step, jobId, String.format("%.1f", finalProgress));
|
||||
|
||||
} else {
|
||||
|
||||
String failMsg = result.getStatus() + "\n" + result.getLogs();
|
||||
|
||||
@@ -0,0 +1,295 @@
|
||||
package com.kamco.cd.training.train.util;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
/**
|
||||
* 학습 진행률 계산 유틸리티 기존 DB 컬럼만을 활용하여 진행률을 실시간 계산
|
||||
*
|
||||
* <p>상태전의 STATUS 가이드 기준: - Step1 (학습): TRAIN jobType → 0% ~ 50% - Step2 (테스트): EVAL jobType → 50% ~
|
||||
* 100%
|
||||
*/
|
||||
@UtilityClass
|
||||
public class TrainingProgressCalculator {
|
||||
|
||||
/**
|
||||
* 학습 진행률 계산 (Step 구분)
|
||||
*
|
||||
* @param statusCd 작업 상태 (QUEUED/RUNNING/SUCCESS/FAILED/STOPPED/CANCELED)
|
||||
* @param currentEpoch 현재 Epoch
|
||||
* @param totalEpoch 전체 Epoch
|
||||
* @param startedDttm 시작 시각
|
||||
* @param finishedDttm 종료 시각
|
||||
* @param jobType 작업 타입 (TRAIN/EVAL)
|
||||
* @param step1State Step1 상태 (READY/IN_PROGRESS/COMPLETED/STOPPED/ERROR)
|
||||
* @param step2State Step2 상태 (READY/IN_PROGRESS/COMPLETED/STOPPED/ERROR)
|
||||
* @return 진행률 (0.00 ~ 100.00)
|
||||
*/
|
||||
public static double calculateProgress(
|
||||
String statusCd,
|
||||
Integer currentEpoch,
|
||||
Integer totalEpoch,
|
||||
ZonedDateTime startedDttm,
|
||||
ZonedDateTime finishedDttm,
|
||||
String jobType,
|
||||
String step1State,
|
||||
String step2State) {
|
||||
if (statusCd == null) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// Step별 진행률 계산
|
||||
boolean isStep1 = "TRAIN".equals(jobType);
|
||||
boolean isStep2 = "EVAL".equals(jobType) || "TEST".equals(jobType);
|
||||
|
||||
// Step1 완료, Step2 완료 여부 확인
|
||||
boolean isStep1Completed = "COMPLETED".equals(step1State);
|
||||
boolean isStep2Completed = "COMPLETED".equals(step2State);
|
||||
|
||||
switch (statusCd) {
|
||||
case "QUEUED":
|
||||
// Step1 대기 중: 0%, Step2 대기 중: 50%
|
||||
return isStep2 ? 50.0 : 0.0;
|
||||
|
||||
case "RUNNING":
|
||||
return calculateRunningProgress(
|
||||
currentEpoch, totalEpoch, startedDttm, isStep1, isStep2, isStep1Completed);
|
||||
|
||||
case "SUCCESS":
|
||||
// Step1 성공: 50%, Step2 성공: 100%
|
||||
if (isStep2 || isStep2Completed) {
|
||||
return 100.0;
|
||||
} else if (isStep1 || isStep1Completed) {
|
||||
return 50.0;
|
||||
}
|
||||
return 100.0; // 기본값
|
||||
|
||||
case "FAILED":
|
||||
case "STOPPED":
|
||||
case "CANCELED":
|
||||
// 실패/취소된 경우 마지막 진행률 반환
|
||||
return calculateRunningProgress(
|
||||
currentEpoch, totalEpoch, startedDttm, isStep1, isStep2, isStep1Completed);
|
||||
|
||||
default:
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 실행 중인 학습의 진행률 계산 (Step 구분)
|
||||
*
|
||||
* <p>Step1 (학습): 0% ~ 50% - PREPARING: 0% ~ 2.5% - CONTAINER_STARTING: 2.5% ~ 5% - DATA_LOADING:
|
||||
* 5% ~ 7.5% - TRAINING: 7.5% ~ 42.5% (35% 비중) - PROCESSING: 42.5% ~ 50%
|
||||
*
|
||||
* <p>Step2 (테스트): 50% ~ 100% - PREPARING: 50% ~ 52.5% - CONTAINER_STARTING: 52.5% ~ 55% -
|
||||
* DATA_LOADING: 55% ~ 57.5% - TESTING: 57.5% ~ 92.5% (35% 비중) - PROCESSING: 92.5% ~ 100%
|
||||
*/
|
||||
private static double calculateRunningProgress(
|
||||
Integer currentEpoch,
|
||||
Integer totalEpoch,
|
||||
ZonedDateTime startedDttm,
|
||||
boolean isStep1,
|
||||
boolean isStep2,
|
||||
boolean isStep1Completed) {
|
||||
|
||||
// Step 기준 계산
|
||||
double baseProgress = 0.0;
|
||||
double maxProgress = 50.0;
|
||||
|
||||
if (isStep2 || isStep1Completed) {
|
||||
// Step2 진행 중 또는 Step1 완료 후
|
||||
baseProgress = 50.0;
|
||||
maxProgress = 100.0;
|
||||
} else if (isStep1) {
|
||||
// Step1 진행 중
|
||||
baseProgress = 0.0;
|
||||
maxProgress = 50.0;
|
||||
}
|
||||
|
||||
double stepRange = maxProgress - baseProgress; // 50%
|
||||
|
||||
// 시작 직후 (Epoch 정보 없음)
|
||||
if (currentEpoch == null || totalEpoch == null || totalEpoch == 0) {
|
||||
return baseProgress + estimateInitialPhaseProgress(startedDttm, stepRange);
|
||||
}
|
||||
|
||||
// Epoch 0 또는 1 (학습 시작 단계)
|
||||
if (currentEpoch <= 1) {
|
||||
return baseProgress + (stepRange * 0.15); // 7.5% (Step1) 또는 57.5% (Step2)
|
||||
}
|
||||
|
||||
// 학습 진행 중 (15% ~ 85% of step range)
|
||||
double epochProgress = (double) currentEpoch / totalEpoch;
|
||||
double trainingProgress = 0.15 + (epochProgress * 0.70); // 15% ~ 85%
|
||||
|
||||
// 학습 완료 (마지막 Epoch)
|
||||
if (currentEpoch >= totalEpoch) {
|
||||
// 85% ~ 100% 사이로 추정
|
||||
return baseProgress + (stepRange * 0.90);
|
||||
}
|
||||
|
||||
return baseProgress + (stepRange * trainingProgress);
|
||||
}
|
||||
|
||||
/**
|
||||
* 초기 단계 진행률 추정 (시작 시간 기반)
|
||||
*
|
||||
* @param startedDttm 시작 시각
|
||||
* @param stepRange Step 범위 (50%)
|
||||
* @return 초기 단계 진행률 (0 ~ 0.15 비율)
|
||||
*/
|
||||
private static double estimateInitialPhaseProgress(ZonedDateTime startedDttm, double stepRange) {
|
||||
if (startedDttm == null) {
|
||||
return stepRange * 0.05; // 5% of step range
|
||||
}
|
||||
|
||||
long elapsedSeconds = ChronoUnit.SECONDS.between(startedDttm, ZonedDateTime.now());
|
||||
|
||||
// 시작 후 30초 이내: PREPARING (0% ~ 5% of step)
|
||||
if (elapsedSeconds < 30) {
|
||||
return Math.min(stepRange * 0.05, (elapsedSeconds / 30.0) * stepRange * 0.05);
|
||||
}
|
||||
// 30초 ~ 60초: CONTAINER_STARTING (5% ~ 10% of step)
|
||||
else if (elapsedSeconds < 60) {
|
||||
return stepRange * 0.05 + ((elapsedSeconds - 30) / 30.0) * stepRange * 0.05;
|
||||
}
|
||||
// 60초 이상: DATA_LOADING (10% ~ 15% of step)
|
||||
else {
|
||||
return Math.min(
|
||||
stepRange * 0.15, stepRange * 0.10 + ((elapsedSeconds - 60) / 60.0) * stepRange * 0.05);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 현재 Phase 추정
|
||||
*
|
||||
* @param statusCd 작업 상태
|
||||
* @param currentEpoch 현재 Epoch
|
||||
* @param totalEpoch 전체 Epoch
|
||||
* @param startedDttm 시작 시각
|
||||
* @param jobType 작업 타입 (TRAIN/EVAL)
|
||||
* @return 현재 Phase
|
||||
*/
|
||||
public static String estimateCurrentPhase(
|
||||
String statusCd,
|
||||
Integer currentEpoch,
|
||||
Integer totalEpoch,
|
||||
ZonedDateTime startedDttm,
|
||||
String jobType) {
|
||||
if ("SUCCESS".equals(statusCd)) {
|
||||
return "COMPLETED";
|
||||
}
|
||||
if ("FAILED".equals(statusCd)) {
|
||||
return "FAILED";
|
||||
}
|
||||
if ("STOPPED".equals(statusCd) || "CANCELED".equals(statusCd)) {
|
||||
return "CANCELED";
|
||||
}
|
||||
if ("QUEUED".equals(statusCd)) {
|
||||
return "QUEUED";
|
||||
}
|
||||
|
||||
// RUNNING 상태
|
||||
boolean isStep2 = "EVAL".equals(jobType) || "TEST".equals(jobType);
|
||||
String prefix = isStep2 ? "STEP2_" : "STEP1_";
|
||||
|
||||
if (currentEpoch == null || totalEpoch == null) {
|
||||
if (startedDttm == null) {
|
||||
return prefix + "PREPARING";
|
||||
}
|
||||
long elapsed = ChronoUnit.SECONDS.between(startedDttm, ZonedDateTime.now());
|
||||
if (elapsed < 30) return prefix + "PREPARING";
|
||||
if (elapsed < 60) return prefix + "CONTAINER_STARTING";
|
||||
return prefix + "DATA_LOADING";
|
||||
}
|
||||
|
||||
if (currentEpoch >= totalEpoch) {
|
||||
return prefix + "PROCESSING_RESULTS";
|
||||
}
|
||||
|
||||
return prefix + "TRAINING";
|
||||
}
|
||||
|
||||
/** 경과 시간 계산 (초) */
|
||||
public static Long calculateElapsedSeconds(ZonedDateTime startedDttm) {
|
||||
if (startedDttm == null) {
|
||||
return null;
|
||||
}
|
||||
return ChronoUnit.SECONDS.between(startedDttm, ZonedDateTime.now());
|
||||
}
|
||||
|
||||
/** 예상 남은 시간 계산 (초) */
|
||||
public static Long estimateRemainingSeconds(double progressPercent, Long elapsedSeconds) {
|
||||
if (elapsedSeconds == null || progressPercent <= 0.0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
double remaining = (100.0 - progressPercent) / progressPercent;
|
||||
return (long) (elapsedSeconds * remaining);
|
||||
}
|
||||
|
||||
/**
|
||||
* 상태 메시지 생성
|
||||
*
|
||||
* @param phase 현재 Phase
|
||||
* @param currentEpoch 현재 Epoch
|
||||
* @param totalEpoch 전체 Epoch
|
||||
* @return 상태 메시지
|
||||
*/
|
||||
public static String generateProgressMessage(
|
||||
String phase, Integer currentEpoch, Integer totalEpoch) {
|
||||
if (phase == null) {
|
||||
return "대기중";
|
||||
}
|
||||
|
||||
// Step 구분
|
||||
boolean isStep1 = phase.startsWith("STEP1_");
|
||||
boolean isStep2 = phase.startsWith("STEP2_");
|
||||
String stepName = isStep1 ? "학습" : isStep2 ? "테스트" : "";
|
||||
|
||||
switch (phase) {
|
||||
case "QUEUED":
|
||||
return "학습 작업이 큐에 등록되었습니다.";
|
||||
|
||||
case "STEP1_PREPARING":
|
||||
return "학습 준비 중입니다.";
|
||||
case "STEP1_CONTAINER_STARTING":
|
||||
return "학습용 Docker 컨테이너를 시작하는 중입니다.";
|
||||
case "STEP1_DATA_LOADING":
|
||||
return "학습 데이터를 로딩하는 중입니다.";
|
||||
case "STEP1_TRAINING":
|
||||
if (currentEpoch != null && totalEpoch != null) {
|
||||
return String.format("학습 진행 중 (Epoch %d/%d)", currentEpoch, totalEpoch);
|
||||
}
|
||||
return "학습 진행 중";
|
||||
case "STEP1_PROCESSING_RESULTS":
|
||||
return "학습 결과를 처리하는 중입니다.";
|
||||
|
||||
case "STEP2_PREPARING":
|
||||
return "테스트 준비 중입니다.";
|
||||
case "STEP2_CONTAINER_STARTING":
|
||||
return "테스트용 Docker 컨테이너를 시작하는 중입니다.";
|
||||
case "STEP2_DATA_LOADING":
|
||||
return "테스트 데이터를 로딩하는 중입니다.";
|
||||
case "STEP2_TRAINING":
|
||||
if (currentEpoch != null && totalEpoch != null) {
|
||||
return String.format("테스트 진행 중 (Epoch %d/%d)", currentEpoch, totalEpoch);
|
||||
}
|
||||
return "테스트 진행 중";
|
||||
case "STEP2_PROCESSING_RESULTS":
|
||||
return "테스트 결과를 처리하는 중입니다.";
|
||||
|
||||
case "COMPLETED":
|
||||
return "학습이 성공적으로 완료되었습니다.";
|
||||
case "FAILED":
|
||||
return "학습이 실패했습니다.";
|
||||
case "CANCELED":
|
||||
return "학습이 취소되었습니다.";
|
||||
|
||||
default:
|
||||
return stepName + " 진행 중";
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user