diff --git a/exporter/build.gradle.kts b/exporter/build.gradle.kts index 9b07490..087c4c3 100644 --- a/exporter/build.gradle.kts +++ b/exporter/build.gradle.kts @@ -1,6 +1,7 @@ plugins { kotlin("jvm") kotlin("plugin.spring") + kotlin("plugin.jpa") version "1.9.24" id("org.springframework.boot") id("io.spring.dependency-management") } @@ -11,10 +12,16 @@ java { } } +repositories { + mavenCentral() +} + dependencies { implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.springframework.boot:spring-boot-starter-actuator") implementation("com.fasterxml.jackson.module:jackson-module-kotlin") + implementation("org.springframework.boot:spring-boot-starter-data-jpa") + runtimeOnly("com.mysql:mysql-connector-j") implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("io.micrometer:micrometer-core") implementation("com.zaxxer:HikariCP") @@ -42,3 +49,9 @@ tasks.withType { enabled = false } } + +allOpen { + annotation("jakarta.persistence.Entity") + annotation("jakarta.persistence.MappedSuperclass") + annotation("jakarta.persistence.Embeddable") +} diff --git a/exporter/src/main/kotlin/lab/monilabexporterex/api/MetricController.kt b/exporter/src/main/kotlin/lab/monilabexporterex/api/MetricController.kt new file mode 100644 index 0000000..38c47fc --- /dev/null +++ b/exporter/src/main/kotlin/lab/monilabexporterex/api/MetricController.kt @@ -0,0 +1,21 @@ +package lab.monilabexporterex.api + +import lab.monilabexporterex.api.dto.GcMetricResponse +import lab.monilabexporterex.service.MetricService +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +@RestController +@RequestMapping("/gc") +class MetricController( + private val metricService: MetricService, +) { + + @GetMapping("/metric") + fun getGcMetrics(): ResponseEntity> { + val data = metricService.getAllGcMetrics() + return ResponseEntity.ok(data) + } +} diff --git a/exporter/src/main/kotlin/lab/monilabexporterex/api/dto/metric_dto.kt b/exporter/src/main/kotlin/lab/monilabexporterex/api/dto/metric_dto.kt new file mode 100644 index 0000000..41ec423 --- /dev/null +++ b/exporter/src/main/kotlin/lab/monilabexporterex/api/dto/metric_dto.kt @@ -0,0 +1,12 @@ +package lab.monilabexporterex.api.dto + +data class GcMetricResponse( + val id: Long, + val label: String, + val count: Long, + val time: Long, + val pause: Long, + val allocationRate: Double, + val liveDataSize: Long, + val gcStrategy: String, +) diff --git a/exporter/src/main/kotlin/lab/monilabexporterex/model/ApplicationEntity.kt b/exporter/src/main/kotlin/lab/monilabexporterex/model/ApplicationEntity.kt new file mode 100644 index 0000000..ee3e45c --- /dev/null +++ b/exporter/src/main/kotlin/lab/monilabexporterex/model/ApplicationEntity.kt @@ -0,0 +1,35 @@ +package lab.monilabexporterex.model + +import jakarta.persistence.* + +@Entity +@Table(name = "tb_metric_application") +class ApplicationEntity( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + val id: Long = 0, + + @Enumerated(EnumType.STRING) + @Column(name = "label", nullable = false, length = 10) + val label: Label, + + @Column(name = "http_requests_count", nullable = false) + val httpRequestsCount: Long, + + @Column(name = "http_latency", nullable = false) + val httpLatency: Double, + + @Column(name = "db_connections_active", nullable = false) + val dbConnectionsActive: Int, + + @Column(name = "db_connections_max", nullable = false) + val dbConnectionsMax: Int, + + @Column(name = "queue_tasks_pending", nullable = false) + val queueTasksPending: Int, + + @Lob + @Column(name = "custom_metrics", columnDefinition = "TEXT", nullable = false) + val customMetrics: String, +) diff --git a/exporter/src/main/kotlin/lab/monilabexporterex/model/ClassLoadingInfoEntity.kt b/exporter/src/main/kotlin/lab/monilabexporterex/model/ClassLoadingInfoEntity.kt new file mode 100644 index 0000000..c31d840 --- /dev/null +++ b/exporter/src/main/kotlin/lab/monilabexporterex/model/ClassLoadingInfoEntity.kt @@ -0,0 +1,34 @@ +package lab.monilabexporterex.model + +import jakarta.persistence.* + +@Entity +@Table(name = "tb_metric_class_loading") +class ClassLoadingInfoEntity( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + val id: Long = 0, + + @Enumerated(EnumType.STRING) + @Column(name = "label", nullable = false, length = 10) + val label: Label, + + @Column(name = "loaded", nullable = false) + val loaded: Int, + + @Column(name = "unloaded", nullable = false) + val unloaded: Long, + + @Column(name = "code_cache_used", nullable = false) + val codeCacheUsed: Long, + + @Column(name = "code_cache_max", nullable = false) + val codeCacheMax: Long, + + @Column(name = "compilation_time", nullable = false) + val compilationTime: Long, + + @Column(name = "reserved_code_cache_size", nullable = false) + val reservedCodeCacheSize: Long, +) diff --git a/exporter/src/main/kotlin/lab/monilabexporterex/model/CpuEntity.kt b/exporter/src/main/kotlin/lab/monilabexporterex/model/CpuEntity.kt new file mode 100644 index 0000000..08b6a84 --- /dev/null +++ b/exporter/src/main/kotlin/lab/monilabexporterex/model/CpuEntity.kt @@ -0,0 +1,34 @@ +package lab.monilabexporterex.model + +import jakarta.persistence.* + +@Entity +@Table(name = "tb_metric_cpu") +class CpuEntity( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + val id: Long = 0, + + @Enumerated(EnumType.STRING) + @Column(name = "label", nullable = false, length = 10) + val label: Label, + + @Column(name = "process_usage", nullable = false) + val processUsage: Double, + + @Column(name = "system_usage", nullable = false) + val systemUsage: Double, + + @Column(name = "uptime", nullable = false) + val uptime: Long, + + @Column(name = "start_time", nullable = false) + val startTime: Long, + + @Column(name = "load_average", nullable = false) + val loadAverage: Double, + + @Column(name = "open_fds", nullable = false) + val openFds: Long, +) diff --git a/exporter/src/main/kotlin/lab/monilabexporterex/model/GcEntity.kt b/exporter/src/main/kotlin/lab/monilabexporterex/model/GcEntity.kt new file mode 100644 index 0000000..8603b21 --- /dev/null +++ b/exporter/src/main/kotlin/lab/monilabexporterex/model/GcEntity.kt @@ -0,0 +1,34 @@ +package lab.monilabexporterex.model + +import jakarta.persistence.* + +@Entity +@Table(name = "tb_metric_gc") +class GcEntity( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + val id: Long = 0, + + @Enumerated(EnumType.STRING) + @Column(name = "label", nullable = false, length = 10) + val label: Label, + + @Column(name = "count", nullable = false) + val count: Long, + + @Column(name = "time", nullable = false) + val time: Long, + + @Column(name = "pause", nullable = false) + val pause: Long, + + @Column(name = "allocation_rate", nullable = false) + val allocationRate: Double, + + @Column(name = "live_data_size", nullable = false) + val liveDataSize: Long, + + @Column(name = "gc_strategy", nullable = false) + val gcStrategy: String, +) diff --git a/exporter/src/main/kotlin/lab/monilabexporterex/model/Label.kt b/exporter/src/main/kotlin/lab/monilabexporterex/model/Label.kt new file mode 100644 index 0000000..66292c9 --- /dev/null +++ b/exporter/src/main/kotlin/lab/monilabexporterex/model/Label.kt @@ -0,0 +1,5 @@ +package lab.monilabexporterex.model + +enum class Label { + TEST, TRAIN +} diff --git a/exporter/src/main/kotlin/lab/monilabexporterex/model/MemoryEntity.kt b/exporter/src/main/kotlin/lab/monilabexporterex/model/MemoryEntity.kt new file mode 100644 index 0000000..924c13f --- /dev/null +++ b/exporter/src/main/kotlin/lab/monilabexporterex/model/MemoryEntity.kt @@ -0,0 +1,40 @@ +package lab.monilabexporterex.model + +import jakarta.persistence.* + +@Entity +@Table(name = "tb_metric_memory") +class MemoryEntity( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + val id: Long = 0, + + @Enumerated(EnumType.STRING) + @Column(name = "label", nullable = false) + val label: Label, + + @Column(name = "used", nullable = false) + val used: Long, + + @Column(name = "max", nullable = false) + val max: Long, + + @Column(name = "committed", nullable = false) + val committed: Long, + + @Column(name = "eden", nullable = false) + val eden: Long, + + @Column(name = "survivor", nullable = false) + val survivor: Long, + + @Column(name = "old", nullable = false) + val old: Long, + + @Column(name = "buffer_pool_used", nullable = false) + val bufferPoolUsed: Long, + + @Column(name = "max_direct_memory_size", nullable = false) + val maxDirectMemorySize: Long, +) diff --git a/exporter/src/main/kotlin/lab/monilabexporterex/model/NetworkEntity.kt b/exporter/src/main/kotlin/lab/monilabexporterex/model/NetworkEntity.kt new file mode 100644 index 0000000..31d4c12 --- /dev/null +++ b/exporter/src/main/kotlin/lab/monilabexporterex/model/NetworkEntity.kt @@ -0,0 +1,34 @@ +package lab.monilabexporterex.model + +import jakarta.persistence.* + +@Entity +@Table(name = "tb_metric_network") +class NetworkEntity( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + val id: Long = 0, + + @Enumerated(EnumType.STRING) + @Column(name = "label", nullable = false, length = 10) + val label: Label, + + @Column(name = "bytes_sent", nullable = false) + val bytesSent: Long, + + @Column(name = "bytes_received", nullable = false) + val bytesReceived: Long, + + @Column(name = "tcp_connections", nullable = false) + val tcpConnections: Int, + + @Column(name = "tcp_established", nullable = false) + val tcpEstablished: Int, + + @Column(name = "open_sockets", nullable = false) + val openSockets: Int, + + @Column(name = "prefer_ipv4", nullable = false) + val preferIPv4: Boolean, +) diff --git a/exporter/src/main/kotlin/lab/monilabexporterex/model/ThreadsEntity.kt b/exporter/src/main/kotlin/lab/monilabexporterex/model/ThreadsEntity.kt new file mode 100644 index 0000000..ca8ec50 --- /dev/null +++ b/exporter/src/main/kotlin/lab/monilabexporterex/model/ThreadsEntity.kt @@ -0,0 +1,35 @@ +package lab.monilabexporterex.model + +import jakarta.persistence.* + +@Entity +@Table(name = "tb_metric_threads") +class ThreadsEntity( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + val id: Long = 0, + + @Enumerated(EnumType.STRING) + @Column(name = "label", nullable = false, length = 10) + val label: Label, + + @Column(name = "count", nullable = false) + val count: Int, + + @Column(name = "daemon_count", nullable = false) + val daemonCount: Int, + + @Column(name = "peak_count", nullable = false) + val peakCount: Int, + + @Column(name = "deadlocked_count", nullable = false) + val deadlockedCount: Int, + + @Column(name = "cpu_time", nullable = false) + val cpuTime: Long, + + @Lob + @Column(name = "states", columnDefinition = "TEXT", nullable = false) + val states: String, +) diff --git a/exporter/src/main/kotlin/lab/monilabexporterex/model/mapper/MetricMapper.kt b/exporter/src/main/kotlin/lab/monilabexporterex/model/mapper/MetricMapper.kt new file mode 100644 index 0000000..2d70b05 --- /dev/null +++ b/exporter/src/main/kotlin/lab/monilabexporterex/model/mapper/MetricMapper.kt @@ -0,0 +1,178 @@ +package lab.monilabexporterex.mapper + +import lab.monilabexporterex.exporter.data.JvmMonitoringData +import lab.monilabexporterex.model.* + +object JvmMonitoringMapper { + + fun toEntity(data: JvmMonitoringData.Memory, label: Label): MemoryEntity = + MemoryEntity( + label = label, + used = data.used, + max = data.max, + committed = data.committed, + eden = data.eden, + survivor = data.survivor, + old = data.old, + bufferPoolUsed = data.bufferPoolUsed, + maxDirectMemorySize = data.maxDirectMemorySize + ) + + fun toData(entity: MemoryEntity): JvmMonitoringData.Memory = + JvmMonitoringData.Memory( + used = entity.used, + max = entity.max, + committed = entity.committed, + eden = entity.eden, + survivor = entity.survivor, + old = entity.old, + bufferPoolUsed = entity.bufferPoolUsed, + maxDirectMemorySize = entity.maxDirectMemorySize + ) + + fun toEntity(data: JvmMonitoringData.Gc, label: Label): GcEntity = + GcEntity( + label = label, + count = data.count, + time = data.time, + pause = data.pause, + allocationRate = data.allocationRate, + liveDataSize = data.liveDataSize, + gcStrategy = data.gcStrategy + ) + + fun toData(entity: GcEntity): JvmMonitoringData.Gc = + JvmMonitoringData.Gc( + count = entity.count, + time = entity.time, + pause = entity.pause, + allocationRate = entity.allocationRate, + liveDataSize = entity.liveDataSize, + gcStrategy = entity.gcStrategy + ) + + fun toEntity(data: JvmMonitoringData.Threads, label: Label): ThreadsEntity = + ThreadsEntity( + label = label, + count = data.count, + daemonCount = data.daemonCount, + peakCount = data.peakCount, + deadlockedCount = data.deadlockedCount, + cpuTime = data.cpuTime, + states = data.states.toString() + ) + + fun toData(entity: ThreadsEntity): JvmMonitoringData.Threads = + JvmMonitoringData.Threads( + count = entity.count, + daemonCount = entity.daemonCount, + peakCount = entity.peakCount, + deadlockedCount = entity.deadlockedCount, + cpuTime = entity.cpuTime, + states = parseStates(entity.states) + ) + + private fun parseStates(json: String): Map { + return json + .removePrefix("{").removeSuffix("}") + .split(",") + .mapNotNull { + val (k, v) = it.split("=") + k.trim() to v.trim().toInt() + }.toMap() + } + + fun toEntity(data: JvmMonitoringData.Cpu, label: Label): CpuEntity = + CpuEntity( + label = label, + processUsage = data.processUsage, + systemUsage = data.systemUsage, + uptime = data.uptime, + startTime = data.startTime, + loadAverage = data.loadAverage, + openFds = data.openFds + ) + + fun toData(entity: CpuEntity): JvmMonitoringData.Cpu = + JvmMonitoringData.Cpu( + processUsage = entity.processUsage, + systemUsage = entity.systemUsage, + uptime = entity.uptime, + startTime = entity.startTime, + loadAverage = entity.loadAverage, + openFds = entity.openFds + ) + + fun toEntity(data: JvmMonitoringData.Network, label: Label): NetworkEntity = + NetworkEntity( + label = label, + bytesSent = data.bytesSent, + bytesReceived = data.bytesReceived, + tcpConnections = data.tcpConnections, + tcpEstablished = data.tcpEstablished, + openSockets = data.openSockets, + preferIPv4 = data.preferIPv4 + ) + + fun toData(entity: NetworkEntity): JvmMonitoringData.Network = + JvmMonitoringData.Network( + bytesSent = entity.bytesSent, + bytesReceived = entity.bytesReceived, + tcpConnections = entity.tcpConnections, + tcpEstablished = entity.tcpEstablished, + openSockets = entity.openSockets, + preferIPv4 = entity.preferIPv4 + ) + + fun toEntity(data: JvmMonitoringData.ClassLoadingInfo, label: Label): ClassLoadingInfoEntity = + ClassLoadingInfoEntity( + label = label, + loaded = data.loaded, + unloaded = data.unloaded, + codeCacheUsed = data.codeCacheUsed, + codeCacheMax = data.codeCacheMax, + compilationTime = data.compilationTime, + reservedCodeCacheSize = data.reservedCodeCacheSize + ) + + fun toData(entity: ClassLoadingInfoEntity): JvmMonitoringData.ClassLoadingInfo = + JvmMonitoringData.ClassLoadingInfo( + loaded = entity.loaded, + unloaded = entity.unloaded, + codeCacheUsed = entity.codeCacheUsed, + codeCacheMax = entity.codeCacheMax, + compilationTime = entity.compilationTime, + reservedCodeCacheSize = entity.reservedCodeCacheSize + ) + + fun toEntity(data: JvmMonitoringData.Application, label: Label): ApplicationEntity = + ApplicationEntity( + label = label, + httpRequestsCount = data.httpRequestsCount, + httpLatency = data.httpLatency, + dbConnectionsActive = data.dbConnectionsActive, + dbConnectionsMax = data.dbConnectionsMax, + queueTasksPending = data.queueTasksPending, + customMetrics = data.customMetrics.toString() + ) + + fun toData(entity: ApplicationEntity): JvmMonitoringData.Application = + JvmMonitoringData.Application( + httpRequestsCount = entity.httpRequestsCount, + httpLatency = entity.httpLatency, + dbConnectionsActive = entity.dbConnectionsActive, + dbConnectionsMax = entity.dbConnectionsMax, + queueTasksPending = entity.queueTasksPending, + customMetrics = parseCustomMetrics(entity.customMetrics) + ) + + private fun parseCustomMetrics(json: String): Map { + return json + .removePrefix("{").removeSuffix("}") + .split(",") + .mapNotNull { + val parts = it.split("=") + if (parts.size == 2) parts[0].trim() to parts[1].trim() else null + }.toMap() + } +} diff --git a/exporter/src/main/kotlin/lab/monilabexporterex/repository/MetricJpaRepository.kt b/exporter/src/main/kotlin/lab/monilabexporterex/repository/MetricJpaRepository.kt new file mode 100644 index 0000000..146d064 --- /dev/null +++ b/exporter/src/main/kotlin/lab/monilabexporterex/repository/MetricJpaRepository.kt @@ -0,0 +1,26 @@ +package lab.monilabexporterex.repository + +import lab.monilabexporterex.model.* +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + +@Repository +interface MemoryRepository : JpaRepository + +@Repository +interface GcRepository : JpaRepository + +@Repository +interface ThreadsRepository : JpaRepository + +@Repository +interface CpuRepository : JpaRepository + +@Repository +interface NetworkRepository : JpaRepository + +@Repository +interface ClassLoadingInfoRepository : JpaRepository + +@Repository +interface ApplicationRepository : JpaRepository diff --git a/exporter/src/main/kotlin/lab/monilabexporterex/service/MetricService.kt b/exporter/src/main/kotlin/lab/monilabexporterex/service/MetricService.kt new file mode 100644 index 0000000..ed112fc --- /dev/null +++ b/exporter/src/main/kotlin/lab/monilabexporterex/service/MetricService.kt @@ -0,0 +1,28 @@ +package lab.monilabexporterex.service + +import lab.monilabexporterex.api.dto.GcMetricResponse +import lab.monilabexporterex.model.GcEntity +import lab.monilabexporterex.repository.GcRepository +import org.springframework.stereotype.Service + +@Service +class MetricService( + private val gcRepository: GcRepository, +) { + fun getAllGcMetrics(): List { + return gcRepository.findAll().map { it.toResponse() } + } + + private fun GcEntity.toResponse(): GcMetricResponse { + return GcMetricResponse( + id = this.id, + label = this.label.name, + count = this.count, + time = this.time, + pause = this.pause, + allocationRate = this.allocationRate, + liveDataSize = this.liveDataSize, + gcStrategy = this.gcStrategy, + ) + } +} diff --git a/exporter/src/main/resources/application.properties b/exporter/src/main/resources/application.properties deleted file mode 100644 index c7bde5c..0000000 --- a/exporter/src/main/resources/application.properties +++ /dev/null @@ -1 +0,0 @@ -spring.application.name=monilab-exporter-ex diff --git a/exporter/src/main/resources/application.yml b/exporter/src/main/resources/application.yml new file mode 100644 index 0000000..bf2a893 --- /dev/null +++ b/exporter/src/main/resources/application.yml @@ -0,0 +1,20 @@ +spring: + application: + name: monilab-exporter-ex + main: + banner-mode: off + jpa: + database-platform: org.hibernate.dialect.MySQL5InnoDBDialect + show-sql: true + hibernate: + ddl-auto: update + properties: + hibernate: + default_batch_fetch_size: 1000 + dialect: org.hibernate.dialect.MySQLDialect + + datasource: + url: jdbc:mysql://${DB_URL:localhost:3306}/${DB_NAME:monilab}?useSSL=false&characterEncoding=UTF-8&serverTimezone=Asia/Seoul&allowPublicKeyRetrieval=true + username: ${DB_USER:root} + password: ${DB_PASSWORD:12345} + driver-class-name: com.mysql.cj.jdbc.Driver