Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ Executes a benchmark test with the specified workload type.
- `--threads, -t`: Number of parallel client threads (default: 1)
- `--object-size, -o`: Size of each object (e.g., 1MB, 256KB, 4GB)
- `--part-size`: Enable S3 multipart upload with the given part size (e.g., 5MB, 64MB). When set, `load.batch.size` is forced to `1`. The engine automatically retries individual parts (up to 3 times) and aborts incomplete uploads on failure. Per-part checksums are applied when checksum is enabled. See [`SPT_SYNTAX.md`](docs/SPT_SYNTAX.md) for details
- `--checksum`: Enable S3 checksum validation with the specified algorithm: `crc32`, `crc32c`, `sha1`, `sha256`. When used with `--part-size`, checksums are applied per part. (env: `SPT_CHECKSUM`)
- `--checksum`: Enable S3 checksum validation with the specified algorithm: `crc32`, `crc32c`, `sha1`, `sha256`, `crc64-nvme`. When used with `--part-size`, checksums are applied per part. `crc64-nvme` currently requires `--s3-driver aws`. (env: `SPT_CHECKSUM`)
- `--object-data-compressibility`: Target compressibility percentage for generated object data, 0-100 (default: 0 = fully random). Each 4KB chunk is split into random and zero-filled portions according to the percentage. (env: `SPT_OBJECT_DATA_COMPRESSIBILITY`)
- `--object-data-dedupable`: Whether generated data remains dedupe-friendly (default: true). Set `false` to stamp every 4KB with a unique object-id + offset header that defeats inline deduplication. Incompatible with `--items-file` / file-based data input. (env: `SPT_OBJECT_DATA_DEDUPABLE`)
- `--cleanup`: Delete all created objects after test completion
Expand Down
2 changes: 1 addition & 1 deletion cli/cmd/envdefaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ func applyEnvDefaultsToRunFlags(cmd *cobra.Command) error {
}
}

// Checksum algorithm: SPT_CHECKSUM env var (values: crc32, crc32c, sha1, sha256)
// Checksum algorithm: SPT_CHECKSUM env var (values: crc32, crc32c, sha1, sha256, crc64-nvme)
_ = setIf("checksum", constants.EnvChecksum)

// Object data shaping
Expand Down
9 changes: 6 additions & 3 deletions cli/cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -929,7 +929,7 @@ Shorthand: --use-rdma is equivalent to --s3-driver rdma. (env: SPT_S3_DRIVER)`)

// Checksum Options
runCmd.Flags().String("checksum", "",
`Enable checksum validation with the specified algorithm: crc32, crc32c, sha1, sha256.
`Enable checksum validation with the specified algorithm: crc32, crc32c, sha1, sha256, crc64-nvme.
Omit to disable checksums. (env: SPT_CHECKSUM)`)

