daniel 작업본 수정
This commit is contained in:
@@ -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);
|
||||
// 디스크 정보 조회 실패 시에도 디렉토리 용량은 반환
|
||||
}
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user