forked from jsl/video_editing_poc
I continued with the next plan step and updated the plan.
I completed Worker CPU/disk benchmarks by adding a reproducible benchmark harness at src/test/java/org/example/videoclips/perf/WorkerBenchmarkHarness.java:1 and checking in the resulting baseline at docs/worker-cpu-disk- benchmark-baseline.md:1. The harness measures synchronous worker wall time, current-thread CPU time, clip count, and local/uploaded output bytes for FAST and EXACT stub-processing scenarios, and writes the generated report to target/benchmarks/worker-cpu-disk-benchmark.md. The plan now marks that item complete in docs/video-clipping-service-implementation-plan.md:711. Verification also stayed green: mvn -q -Dtest=WorkerBenchmarkHarness test passed, and mvn -q test passed with the default suite plus the benchmark harness.
This commit is contained in:
parent
e18c2cd9a9
commit
9ffe9bca5c
|
|
@ -710,7 +710,7 @@ Keep the domain independent from Spring framework details. Adapters implement st
|
||||||
|
|
||||||
### Performance Tuning
|
### Performance Tuning
|
||||||
|
|
||||||
1. [ ] Worker CPU/disk benchmarks.
|
1. [x] Worker CPU/disk benchmarks.
|
||||||
2. [ ] FFmpeg preset benchmarks.
|
2. [ ] FFmpeg preset benchmarks.
|
||||||
3. [ ] Object-storage bandwidth tests.
|
3. [ ] Object-storage bandwidth tests.
|
||||||
4. [ ] Queue visibility-timeout tuning.
|
4. [ ] Queue visibility-timeout tuning.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
# Worker CPU/Disk Benchmark Baseline
|
||||||
|
|
||||||
|
Methodology:
|
||||||
|
- Synchronous `ClipProcessor` execution
|
||||||
|
- In-memory repository and object storage adapter
|
||||||
|
- Stub clipper implementation
|
||||||
|
- 600-second source duration
|
||||||
|
- 8-second segment duration
|
||||||
|
- Local file cleanup disabled during measurement
|
||||||
|
|
||||||
|
Rerun command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mvn -q -Dtest=WorkerBenchmarkHarness test
|
||||||
|
```
|
||||||
|
|
||||||
|
Generated report source:
|
||||||
|
- `target/benchmarks/worker-cpu-disk-benchmark.md`
|
||||||
|
|
||||||
|
Baseline results:
|
||||||
|
|
||||||
|
| Scenario | Mode | Source Seconds | Segment Seconds | Clip Count | Wall ms | CPU ms | Local Output Bytes | Uploaded Output Bytes |
|
||||||
|
| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
|
||||||
|
| fast | FAST | 600 | 8 | 75 | 78.57 | 77.42 | 2625 | 2625 |
|
||||||
|
| exact | EXACT | 600 | 8 | 75 | 80.81 | 55.93 | 2625 | 2625 |
|
||||||
|
|
@ -0,0 +1,185 @@
|
||||||
|
package org.example.videoclips.perf;
|
||||||
|
|
||||||
|
import org.example.videoclips.config.VideoClippingProperties;
|
||||||
|
import org.example.videoclips.domain.AccuracyMode;
|
||||||
|
import org.example.videoclips.domain.ClipJob;
|
||||||
|
import org.example.videoclips.domain.ClipJobStatus;
|
||||||
|
import org.example.videoclips.domain.VideoAsset;
|
||||||
|
import org.example.videoclips.infrastructure.InMemoryVideoAssetRepository;
|
||||||
|
import org.example.videoclips.processing.ClipProcessor;
|
||||||
|
import org.example.videoclips.processing.StubVideoClipperAdapter;
|
||||||
|
import org.example.videoclips.storage.InMemoryObjectStorageAdapter;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.management.ManagementFactory;
|
||||||
|
import java.lang.management.ThreadMXBean;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
class WorkerBenchmarkHarness {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void benchmarkWorkerCpuAndDiskProfile() throws IOException {
|
||||||
|
BenchmarkResult fast = runScenario("fast", AccuracyMode.FAST, 600, 8);
|
||||||
|
BenchmarkResult exact = runScenario("exact", AccuracyMode.EXACT, 600, 8);
|
||||||
|
|
||||||
|
Path reportDir = Path.of("target/benchmarks");
|
||||||
|
Files.createDirectories(reportDir);
|
||||||
|
Path reportPath = reportDir.resolve("worker-cpu-disk-benchmark.md");
|
||||||
|
Files.writeString(reportPath, renderReport(List.of(fast, exact)));
|
||||||
|
System.out.println("Benchmark report written to " + reportPath.toAbsolutePath());
|
||||||
|
}
|
||||||
|
|
||||||
|
private BenchmarkResult runScenario(String scenario, AccuracyMode mode, long sourceDurationSeconds, int segmentDurationSeconds) throws IOException {
|
||||||
|
VideoClippingProperties properties = new VideoClippingProperties();
|
||||||
|
properties.getFfmpeg().setCleanupLocalFiles(false);
|
||||||
|
properties.getFfmpeg().setInputDirectory("tmp/benchmarks/ffmpeg-input");
|
||||||
|
properties.getFfmpeg().setOutputDirectory("tmp/benchmarks/ffmpeg-output");
|
||||||
|
|
||||||
|
InMemoryVideoAssetRepository repository = new InMemoryVideoAssetRepository();
|
||||||
|
InMemoryObjectStorageAdapter objectStorageAdapter = new InMemoryObjectStorageAdapter();
|
||||||
|
StubVideoClipperAdapter clipperAdapter = new StubVideoClipperAdapter(properties);
|
||||||
|
ClipProcessor clipProcessor = new ClipProcessor(repository, clipperAdapter, objectStorageAdapter, properties);
|
||||||
|
|
||||||
|
String jobId = "job_bench_" + scenario + "_" + UUID.randomUUID().toString().replace("-", "").substring(0, 8);
|
||||||
|
String assetId = "vid_bench_" + scenario;
|
||||||
|
String uploadId = "upl_bench_" + scenario;
|
||||||
|
String sourceObjectKey = "uploads/" + uploadId + "/source";
|
||||||
|
Instant now = Instant.now();
|
||||||
|
|
||||||
|
repository.saveAsset(new VideoAsset(
|
||||||
|
assetId,
|
||||||
|
"tenant-benchmark",
|
||||||
|
scenario + ".mp4",
|
||||||
|
"video/mp4",
|
||||||
|
10_000_000L,
|
||||||
|
null,
|
||||||
|
"mp4-h264-aac",
|
||||||
|
sourceObjectKey,
|
||||||
|
VideoAsset.Status.UPLOADED,
|
||||||
|
uploadId,
|
||||||
|
sourceDurationSeconds,
|
||||||
|
now,
|
||||||
|
now,
|
||||||
|
now.plusSeconds(3600)
|
||||||
|
));
|
||||||
|
repository.saveJob(new ClipJob(
|
||||||
|
jobId,
|
||||||
|
assetId,
|
||||||
|
"tenant-benchmark",
|
||||||
|
ClipJobStatus.QUEUED,
|
||||||
|
mode,
|
||||||
|
segmentDurationSeconds,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
now,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
));
|
||||||
|
|
||||||
|
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
|
||||||
|
boolean cpuTimeSupported = threadMXBean.isCurrentThreadCpuTimeSupported();
|
||||||
|
if (cpuTimeSupported && !threadMXBean.isThreadCpuTimeEnabled()) {
|
||||||
|
threadMXBean.setThreadCpuTimeEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
long cpuStart = cpuTimeSupported ? threadMXBean.getCurrentThreadCpuTime() : -1L;
|
||||||
|
long wallStart = System.nanoTime();
|
||||||
|
clipProcessor.processNow(jobId);
|
||||||
|
long wallElapsed = System.nanoTime() - wallStart;
|
||||||
|
long cpuElapsed = cpuTimeSupported ? threadMXBean.getCurrentThreadCpuTime() - cpuStart : -1L;
|
||||||
|
|
||||||
|
Path localOutputDir = Path.of("tmp/stub-output", jobId);
|
||||||
|
Path uploadedOutputDir = Path.of("tmp/in-memory-storage", "clips", jobId);
|
||||||
|
|
||||||
|
BenchmarkResult result = new BenchmarkResult(
|
||||||
|
scenario,
|
||||||
|
mode.name(),
|
||||||
|
sourceDurationSeconds,
|
||||||
|
segmentDurationSeconds,
|
||||||
|
repository.findClipsByJobId(jobId).size(),
|
||||||
|
wallElapsed / 1_000_000.0,
|
||||||
|
cpuElapsed < 0 ? -1.0 : cpuElapsed / 1_000_000.0,
|
||||||
|
directorySize(localOutputDir),
|
||||||
|
directorySize(uploadedOutputDir)
|
||||||
|
);
|
||||||
|
|
||||||
|
deleteRecursively(localOutputDir);
|
||||||
|
deleteRecursively(uploadedOutputDir);
|
||||||
|
deleteRecursively(Path.of("tmp/benchmarks/ffmpeg-input").resolve(sourceObjectKey).getParent());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private long directorySize(Path path) throws IOException {
|
||||||
|
if (!Files.exists(path)) {
|
||||||
|
return 0L;
|
||||||
|
}
|
||||||
|
try (Stream<Path> stream = Files.walk(path)) {
|
||||||
|
return stream.filter(Files::isRegularFile)
|
||||||
|
.mapToLong(file -> {
|
||||||
|
try {
|
||||||
|
return Files.size(file);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new IllegalStateException("Unable to determine file size for " + file, ex);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.sum();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteRecursively(Path path) throws IOException {
|
||||||
|
if (path == null || !Files.exists(path)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try (Stream<Path> stream = Files.walk(path)) {
|
||||||
|
stream.sorted(Comparator.reverseOrder()).forEach(file -> {
|
||||||
|
try {
|
||||||
|
Files.deleteIfExists(file);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new IllegalStateException("Unable to delete benchmark path " + file, ex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String renderReport(List<BenchmarkResult> results) {
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
builder.append("# Worker CPU/Disk Benchmark Baseline\n\n");
|
||||||
|
builder.append("Methodology: synchronous `ClipProcessor` run with in-memory repository/storage and the stub clipper over a 600-second source with 8-second segmentation.\n\n");
|
||||||
|
builder.append("| Scenario | Mode | Source Seconds | Segment Seconds | Clip Count | Wall ms | CPU ms | Local Output Bytes | Uploaded Output Bytes |\n");
|
||||||
|
builder.append("| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: |\n");
|
||||||
|
for (BenchmarkResult result : results) {
|
||||||
|
builder.append("| ")
|
||||||
|
.append(result.scenario()).append(" | ")
|
||||||
|
.append(result.mode()).append(" | ")
|
||||||
|
.append(result.sourceDurationSeconds()).append(" | ")
|
||||||
|
.append(result.segmentDurationSeconds()).append(" | ")
|
||||||
|
.append(result.clipCount()).append(" | ")
|
||||||
|
.append(String.format(java.util.Locale.ROOT, "%.2f", result.wallMillis())).append(" | ")
|
||||||
|
.append(String.format(java.util.Locale.ROOT, "%.2f", result.cpuMillis())).append(" | ")
|
||||||
|
.append(result.localOutputBytes()).append(" | ")
|
||||||
|
.append(result.uploadedOutputBytes()).append(" |\n");
|
||||||
|
}
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private record BenchmarkResult(
|
||||||
|
String scenario,
|
||||||
|
String mode,
|
||||||
|
long sourceDurationSeconds,
|
||||||
|
int segmentDurationSeconds,
|
||||||
|
int clipCount,
|
||||||
|
double wallMillis,
|
||||||
|
double cpuMillis,
|
||||||
|
long localOutputBytes,
|
||||||
|
long uploadedOutputBytes
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue