Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
868a89e
Added additional threading functionality
smoell Apr 15, 2025
070f00c
Added additional threading functionality
smoell Apr 22, 2025
bd56b34
Added additional threading functionality
smoell Apr 22, 2025
a16dec2
Added additional threading functionality
smoell Apr 22, 2025
0742b6f
Added updated Lambda functions
smoell Apr 25, 2025
285c64b
Added jmx and thread changes
smoell May 19, 2025
ad709ec
Added jmx and thread changes
smoell May 19, 2025
216d964
Merged Lambda functions
smoell May 23, 2025
2f87eab
Fixed S3 naming
smoell May 24, 2025
10318fd
Fixed Lambda build
smoell May 24, 2025
ed53db4
Update build.sh
smoell May 24, 2025
e8fc084
Fixed Lambda build
smoell May 24, 2025
3a16e80
Update build.sh
smoell May 24, 2025
ddde05c
Fixed SSL error
smoell May 24, 2025
2e28979
Added Grafana and Prometheus
smoell May 24, 2025
b895f3f
Added Grafana and Prometheus
smoell May 24, 2025
ebf8546
Added Grafana and Prometheus
smoell May 24, 2025
a50b549
Added Grafana and Prometheus
smoell May 24, 2025
7dee813
Added Grafana and Prometheus
smoell May 24, 2025
3393895
Fixed policy
smoell May 26, 2025
5e92e6e
Fixed Grafana user
smoell May 26, 2025
85c727c
Fixed Grafana user
smoell May 26, 2025
09fc770
Fixed Grafana user
smoell May 26, 2025
cb0ad5c
Removed AMG and AMP
smoell May 30, 2025
49ce2e1
Removed AMG and AMP
smoell May 30, 2025
125b89c
Removed AMG and AMP
smoell May 30, 2025
7b44934
Removed AMG and AMP
smoell May 30, 2025
13e7b82
Removed AMG and AMP
smoell May 30, 2025
623637f
Removed AMG and AMP
smoell May 31, 2025
ac09ac5
Removed AMG and AMP
smoell May 31, 2025
f37e979
Removed AMG and AMP
smoell May 31, 2025
9fd98c4
Fixed Prometheus and Grafana setup
smoell Jun 6, 2025
1d70018
Fixed Grafana setup
smoell Jun 16, 2025
5acc979
Fixed permissions for Prometheus
smoell Jun 18, 2025
1bc7cdf
DNS resolution fixed
smoell Jun 18, 2025
cf308de
DNS resolution fixed
smoell Jun 18, 2025
8478785
new version
smoell Jun 22, 2025
ded109d
Fixed Prometheus-setup
smoell Jun 23, 2025
2610a79
Added storage class
smoell Jun 23, 2025
6568f65
Added monitoring-setup
smoell Jun 24, 2025
ddaf16c
Added monitoring-setup
smoell Jun 24, 2025
3a181e6
Added monitoring-setup
smoell Jun 25, 2025
db65639
Added monitoring-setup
smoell Jun 25, 2025
1ebc945
Added monitoring-setup
smoell Jun 25, 2025
2e8e600
Added monitoring-setup
smoell Jun 25, 2025
f4732f3
Added monitoring-setup
smoell Jun 25, 2025
69df9bc
Added monitoring-setup
smoell Jun 25, 2025
3fe2146
Added monitoring-setup
smoell Jun 25, 2025
a76f220
EKS Version 1.33
smoell Jun 26, 2025
5348363
Bugfixing
smoell Jun 27, 2025
c82adc9
Fixed IAM policy
smoell Jun 30, 2025
282e98e
Moved to Lambda HTTP endpoint
smoell Jun 30, 2025
83db37a
Moved to Lambda HTTP endpoint
smoell Jul 1, 2025
8076491
Moved to Lambda HTTP endpoint
smoell Jul 1, 2025
76b211e
Moved to Lambda HTTP endpoint
smoell Jul 1, 2025
954d233
Moved to Lambda HTTP endpoint
smoell Jul 1, 2025
dd190e7
Fixed issues for Bedrock access
smoell Jul 3, 2025
5c16920
Fixed issues for Bedrock access
smoell Jul 3, 2025
c42bbf8
Fixed issues for Bedrock access
smoell Jul 3, 2025
9cd81e2
Fixed issues for Bedrock access
smoell Jul 3, 2025
7b3456b
Fixed issues for Bedrock access
smoell Jul 5, 2025
4925204
Fixed issues for Bedrock access
smoell Jul 5, 2025
0de5669
Fixed issues for Bedrock access
smoell Jul 5, 2025
fe57474
Fixed issues for Bedrock access
smoell Jul 5, 2025
f055a21
Fixed issues for Bedrock access
smoell Jul 5, 2025
b372aa2
Fixes for Lambda/k8s-connection
smoell Jul 7, 2025
afb3d6d
Fixes for Lambda/k8s-connection
smoell Jul 7, 2025
06f3964
Fixes for Lambda/k8s-connection
smoell Jul 7, 2025
fff1043
Fixes for Lambda/k8s-connection
smoell Jul 7, 2025
6d92223
Fixed Bedrock tokens
smoell Jul 7, 2025
c80307f
Added documentation
smoell Jul 7, 2025
f4a8a8b
Bugfixing and prep for basic auth
smoell Jul 8, 2025
1b3b2d4
Merge remote-tracking branch 'upstream/main'
smoell Jul 8, 2025
b98d2b7
Bugfix
smoell Jul 8, 2025
2972f0f
Lambda fix for CDK
smoell Jul 9, 2025
0e29350
Lambda fix for CDK
smoell Jul 9, 2025
a1337b3
Lambda fix for CDK
smoell Jul 9, 2025
5a4f825
Lambda fix for CDK
smoell Jul 9, 2025
f8ac96e
Lambda fix for CDK
smoell Jul 10, 2025
0e9376d
Lambda fix for CDK
smoell Jul 10, 2025
b02e9d6
Lambda fix for CDK
smoell Jul 10, 2025
87a108d
Lambda fix for CDK
smoell Jul 10, 2025
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
6 changes: 6 additions & 0 deletions apps/unicorn-store-spring/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/io.micrometer/micrometer-registry-prometheus -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<version>1.14.7</version>
</dependency>
<!-- AWS SDK dependencies -->
<dependency>
<groupId>software.amazon.awssdk</groupId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package com.unicorn.store.config;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.config.MeterFilter;
import org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.net.URI;
import java.net.UnknownHostException;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.util.Optional;

