daniel 작업본 수정

This commit is contained in:
2026-04-09 14:21:17 +09:00
parent bb48cb8610
commit ce404924fd
4 changed files with 258 additions and 37 deletions

View File

@@ -622,7 +622,6 @@ public class FileManagerService {
final int[] fileCount = {0};
final int[] directoryCount = {0};
log.info("[StorageSpace] walkFileTree 시작 - path={}", directoryPath);
try {
Files.walkFileTree(
directory,
@@ -642,12 +641,6 @@ public class FileManagerService {
return FileVisitResult.CONTINUE;
}
});
log.info(
"[StorageSpace] walkFileTree 완료 - fileCount={}, dirCount={}, usedSize={} bytes ({})",
fileCount[0],
directoryCount[0],
usedSize[0],
formatFileSize(usedSize[0]));
} catch (IOException e) {
log.error("디렉토리 용량 계산 중 오류 발생: {}", directoryPath, e);
return FileManagerDto.StorageSpaceRes.builder()
@@ -666,7 +659,7 @@ public class FileManagerService {
.build();
}
// 디스크 공간 정보 조회 (FileStore 사용) — basePath가 속한 파티션/NFS 마운트 전체 기준
// 디스크 공간 정보 조회 (FileStore 사용)
long totalDiskSpace = 0;
long freeSpace = 0;
long usableSpace = 0;
@@ -674,28 +667,17 @@ public class FileManagerService {
try {
java.nio.file.FileStore fileStore = Files.getFileStore(directory);
totalDiskSpace = fileStore.getTotalSpace();
freeSpace = fileStore.getUnallocatedSpace(); // OS 미할당 공간 (root 예약 블록 포함 가능)
usableSpace = fileStore.getUsableSpace(); // 실제 프로세스가 사용 가능한 공간
log.info(
"[StorageSpace] FileStore - name={}, type={}, total={} bytes ({}), unallocated={} bytes ({}), usable={} bytes ({})",
fileStore.name(),
fileStore.type(),
totalDiskSpace,
formatFileSize(totalDiskSpace),
freeSpace,
formatFileSize(freeSpace),
usableSpace,
formatFileSize(usableSpace));
totalDiskSpace = fileStore.getTotalSpace(); // 전체 디스크 용량
freeSpace = fileStore.getUnallocatedSpace(); // 남은 저장공간 (할당되지 않은 공간)
usableSpace = fileStore.getUsableSpace(); // 사용 가능한 공간 (실제 사용 가능)
// 사용률은 usableSpace 기준으로 계산 (더 정확한 실사용 가능 공간 반영)
// 디스크 사용률 계산
if (totalDiskSpace > 0) {
long usedDiskSpace = totalDiskSpace - usableSpace;
long usedDiskSpace = totalDiskSpace - freeSpace;
usagePercentage = (usedDiskSpace * 100.0) / totalDiskSpace;
}
log.info("[StorageSpace] usagePercentage={}%", Math.round(usagePercentage * 100.0) / 100.0);
} catch (IOException e) {
log.warn("[StorageSpace] 디스크 공간 정보 조회 실패: {}", directoryPath, e);
log.debug("디스크 공간 정보 조회 중 오류 발생: {}", directoryPath, e);
// 디스크 정보 조회 실패 시에도 디렉토리 용량은 반환
}

View File

@@ -220,7 +220,20 @@ public class ModelTestMetricsJobRepositoryImpl extends QuerydslRepositorySupport
// Train 메트릭 (Loss, LR, Duration)
modelMetricsTrainEntity.loss,
modelMetricsTrainEntity.lr,
modelMetricsTrainEntity.durationTime)))
modelMetricsTrainEntity.durationTime),
Projections.constructor(
com.kamco.cd.training.train.dto.ModelTrainMetricsDto.TestMetrics.class,
modelMetricsTestEntity.model1,
modelMetricsTestEntity.tp,
modelMetricsTestEntity.fp,
modelMetricsTestEntity.fn,
modelMetricsTestEntity.precisions,
modelMetricsTestEntity.recall,
modelMetricsTestEntity.f1Score,
modelMetricsTestEntity.accuracy,
modelMetricsTestEntity.iou,
modelMetricsTestEntity.detectionCount,
modelMetricsTestEntity.gtCount)))
.from(modelMetricsValidationEntity)
.innerJoin(modelMasterEntity)
.on(modelMetricsValidationEntity.model.id.eq(modelMasterEntity.id))
@@ -228,6 +241,8 @@ public class ModelTestMetricsJobRepositoryImpl extends QuerydslRepositorySupport
.on(
modelMetricsTrainEntity.model.id.eq(modelMasterEntity.id),
modelMetricsTrainEntity.epoch.eq(epoch))
.leftJoin(modelMetricsTestEntity)
.on(modelMetricsTestEntity.model.id.eq(modelId))
.where(
modelMetricsValidationEntity.model.id.eq(modelId),
modelMetricsValidationEntity.epoch.eq(epoch))