// S3 Tables Options
Expand Down Expand Up @@ -1169,10 +1169,13 @@ func buildScenarioParams(workloadType string, cmd *cobra.Command) (scenario.Para
checksumAlgo = strings.ToLower(strings.TrimSpace(checksumAlgo))
switch checksumAlgo {
case scenario.ChecksumCRC32, scenario.ChecksumCRC32C,
scenario.ChecksumSHA1, scenario.ChecksumSHA256:
scenario.ChecksumSHA1, scenario.ChecksumSHA256, scenario.ChecksumCRC64NVME:
// valid
default:
return params, fmt.Errorf("invalid --checksum value %q: must be one of: crc32, crc32c, sha1, sha256", checksumAlgo)
return params, fmt.Errorf("invalid --checksum value %q: must be one of: crc32, crc32c, sha1, sha256, crc64-nvme", checksumAlgo)
}
if checksumAlgo == scenario.ChecksumCRC64NVME && s3Driver != scenario.S3DriverAws {
return params, fmt.Errorf("invalid --checksum value %q for --s3-driver %q: crc64-nvme requires --s3-driver aws", checksumAlgo, s3Driver)
}
params.Checksum = checksumAlgo
}
Expand Down
54 changes: 54 additions & 0 deletions cli/cmd/run_scenario_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,7 @@ func TestBuildScenarioParams(t *testing.T) {
cmd.Flags().String("rdma-device", "auto", "")
cmd.Flags().String("rdma-log-level", "WARN", "")
cmd.Flags().Int64("rdma-timeout-ms", 30000, "")
cmd.Flags().String("checksum", "", "")
cmd.Flags().Float64("object-data-compressibility", 0.0, "")
cmd.Flags().Bool("object-data-dedupable", true, "")

Expand Down Expand Up @@ -576,6 +577,7 @@ func TestBuildScenarioParamsEdgeCases(t *testing.T) {
cmd.Flags().String("rdma-device", "auto", "")
cmd.Flags().String("rdma-log-level", "WARN", "")
cmd.Flags().Int64("rdma-timeout-ms", 30000, "")
cmd.Flags().String("checksum", "", "")
cmd.Flags().Float64("object-data-compressibility", 0.0, "")
cmd.Flags().Bool("object-data-dedupable", true, "")

Expand Down Expand Up @@ -627,6 +629,7 @@ func TestBuildScenarioParamsServiceThreads(t *testing.T) {
cmd.Flags().String("rdma-device", "auto", "")
cmd.Flags().String("rdma-log-level", "WARN", "")
cmd.Flags().Int64("rdma-timeout-ms", 30000, "")
cmd.Flags().String("checksum", "", "")

_ = cmd.Flags().Set("service-threads", fmt.Sprintf("%d", tt.value))

Expand Down Expand Up @@ -666,6 +669,7 @@ func TestBuildScenarioParams_S3DriverFlag(t *testing.T) {
cmd.Flags().String("rdma-device", "auto", "")
cmd.Flags().String("rdma-log-level", "WARN", "")
cmd.Flags().Int64("rdma-timeout-ms", 30000, "")
cmd.Flags().String("checksum", "", "")
cmd.Flags().Float64("object-data-compressibility", 0.0, "")
cmd.Flags().Bool("object-data-dedupable", true, "")
return cmd
Expand Down Expand Up @@ -730,6 +734,56 @@ func TestBuildScenarioParams_S3DriverFlag(t *testing.T) {
}
})

t.Run("--checksum crc64-nvme with --s3-driver aws is accepted", func(t *testing.T) {
cmd := newCmd()
_ = cmd.Flags().Set("s3-driver", "aws")
_ = cmd.Flags().Set("checksum", "crc64-nvme")
p, err := buildScenarioParams("mock", cmd)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if p.Checksum != "crc64-nvme" {
t.Errorf("Checksum = %q, want %q", p.Checksum, "crc64-nvme")
}
})

t.Run("--checksum crc64-nvme with default driver fails fast", func(t *testing.T) {
cmd := newCmd()
_ = cmd.Flags().Set("checksum", "crc64-nvme")
_, err := buildScenarioParams("mock", cmd)
if err == nil {
t.Fatal("expected validation error")
}
if !strings.Contains(err.Error(), "requires --s3-driver aws") {
t.Errorf("expected aws conflict message, got: %v", err)
}
})

t.Run("--checksum crc64-nvme with rdma fails fast", func(t *testing.T) {
cmd := newCmd()
_ = cmd.Flags().Set("s3-driver", "rdma")
_ = cmd.Flags().Set("checksum", "crc64-nvme")
_, err := buildScenarioParams("mock", cmd)
if err == nil {
t.Fatal("expected validation error")
}
if !strings.Contains(err.Error(), "requires --s3-driver aws") {
t.Errorf("expected aws conflict message, got: %v", err)
}
})

t.Run("invalid --checksum value lists crc64-nvme", func(t *testing.T) {
cmd := newCmd()
_ = cmd.Flags().Set("checksum", "blake3")
_, err := buildScenarioParams("mock", cmd)
if err == nil {
t.Fatal("expected validation error")
}
if !strings.Contains(err.Error(), "crc64-nvme") {
t.Errorf("expected crc64-nvme in validation list, got: %v", err)
}
})

t.Run("object data knobs are parsed", func(t *testing.T) {
cmd := newCmd()
_ = cmd.Flags().Set("object-data-compressibility", "75")
Expand Down
4 changes: 2 additions & 2 deletions cli/docs/SPT_SYNTAX.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ You can use these variables to avoid repeating sensitive or commonly used parame
- **Docker:** `SPT_SKIP_IMAGE_PULL` (skip pulling the engine image)
- **Engine tuning:** `SPT_SERVICE_THREADS` (virtual-thread carrier parallelism)
- **Multipart upload:** `SPT_PART_SIZE` (part size, e.g. `64MB`)
- **Checksum:** `SPT_CHECKSUM` (algorithm: `crc32`, `crc32c`, `sha1`, `sha256`)
- **Checksum:** `SPT_CHECKSUM` (algorithm: `crc32`, `crc32c`, `sha1`, `sha256`, `crc64-nvme`; `crc64-nvme` requires `SPT_S3_DRIVER=aws` or `--s3-driver aws`)
- **Data shaping:** `SPT_OBJECT_DATA_COMPRESSIBILITY` (0-100, default 0), `SPT_OBJECT_DATA_DEDUPABLE` (true/false, default true)
- **Storage driver:** `SPT_S3_DRIVER` (driver backend: `default`, `aws`, `rdma`)
- **RDMA:** `SPT_RDMA_ENABLED`, `RDMA_LOCAL_IP`, `RDMA_DEVICE`, `RDMA_LOG_LEVEL`, `RDMA_THRESHOLD_BYTES`, `RDMA_TIMEOUT_MS`, `RDMA_FALLBACK_ENABLED`
Expand Down Expand Up @@ -97,7 +97,7 @@ Required for S3 workloads, optional/ignored for `mock`.
| `--object-count` | `-n` | `0` | Fixed number of objects to process |
| `--duration` | `-d` | `""` | Fixed time duration (e.g., `5m`, `1h`) |
| `--seed-objects` | | `2500` | Objects to pre-create for `read` benchmarks |
| `--checksum` | | `""` | Enable S3 checksum validation with the specified algorithm: `crc32`, `crc32c`, `sha1`, `sha256`. Omit to disable checksums. When set with `--part-size`, checksums are applied per part. (env: `SPT_CHECKSUM`) |
| `--checksum` | | `""` | Enable S3 checksum validation with the specified algorithm: `crc32`, `crc32c`, `sha1`, `sha256`, `crc64-nvme`. Omit to disable checksums. When set with `--part-size`, checksums are applied per part. `crc64-nvme` currently requires `--s3-driver aws`. (env: `SPT_CHECKSUM`) |
| `--object-data-compressibility` | | `0` | Target compressibility percentage for generated object data (0-100). Each 4KB chunk is split into random and zero-filled portions. 0 = fully random, 100 = fully compressible. (env: `SPT_OBJECT_DATA_COMPRESSIBILITY`) |
| `--object-data-dedupable` | | `true` | Whether generated data remains dedupe-friendly. Set `false` to stamp every 4KB with a 16-byte object-id + offset header that practically eliminates inline deduplication. Incompatible with file-based data input. (env: `SPT_OBJECT_DATA_DEDUPABLE`) |
| `--save-items` | | `false` | Save `items.csv` listing created objects (`write` only) |
Expand Down
2 changes: 2 additions & 0 deletions cli/internal/scenario/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ const (
ChecksumSHA1 = "sha1"
// ChecksumSHA256 selects SHA-256 checksum validation.
ChecksumSHA256 = "sha256"
// ChecksumCRC64NVME selects CRC64-NVME checksum validation.
ChecksumCRC64NVME = "crc64-nvme"

itemTypeData = "data"
itemTypePath = "path"
Expand Down
26 changes: 26 additions & 0 deletions cli/internal/scenario/defaults_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -967,6 +967,32 @@ func TestGenerateDefaults(t *testing.T) {
}
},
},
{
name: "checksum crc64-nvme",
params: Params{
WorkloadType: "write",
Endpoint: "http://s3.example.com",
AccessKey: "key",
SecretKey: "secret",
Bucket: "bucket",
Threads: 4,
Checksum: "crc64-nvme",
},
wantErr: false,
checkOutput: func(t *testing.T, data []byte) {
t.Helper()
var config DefaultsConfig
if err := yaml.Unmarshal(data, &config); err != nil {
t.Fatalf("Failed to unmarshal YAML: %v", err)
}
if config.Storage.Checksum == nil {
t.Fatal("Expected storage.checksum section")
}
if config.Storage.Checksum.Algorithm != "crc64-nvme" {
t.Errorf("Expected algorithm 'crc64-nvme', got %q", config.Storage.Checksum.Algorithm)
}
},
},
{
name: "no checksum omits checksum section",
params: Params{
Expand Down
2 changes: 1 addition & 1 deletion cli/internal/scenario/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ type Params struct {
RdmaTimeoutMs int64 // RDMA operation timeout (default: 30000)

// Checksum validation
Checksum string // Checksum algorithm: crc32, crc32c, sha1, sha256 (empty = disabled)
Checksum string // Checksum algorithm: crc32, crc32c, sha1, sha256, crc64-nvme (empty = disabled)

ObjectDataCompressibility float64 // Object data compressibility percentage [0..100]
ObjectDataDedupable bool // True keeps repeating ring-buffer data dedupe-friendly; false enables anti-dedupe stamping
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ S3StorageDriver driver = S3StorageDriverFactory.create(config);
| connectionTimeout | int | 10000 | Connection timeout in milliseconds |
| enableRequestMetrics | boolean | false | Enable AWS SDK request metrics |
| checksumEnabled | boolean | false | Compute and send a checksum on write requests |
| checksumAlgorithm | String | null | Checksum algorithm: `crc32`, `crc32c`, `sha1`, `sha256`. MD5 is not supported as a flexible checksum by the AWS SDK |
| checksumAlgorithm | String | null | Checksum algorithm: `crc32`, `crc32c`, `sha1`, `sha256`, `crc64-nvme`. MD5 is not supported as a flexible checksum by the AWS SDK |

## API Methods

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@ static ChecksumAlgorithm resolveChecksumAlgorithm(final String algorithm) {
return ChecksumAlgorithm.SHA1;
case "sha256":
return ChecksumAlgorithm.SHA256;
case "crc64-nvme":
return ChecksumAlgorithm.CRC64_NVME;
default:
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1510,12 +1510,18 @@ void sha256() {
assertEquals(ChecksumAlgorithm.SHA256, S3AwsStorageDriver.resolveChecksumAlgorithm("sha256"));
}

@Test
void crc64Nvme() {
assertEquals(ChecksumAlgorithm.CRC64_NVME, S3AwsStorageDriver.resolveChecksumAlgorithm("crc64-nvme"));
}

@Test
void caseInsensitive() {
assertEquals(ChecksumAlgorithm.CRC32, S3AwsStorageDriver.resolveChecksumAlgorithm("CRC32"));
assertEquals(ChecksumAlgorithm.CRC32_C, S3AwsStorageDriver.resolveChecksumAlgorithm("CRC32C"));
assertEquals(ChecksumAlgorithm.SHA1, S3AwsStorageDriver.resolveChecksumAlgorithm("SHA1"));
assertEquals(ChecksumAlgorithm.SHA256, S3AwsStorageDriver.resolveChecksumAlgorithm("SHA256"));
assertEquals(ChecksumAlgorithm.CRC64_NVME, S3AwsStorageDriver.resolveChecksumAlgorithm("CRC64-NVME"));
}

@Test
Expand Down Expand Up @@ -1610,6 +1616,33 @@ void initiateMultipartUpload_includesChecksumWhenEnabled() throws Exception {
assertEquals(ChecksumAlgorithm.CRC32, cap.getValue().checksumAlgorithm());
}

@SuppressWarnings("unchecked")
@Test
void initiateMultipartUpload_includesCrc64NvmeWhenEnabled() throws Exception {
setChecksumFields(drv, true, ChecksumAlgorithm.CRC64_NVME);

CreateMultipartUploadResponse mockResponse = CreateMultipartUploadResponse.builder()
.uploadId("test-upload-id-crc64")
.build();

when(mockS3Client.createMultipartUpload(any(CreateMultipartUploadRequest.class)))
.thenReturn(CompletableFuture.completedFuture(mockResponse));

@SuppressWarnings("rawtypes")
CompositeDataOperation compositeOp = mock(CompositeDataOperation.class);
DataItem item = mock(DataItem.class);
when(compositeOp.item()).thenReturn(item);
when(compositeOp.type()).thenReturn(OpType.CREATE);
when(compositeOp.dstPath()).thenReturn("/test-bucket");
when(item.name()).thenReturn("test-key");

drv.execute((Operation) compositeOp).join();

ArgumentCaptor<CreateMultipartUploadRequest> cap = ArgumentCaptor.forClass(CreateMultipartUploadRequest.class);
verify(mockS3Client).createMultipartUpload(cap.capture());
assertEquals(ChecksumAlgorithm.CRC64_NVME, cap.getValue().checksumAlgorithm());
}

@SuppressWarnings("unchecked")
@Disabled("Requires complex setup with real CompositeDataOperation/PartialDataOperation instances - tested through integration")
@Test
Expand Down
Loading