@Configuration
public class MonitoringConfig {

private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private static final File NAMESPACE_FILE = new File("/var/run/secrets/kubernetes.io/serviceaccount/namespace");

@Bean
public MeterRegistryCustomizer<MeterRegistry> meterRegistryCustomizer() {
return registry -> {
String clusterType = System.getenv("ECS_CONTAINER_METADATA_URI_V4") != null ? "ecs" : "eks";
String cluster = clusterType.equals("ecs") ? extractClusterNameFromMetadata().orElse("unknown") : Optional.ofNullable(System.getenv("CLUSTER")).orElse("unknown");
String containerName = "unicorn-store-spring";
String taskOrPodId = extractTaskOrPodId().orElse("unknown");
String namespace = clusterType.equals("eks") ? readNamespaceFile().orElse("default") : "";

// Get the container/pod IP address
String ipAddress = getContainerOrPodIp().orElse("unknown");

registry.config().commonTags(
"cluster", cluster,
"cluster_type", clusterType,
"container_name", containerName,
"task_pod_id", taskOrPodId,
"instance", ipAddress, // Keep this for backward compatibility
"container_ip", ipAddress // Add this new tag that won't be overwritten
);

if (!namespace.isEmpty()) {
registry.config().commonTags("namespace", namespace);
} else {
registry.config().commonTags("namespace", "<no namespace>");
}

registry.config().meterFilter(
MeterFilter.deny(id ->
id.getName().equals("jvm.gc.pause") &&
!id.getTags().stream().allMatch(tag ->
tag.getKey().equals("action") ||
tag.getKey().equals("cause") ||
tag.getKey().equals("gc")
)
)
);
};
}

private Optional<String> extractTaskOrPodId() {
String metadataUri = System.getenv("ECS_CONTAINER_METADATA_URI_V4");
if (metadataUri != null) {
try {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(metadataUri + "/task"))
.build();

HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());

JsonNode root = OBJECT_MAPPER.readTree(response.body());
String taskArn = root.path("TaskARN").asText();
String[] parts = taskArn.split("/");
return parts.length > 1 ? Optional.of(parts[parts.length - 1]) : Optional.empty();

} catch (IOException | InterruptedException e) {
return Optional.empty();
}
}