View File

@@ -3,7 +3,6 @@ package com.kamco.cd.training.train.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import java.nio.file.Path;
import java.util.Properties;
import java.util.UUID;
import lombok.AllArgsConstructor;
import lombok.Getter;
@@ -51,6 +50,53 @@ public class ModelTrainMetricsDto {
private Integer epoch; // epoch 번호
private Properties properties;
@JsonProperty("test_metrics")
private TestMetrics testMetrics; // 테스트 메트릭 추가
// 기존 방식용 생성자 (metricType, epoch, testMetrics 없음)
public ModelMetricJsonDto(String cdModelType, String modelVersion, Properties properties) {
this.cdModelType = cdModelType;
this.modelVersion = modelVersion;
this.metricType = null;
this.epoch = null;
this.properties = properties;
this.testMetrics = null;
}
}
@Getter
@AllArgsConstructor
@NoArgsConstructor
public static class TestMetrics {
private String model; // 모델명
@JsonProperty("true_positive")
private Long tp; // True Positive
@JsonProperty("false_positive")
private Long fp; // False Positive
@JsonProperty("false_negative")
private Long fn; // False Negative
private Float precisions; // Precision
private Float recall; // Recall
@JsonProperty("f1_score")
private Float f1Score; // F1 Score
private Float accuracy; // Accuracy
private Float iou; // IoU
@JsonProperty("detection_count")
private Long detectionCount; // Detection Count
@JsonProperty("gt_count")
private Long gtCount; // Ground Truth Count
}
@Getter
@@ -106,6 +152,25 @@ public class ModelTrainMetricsDto {
@JsonProperty("duration_time")
private Float durationTime;
// 기존 방식용 생성자 (getTestMetricPackingInfo에서 사용)
public Properties(Float f1Score, Float precisions, Float recall, Float iou, Double loss) {
this.changedFscore = f1Score;
this.changedPrecision = precisions;
this.changedRecall = recall;
this.unchangedFscore = null;
this.unchangedPrecision = null;
this.unchangedRecall = null;
this.mFscore = null;
this.mPrecision = null;
this.mRecall = null;
this.mIou = iou;
this.mAcc = null;
this.aAcc = null;
this.loss = loss;
this.lr = null;
this.durationTime = null;
}
}
@Getter

View File

