Skip to content
Draft
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
5 changes: 1 addition & 4 deletions .github/workflows/build-deploy-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ on:
types: [ published ]
push:
branches:
- master
- vibecoded-test
paths-ignore:
- "**/*.md"
- "Makefile"
Expand Down Expand Up @@ -131,9 +131,6 @@ jobs:
echo "nais_config_path=.nais/test/nais.yaml" >> "$GITHUB_OUTPUT"
fi




deploy:
name: Deploy to NAIS
needs: build-push
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,5 @@ http-client.private.env.json

.direnv/
.envrc
.tools/
.metals/
Original file line number Diff line number Diff line change
@@ -0,0 +1,309 @@
error id: file://<WORKSPACE>/src/test/java/no/ssb/dlp/pseudo/service/performance/PseudoServiceDaeadPerformanceTest.java:
file://<WORKSPACE>/src/test/java/no/ssb/dlp/pseudo/service/performance/PseudoServiceDaeadPerformanceTest.java
empty definition using pc, found symbol in pc:
empty definition using semanticdb
empty definition using fallback
non-local guesses:

offset: 3633
uri: file://<WORKSPACE>/src/test/java/no/ssb/dlp/pseudo/service/performance/PseudoServiceDaeadPerformanceTest.java
text:
```scala
package no.ssb.dlp.pseudo.service.performance;

import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.google.crypto.tink.Aead;
import com.google.crypto.tink.JsonKeysetWriter;
import com.google.crypto.tink.KeyTemplates;
import com.google.crypto.tink.KeysetHandle;
import com.google.crypto.tink.aead.AeadConfig;
import com.google.crypto.tink.daead.DeterministicAeadConfig;
import no.ssb.dlp.pseudo.core.util.Json;
import no.ssb.dlp.pseudo.core.tink.model.EncryptedKeysetWrapper;
import no.ssb.dlp.pseudo.service.pseudo.PseudoConfigSplitter;
import no.ssb.dlp.pseudo.service.pseudo.PseudoController;
import no.ssb.dlp.pseudo.service.pseudo.PseudoSecrets;
import no.ssb.dlp.pseudo.service.pseudo.RecordMapProcessorFactory;
import no.ssb.dlp.pseudo.service.pseudo.StreamProcessorFactory;
import no.ssb.dlp.pseudo.service.secrets.MockSecretService;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

import java.io.ByteArrayOutputStream;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.SplittableRandom;
import java.util.function.Supplier;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;

class PseudoServiceDaeadPerformanceTest {

private static final String ENABLED_PROP = "pseudo.performance.enabled";
private static final String BATCH_SIZE_PROP = "pseudo.performance.batchSize";
private static final String WARMUP_ROUNDS_PROP = "pseudo.performance.warmupRounds";
private static final String MEASURE_ROUNDS_PROP = "pseudo.performance.measureRounds";

private static final URI TEST_KEK_URI = URI.create("test-kek://local/master-key");

private static PseudoController controller;
private static EncryptedKeysetWrapper keyset;

@BeforeAll
static void setUp() throws Exception {
AeadConfig.register();
DeterministicAeadConfig.register();

Aead masterAead = KeysetHandle.generateNew(KeyTemplates.get("AES256_GCM"))
.getPrimitive(Aead.class);

keyset = createWrappedDaeadKeyset(masterAead, TEST_KEK_URI);

LoadingCache<String, Aead> aeadCache = Caffeine.newBuilder()
.maximumSize(100)
.build(uri -> {
if (!TEST_KEK_URI.toString().equals(uri)) {
throw new IllegalArgumentException("Unknown KEK URI in test: " + uri);
}
return masterAead;
});

PseudoSecrets pseudoSecrets = new PseudoSecrets(new MockSecretService(), Map.of());
RecordMapProcessorFactory recordMapProcessorFactory =
new RecordMapProcessorFactory(pseudoSecrets, aeadCache);

controller = new PseudoController(
new StreamProcessorFactory(),
recordMapProcessorFactory,
new PseudoConfigSplitter()
);
}

@Test
void benchmarkDaeadFieldEndpoints() throws Exception {
Assumptions.assumeTrue(Boolean.getBoolean(ENABLED_PROP),
() -> "Skipping performance benchmark. Run with -D" + ENABLED_PROP + "=true");

final int batchSize = Integer.getInteger(BATCH_SIZE_PROP, 10_000);
final int warmupRounds = Integer.getInteger(WARMUP_ROUNDS_PROP, 2);
final int@@ measureRounds = Integer.getInteger(MEASURE_ROUNDS_PROP, 8);

List<String> inputValues = generateValues(batchSize, 42L);
String daeadFunc = "daead(keyId=" + keyset.primaryKeyId() + ")";

PseudoController.PseudoFieldRequest pseudoReq = new PseudoController.PseudoFieldRequest();
pseudoReq.setName("fnr");
pseudoReq.setPattern("**");
pseudoReq.setPseudoFunc(daeadFunc);
pseudoReq.setKeyset(keyset);
pseudoReq.setValues(inputValues);

String pseudoReqJson = Json.from(pseudoReq);

BenchmarkResult pseudonymizeResult = benchmark(
"pseudonymize",
warmupRounds,
measureRounds,
() -> {
String responseJson = callPseudonymize(pseudoReqJson);
List<String> data = extractDataValues(responseJson);
assertEquals(inputValues.size(), data.size());
return data;
}
);

List<String> pseudonymizedValues = callPseudonymizeAndExtractData(pseudoReqJson);

PseudoController.DepseudoFieldRequest depseudoReq = new PseudoController.DepseudoFieldRequest();
depseudoReq.setName("fnr");
depseudoReq.setPattern("**");
depseudoReq.setPseudoFunc(daeadFunc);
depseudoReq.setKeyset(keyset);
depseudoReq.setValues(pseudonymizedValues);

String depseudoReqJson = Json.from(depseudoReq);

BenchmarkResult depseudonymizeResult = benchmark(
"depseudonymize",
warmupRounds,
measureRounds,
() -> {
String responseJson = callDepseudonymize(depseudoReqJson);
List<String> restored = extractDataValues(responseJson);
assertEquals(inputValues, restored);
return restored;
}
);

Map<String, Object> report = new LinkedHashMap<>();
report.put("timestamp", Instant.now().toString());
report.put("batchSize", batchSize);
report.put("warmupRounds", warmupRounds);
report.put("measureRounds", measureRounds);
report.put("pseudonymize", pseudonymizeResult.toMap());
report.put("depseudonymize", depseudonymizeResult.toMap());

Path reportPath = Path.of("target", "performance", "pseudo-service-daead-field.json");
Files.createDirectories(reportPath.getParent());
Files.writeString(reportPath, Json.prettyFrom(report), StandardCharsets.UTF_8);

System.out.println("DAEAD benchmark report written to: " + reportPath.toAbsolutePath());
System.out.println(Json.prettyFrom(report));
}

private static BenchmarkResult benchmark(String name,
int warmupRounds,
int measureRounds,
Supplier<List<String>> call) {
for (int i = 0; i < warmupRounds; i++) {
call.get();
}

List<Double> elapsedMillis = new ArrayList<>(measureRounds);
int itemCount = -1;
for (int i = 0; i < measureRounds; i++) {
long start = System.nanoTime();
List<String> data = call.get();
long end = System.nanoTime();
if (itemCount < 0) {
itemCount = data.size();
}
elapsedMillis.add((end - start) / 1_000_000d);
}

return BenchmarkResult.of(name, itemCount, elapsedMillis);
}

private static String callPseudonymize(String requestJson) {
return collectBody(
controller.pseudonymizeField(requestJson).body()
);
}

private static List<String> callPseudonymizeAndExtractData(String requestJson) {
return extractDataValues(callPseudonymize(requestJson));
}

private static String callDepseudonymize(String requestJson) {
return collectBody(
controller.depseudonymizeField(requestJson).body()
);
}

private static String collectBody(io.reactivex.Flowable<byte[]> body) {
StringBuilder sb = new StringBuilder();
for (byte[] part : body.blockingIterable()) {
sb.append(new String(part, StandardCharsets.UTF_8));
}
return sb.toString();
}

private static List<String> extractDataValues(String responseJson) {
Map<String, Object> payload = Json.toGenericMap(responseJson);
Object dataObj = payload.get("data");
assertNotNull(dataObj, "Expected response to contain data array");

List<?> raw = (List<?>) dataObj;
List<String> values = new ArrayList<>(raw.size());
for (Object value : raw) {
values.add(value == null ? null : String.valueOf(value));
}
return values;
}

private static List<String> generateValues(int size, long seed) {
SplittableRandom random = new SplittableRandom(seed);
List<String> values = new ArrayList<>(size);
final String alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

for (int i = 0; i < size; i++) {
int len = 10 + random.nextInt(11);
StringBuilder sb = new StringBuilder(len);
for (int c = 0; c < len; c++) {
sb.append(alphabet.charAt(random.nextInt(alphabet.length())));
}
values.add(sb.toString());
}

return values;
}

private static EncryptedKeysetWrapper createWrappedDaeadKeyset(Aead masterAead, URI kekUri) throws Exception {
KeysetHandle dataKeyset = KeysetHandle.generateNew(KeyTemplates.get("AES256_SIV"));
ByteArrayOutputStream baos = new ByteArrayOutputStream();
dataKeyset.write(JsonKeysetWriter.withOutputStream(baos), masterAead);

EncryptedKeysetWrapper wrapper = Json.toObject(
EncryptedKeysetWrapper.class,
baos.toString(StandardCharsets.UTF_8)
);
wrapper.setKekUri(kekUri);
return wrapper;
}

private record BenchmarkResult(
String name,
int itemCount,
double minMs,
double maxMs,
double avgMs,
double p50Ms,
double p95Ms,
double throughputPerSec
) {
private static BenchmarkResult of(String name, int itemCount, List<Double> elapsedMillis) {
List<Double> sorted = new ArrayList<>(elapsedMillis);
Collections.sort(sorted);

double min = sorted.get(0);
double max = sorted.get(sorted.size() - 1);
double sum = sorted.stream().mapToDouble(Double::doubleValue).sum();
double avg = sum / sorted.size();
double p50 = percentile(sorted, 0.50);
double p95 = percentile(sorted, 0.95);
double throughput = (itemCount * 1000d) / avg;

return new BenchmarkResult(name, itemCount, min, max, avg, p50, p95, throughput);
}

private static double percentile(List<Double> sorted, double p) {
if (sorted.size() == 1) {
return sorted.get(0);
}
int idx = (int) Math.ceil(p * sorted.size()) - 1;
idx = Math.max(0, Math.min(idx, sorted.size() - 1));
return sorted.get(idx);
}

private Map<String, Object> toMap() {
Map<String, Object> map = new LinkedHashMap<>();
map.put("name", name);
map.put("items", itemCount);
map.put("minMs", minMs);
map.put("maxMs", maxMs);
map.put("avgMs", avgMs);
map.put("p50Ms", p50Ms);
map.put("p95Ms", p95Ms);
map.put("throughputItemsPerSec", throughputPerSec);
return map;
}
}
}

```


#### Short summary:

empty definition using pc, found symbol in pc:
10 changes: 5 additions & 5 deletions .nais/test/nais.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
apiVersion: nais.io/v1alpha1
kind: Application
metadata:
name: pseudo-service
name: pseudo-service-develop
namespace: {{team}}
labels:
team: {{team}}
Expand All @@ -14,8 +14,8 @@ spec:
port: 10210
terminationGracePeriodSeconds: 180
replicas:
max: 5
min: 2
max: 1
min: 1
resources:
requests:
cpu: 100m
Expand All @@ -24,8 +24,8 @@ spec:
memory: 6Gi

ingresses:
- https://pseudo-service.intern.test.ssb.no
- https://pseudo-service.test.ssb.no
- https://pseudo-service-develop.intern.test.ssb.no
- https://pseudo-service-develop.test.ssb.no

accessPolicy:
outbound:
Expand Down
Binary file added .tools/async-profiler-4.3-macos.zip
Binary file not shown.
Loading
Loading