// EKS fallback: read pod name from Downward API
return readFile("/etc/podinfo/name");
}

private Optional<String> extractClusterNameFromMetadata() {
String metadataUri = System.getenv("ECS_CONTAINER_METADATA_URI_V4");
if (metadataUri != null) {
try {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(metadataUri + "/task"))
.build();

HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());

JsonNode root = OBJECT_MAPPER.readTree(response.body());
String clusterArn = root.path("Cluster").asText();
String[] parts = clusterArn.split("/");
return parts.length > 1 ? Optional.of(parts[parts.length - 1]) : Optional.empty();

} catch (IOException | InterruptedException e) {
return Optional.empty();
}
}
return Optional.empty();
}

private Optional<String> readNamespaceFile() {
return readFile(NAMESPACE_FILE.getAbsolutePath());
}

private Optional<String> readFile(String path) {
try {
return Optional.of(Files.readString(new File(path).toPath()).trim());
} catch (IOException e) {
return Optional.empty();
}
}

// New method to get the container or pod IP address
private Optional<String> getContainerOrPodIp() {
// For ECS
String metadataUri = System.getenv("ECS_CONTAINER_METADATA_URI_V4");
if (metadataUri != null) {
try {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(metadataUri))
.build();

HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());

JsonNode root = OBJECT_MAPPER.readTree(response.body());

if (root.has("Networks") && root.path("Networks").isArray() && !root.path("Networks").isEmpty()) {
JsonNode network = root.path("Networks").get(0);
if (network.has("IPv4Addresses") && network.path("IPv4Addresses").isArray() &&
!network.path("IPv4Addresses").isEmpty()) {
return Optional.of(network.path("IPv4Addresses").get(0).asText());
}
}
} catch (IOException | InterruptedException e) {
// Fall through to next method
}
}

// For Kubernetes/EKS
String podIp = System.getenv("KUBERNETES_POD_IP");
if (podIp != null && !podIp.isEmpty()) {
return Optional.of(podIp);
}