@@ -6,6 +6,7 @@ import com.kamco.cd.training.common.enums.TrainStatusType;
import com.kamco.cd.training.postgres.core.ModelTestMetricsJobCoreService;
import com.kamco.cd.training.train.dto.ModelTrainMetricsDto.BestPthInfo;
import com.kamco.cd.training.train.dto.ModelTrainMetricsDto.ModelMetricJsonDto;
import com.kamco.cd.training.train.dto.ModelTrainMetricsDto.ModelTestFileName;
import com.kamco.cd.training.train.dto.ModelTrainMetricsDto.ResponsePathDto;
import java.io.BufferedReader;
import java.io.IOException;
@@ -18,6 +19,7 @@ import java.nio.file.StandardOpenOption;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@@ -156,14 +158,19 @@ public class ModelTestMetricsJobService {
modelInfo.getModelId(),
bestPthFiles.stream().map(BestPthInfo::getFileName).collect(Collectors.toList()));
// 2. 각 Best PTH별로 개별 ZIP 생성
// 2. 기존 방식: F-Score 기준 통합 ZIP 생성 (1개)
createLegacyZipFile(modelInfo, responsePath);
// 3. 신규 방식: 각 Best PTH별로 개별 ZIP 생성 (3개)
createIndividualZipFiles(modelInfo, bestPthFiles, responsePath);
modelTestMetricsJobCoreService.updatePackingEnd(
modelInfo.getModelId(), ZonedDateTime.now(), TrainStatusType.COMPLETED.getId());
log.info(
"모든 ZIP 파일 생성 완료: modelId={}, zipCount={}", modelInfo.getModelId(), bestPthFiles.size());
"모든 ZIP 파일 생성 완료: modelId={}, 기존방식=1개, 개별방식={}",
modelInfo.getModelId(),
bestPthFiles.size());
} catch (Exception e) {
log.error("ZIP 파일 생성 실패: modelId={}", modelInfo.getModelId(), e);
@@ -213,14 +220,109 @@ public class ModelTestMetricsJobService {
}
/**
* Response 폴더에서 모든 best_changed_*.pth 파일 찾기
* 기존 방식: F-Score 기준 통합 ZIP 파일 생성 파일명: {modelNo}.{modelVer}.{uuid}.zip 포함 파일: model_config.py,
* best_changed_fscore_epoch_N.pth, {modelVersion}.json, yolov8_6th-6m.pt
*
* @param modelInfo 모델 정보
* @param responsePath Response 디렉토리 경로
*/
private void createLegacyZipFile(ResponsePathDto modelInfo, Path responsePath) {
try {
log.info("기존 방식 ZIP 파일 생성 시작: modelId={}", modelInfo.getModelId());
// 1. Test 메트릭 기반 JSON 생성 (기존 getTestMetricPackingInfo 사용)
ModelMetricJsonDto jsonDto =
modelTestMetricsJobCoreService.getTestMetricPackingInfo(modelInfo.getModelId());
if (jsonDto == null) {
log.warn("Test 메트릭 정보를 찾을 수 없습니다: modelId={}", modelInfo.getModelId());
return;
}
// 2. JSON 파일명: {modelVersion}.json (예: G4_000001.json)
String legacyJsonFileName = jsonDto.getModelVersion() + ".json";
Path legacyJsonPath = responsePath.resolve(legacyJsonFileName);
writeJsonFile(jsonDto, legacyJsonPath);
log.info("JSON 파일 생성 완료: {}", legacyJsonFileName);
// 3. Best Epoch 파일명 찾기 (F-Score 기준)
ModelTestFileName fileInfo =
modelTestMetricsJobCoreService.findModelTestFileNames(modelInfo.getModelId());
if (fileInfo == null || fileInfo.getBestEpochFileName() == null) {
log.warn("Best Epoch 파일명을 찾을 수 없습니다: modelId={}", modelInfo.getModelId());
return;
}
log.info("Best Epoch 파일명: {}.pth", fileInfo.getBestEpochFileName());
// 4. ZIP에 포함할 파일 리스트
Set<String> targetNames =
Set.of(
"model_config.py",
fileInfo.getBestEpochFileName() + ".pth", // best_changed_fscore_epoch_N.pth
legacyJsonFileName); // {modelVersion}.json
List<Path> filesToZip = new ArrayList<>();
// Response 폴더에서 파일 수집
try (Stream<Path> stream = Files.list(responsePath)) {
filesToZip.addAll(
stream
.filter(Files::isRegularFile)
.filter(p -> targetNames.contains(p.getFileName().toString()))
.collect(Collectors.toList()));
}
log.info("Response 폴더에서 수집한 파일 {}개", filesToZip.size());
// PT 파일 추가 (사전학습 모델)
Path ptPath = findPretrainedModel();
if (ptPath != null) {
filesToZip.add(ptPath);
log.info("사전학습 모델 추가: {}", ptPath.getFileName());
} else {
log.warn("사전학습 모델(.pt) 파일을 찾을 수 없습니다.");
}
// 5. ZIP 파일명 생성: {modelNo}.{modelVer}.{uuid}.zip
String legacyZipFileName =
String.format(
"%s.zip", jsonDto.getModelVersion() // G4.G4_000001.uuid
);
Path legacyZipPath = responsePath.resolve(legacyZipFileName);
// 6. ZIP 압축
zipFiles(filesToZip, legacyZipPath);
long zipSize = Files.size(legacyZipPath);
log.info("기존 방식 ZIP 파일 생성 완료: fileName={}, size={} bytes", legacyZipFileName, zipSize);
} catch (Exception e) {
log.error("기존 방식 ZIP 파일 생성 실패: modelId={}", modelInfo.getModelId(), e);
// 에러가 발생해도 신규 방식 ZIP은 계속 생성되도록 throw 하지 않음
}
}
/**
* Response 폴더에서 모든 best_changed_*.pth 파일 찾기 패턴: best_changed_{metricType}_epoch_{N}.pth 예:
* best_changed_fscore_epoch_3.pth, best_changed_precision_epoch_2.pth,
* best_changed_accuracy_epoch_5.pth 등
*
* @param responsePath Response 디렉토리 경로
* @return Best PTH 파일 정보 리스트
*/
private List<BestPthInfo> findAllBestPthFiles(Path responsePath) throws IOException {
List<BestPthInfo> bestFiles = new ArrayList<>();
Pattern pattern = Pattern.compile("best_changed_(fscore|precision|recall)_epoch_(\\d+)\\.pth");
// 개선: 모든 메트릭 타입을 자동으로 감지하는 유연한 패턴
// best_changed_{어떤문자든}_epoch_{숫자}.pth 형식의 모든 파일 검색
Pattern pattern = Pattern.compile("best_changed_(.+?)_epoch_(\\d+)\\.pth");
log.info("📂 Best PTH 파일 검색 시작: path={}", responsePath);
log.info("🔍 검색 패턴: best_changed_{{metricType}}_epoch_{{N}}.pth");
try (Stream<Path> stream = Files.list(responsePath)) {
stream
@@ -230,14 +332,47 @@ public class ModelTestMetricsJobService {
String fileName = file.getFileName().toString();
Matcher matcher = pattern.matcher(fileName);
if (matcher.matches()) {
String metricType = matcher.group(1); // fscore, precision, recall
String metricType =
matcher.group(1); // 메트릭 타입 (fscore, precision, recall, accuracy 등)
Integer epoch = Integer.parseInt(matcher.group(2));
log.info(
"Best PTH 파일 발견: file={}, metricType={}, epoch={}",
fileName,
metricType,
epoch);
bestFiles.add(new BestPthInfo(fileName, metricType, epoch, file));
}
});
}
log.info("Best PTH 파일 검색 완료: 총 {}개 발견", bestFiles.size());
if (bestFiles.isEmpty()) {
log.warn(" Best PTH 파일이 하나도 발견되지 않았습니다. 파일명 패턴을 확인하세요.");
log.warn(" 예상 패턴: best_changed_{{metricType}}_epoch_{{N}}.pth");
// 디버깅: response 폴더의 모든 .pth 파일 출력
try (Stream<Path> debugStream = Files.list(responsePath)) {
List<String> allPthFiles =
debugStream
.filter(Files::isRegularFile)
.map(p -> p.getFileName().toString())
.filter(name -> name.endsWith(".pth"))
.collect(Collectors.toList());
if (!allPthFiles.isEmpty()) {
log.info(" Response 폴더의 .pth 파일 목록:");
allPthFiles.forEach(name -> log.info(" - {}", name));
} else {
log.warn(" Response 폴더에 .pth 파일이 전혀 없습니다.");
}
} catch (IOException e) {
log.error("디버깅 중 에러 발생", e);
}
}
return bestFiles;
}
@@ -268,6 +403,13 @@ public class ModelTestMetricsJobService {
for (BestPthInfo bestPth : bestPthFiles) {
try {
log.info(
"ZIP 파일 생성 시작: modelId={}, metricType={}, epoch={}, pthFile={}",
modelInfo.getModelId(),
bestPth.getMetricType(),
bestPth.getEpoch(),
bestPth.getFileName());
// 1. 메트릭 JSON 생성
ModelMetricJsonDto metricJson =
modelTestMetricsJobCoreService.getMetricsByEpoch(
@@ -282,10 +424,20 @@ public class ModelTestMetricsJobService {
continue;
}
log.info(
"메트릭 JSON 조회 성공: modelId={}, metricType={}, epoch={}, cdModelType={}, modelVersion={}",
modelInfo.getModelId(),
metricJson.getMetricType(),
metricJson.getEpoch(),
metricJson.getCdModelType(),
metricJson.getModelVersion());
String jsonFileName = bestPth.getMetricType() + "_metrics.json";
Path jsonPath = responsePath.resolve(jsonFileName);
writeJsonFile(metricJson, jsonPath);
log.info("JSON 파일 생성 완료: {}", jsonFileName);
// 2. ZIP에 포함할 파일 리스트
List<Path> filesToZip = new ArrayList<>();
filesToZip.add(modelConfigPath); // model_config.py
@@ -293,25 +445,32 @@ public class ModelTestMetricsJobService {
filesToZip.add(jsonPath); // {type}_metrics.json
filesToZip.add(ptPath); // yolov8_6th-6m.pt
// 3. ZIP 파일명 생성
// 3. ZIP 파일명 생성 (하이퍼파라미터별 구분)
String zipFileName =
String.format(
"%s.%s.zip",
metricJson.getModelVersion(), // ex) G1.G1_000001.dfdaj-dlks-fjad-dkdajfl
bestPth.getMetricType()); // ex) fscore/precision/recall
metricJson.getModelVersion(), // G1.G1_000001.{uuid}
bestPth.getMetricType()); // fscore/precision/recall
Path zipPath = responsePath.resolve(zipFileName);
// 4. ZIP 압축
zipFiles(filesToZip, zipPath);
log.info("ZIP 파일 생성 완료: {}", zipPath);
long zipSize = Files.size(zipPath);
log.info(
"ZIP 파일 생성 완료: path={}, size={} bytes, metricType={}, epoch={}",
zipPath,
zipSize,
bestPth.getMetricType(),
bestPth.getEpoch());
} catch (Exception e) {
log.error(
"ZIP 파일 생성 실패: metricType={}, epoch={}",
"ZIP 파일 생성 실패: metricType={}, epoch={}, pthFile={}",
bestPth.getMetricType(),
bestPth.getEpoch(),
bestPth.getFileName(),
e);
throw new RuntimeException(e);
}