// Try to get local IP as fallback
try {
return Optional.of(InetAddress.getLocalHost().getHostAddress());
} catch (UnknownHostException e) {
return Optional.empty();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// src/main/java/com/unicorn/store/controller/ThreadManagementController.java
package com.unicorn.store.controller;

import com.unicorn.store.service.ThreadGeneratorService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/threads")
public class ThreadManagementController {

private final ThreadGeneratorService threadGeneratorService;

public ThreadManagementController(ThreadGeneratorService threadGeneratorService) {
this.threadGeneratorService = threadGeneratorService;
}

@PostMapping("/start")
public ResponseEntity<String> startThreads(@RequestParam(defaultValue = "500") int count) {
try {
threadGeneratorService.startThreads(count);
return ResponseEntity.ok("Successfully started " + count + " threads");
} catch (IllegalStateException e) {
return ResponseEntity.badRequest().body(e.getMessage());
}
}

@PostMapping("/stop")
public ResponseEntity<String> stopThreads() {
try {
threadGeneratorService.stopThreads();
return ResponseEntity.ok("Successfully stopped all threads");
} catch (IllegalStateException e) {
return ResponseEntity.badRequest().body(e.getMessage());
}
}

@GetMapping("/count")
public ResponseEntity<Integer> getThreadCount() {
return ResponseEntity.ok(threadGeneratorService.getActiveThreadCount());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// src/main/java/com/unicorn/store/monitoring/ThreadMonitoringMBean.java
package com.unicorn.store.monitoring;

import com.unicorn.store.service.ThreadGeneratorService;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.stereotype.Component;

@Component
@ManagedResource(objectName = "com.unicorn.store:type=ThreadMonitoring,name=ThreadStats")
public class ThreadMonitoringMBean {

private final ThreadGeneratorService threadGeneratorService;

public ThreadMonitoringMBean(ThreadGeneratorService threadGeneratorService) {
this.threadGeneratorService = threadGeneratorService;
}

@ManagedAttribute(description = "Number of active custom threads")
public int getActiveThreadCount() {
return threadGeneratorService.getActiveThreadCount();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// src/main/java/com/unicorn/store/service/ThreadGeneratorService.java
package com.unicorn.store.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

@Service
public class ThreadGeneratorService {
private static final Logger logger = LoggerFactory.getLogger(ThreadGeneratorService.class);
private final List<Thread> activeThreads = new ArrayList<>();
private final AtomicBoolean running = new AtomicBoolean(false);

public synchronized void startThreads(int threadCount) {
if (running.get()) {
throw new IllegalStateException("Threads are already running");
}

running.set(true);
logger.info("Starting {} threads", threadCount);

for (int i = 0; i < threadCount; i++) {
Thread thread = new Thread(new DummyWorkload(running));
thread.setName("DummyThread-" + i);
activeThreads.add(thread);
thread.start();
}

logger.info("Started {} threads", threadCount);
}

public synchronized void stopThreads() {
if (!running.get()) {
throw new IllegalStateException("No threads are running");
}

logger.info("Stopping {} threads", activeThreads.size());
running.set(false);

// Wait for all threads to complete
activeThreads.forEach(thread -> {
try {
thread.join(5000); // Wait up to 5 seconds for each thread
} catch (InterruptedException e) {
logger.warn("Interrupted while waiting for thread {} to stop", thread.getName());
}
});

activeThreads.clear();
logger.info("All threads stopped");
}

public int getActiveThreadCount() {
return activeThreads.size();
}

private static class DummyWorkload implements Runnable {
private final AtomicBoolean running;

public DummyWorkload(AtomicBoolean running) {
this.running = running;
}

@Override
public void run() {
while (running.get()) {
// Simulate some work
try {
// Calculate some dummy values to keep CPU busy
double result = 0;
for (int i = 0; i < 1000; i++) {
result += Math.sqrt(i) * Math.random();
}
Thread.sleep(100); // Sleep to prevent excessive CPU usage
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
}
}
30 changes: 20 additions & 10 deletions apps/unicorn-store-spring/src/main/resources/application.properties
Original file line number Diff line number Diff line change
@@ -1,20 +1,30 @@
spring.sql.init.mode=always
# spring.datasource.url=jdbc:postgresql://localhost:5432/unicorns
spring.datasource.username=postgres
# spring.datasource.password=postgres
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
server.error.include-message=always
spring.jpa.open-in-view=false
spring.datasource.hikari.maximumPoolSize=1
spring.datasource.hikari.data-source-properties.preparedStatementCacheQueries=0
management.endpoint.health.probes.enabled=true

# Disable Hibernate usage of JDBC metadata
spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults=false

# Database initialization will typically be performed outside of Spring lifecycle
spring.jpa.hibernate.ddl-auto=none

spring.datasource.hikari.initialization-fail-timeout=0
spring.datasource.hikari.maximumPoolSize=1
spring.datasource.hikari.allow-pool-suspension=true
spring.datasource.hikari.data-source-properties.preparedStatementCacheQueries=0

# Virtual Threads
spring.threads.virtual.enabled=true

# Actuator config
spring.jmx-enabled=true
management.endpoints.web.exposure.include=threaddump,prometheus,health,info
management.endpoint.health.probes.enabled=true
management.endpoint.prometheus.enabled=true
management.endpoint.health.group.liveness.include=livenessState
management.endpoint.health.group.readiness.include=readinessState

management.metrics.enable.all=true
management.metrics.tags.application=otel-jmx-unicorn-store

# Enable virtual threads
spring.threads.virtual.enabled=true
server.port=8080
server.address=0.0.0.0
5 changes: 5 additions & 0 deletions infrastructure/cdk/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -75,5 +75,10 @@
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>software.constructs</groupId>
<artifactId>constructs</artifactId>
<version>10.3.0</version>
</dependency>
</dependencies>
</project>
Loading