added client command
This commit is contained in:
@@ -7,12 +7,11 @@ plugins {
|
||||
id 'maven-publish'
|
||||
}
|
||||
|
||||
|
||||
import net.woggioni.gradle.envelope.EnvelopeJarTask
|
||||
import net.woggioni.gradle.graalvm.NativeImageConfigurationTask
|
||||
import net.woggioni.gradle.graalvm.NativeImagePlugin
|
||||
import net.woggioni.gradle.graalvm.NativeImageTask
|
||||
import net.woggioni.gradle.graalvm.NativeImageConfigurationTask
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
Property<String> mainClassName = objects.property(String.class)
|
||||
mainClassName.set('net.woggioni.gbcs.cli.GradleBuildCacheServerCli')
|
||||
@@ -43,6 +42,7 @@ dependencies {
|
||||
implementation catalog.netty.codec.http
|
||||
implementation catalog.picocli
|
||||
|
||||
implementation project(":gbcs-client")
|
||||
implementation rootProject
|
||||
|
||||
// runtimeOnly catalog.slf4j.jdk14
|
||||
|
@@ -3,6 +3,7 @@ module net.woggioni.gbcs.cli {
|
||||
requires net.woggioni.gbcs;
|
||||
requires info.picocli;
|
||||
requires net.woggioni.gbcs.base;
|
||||
requires net.woggioni.gbcs.client;
|
||||
requires kotlin.stdlib;
|
||||
requires net.woggioni.jwo;
|
||||
|
||||
|
@@ -8,6 +8,8 @@ import net.woggioni.gbcs.base.debug
|
||||
import net.woggioni.gbcs.base.info
|
||||
import net.woggioni.gbcs.cli.impl.AbstractVersionProvider
|
||||
import net.woggioni.gbcs.cli.impl.GbcsCommand
|
||||
import net.woggioni.gbcs.cli.impl.commands.BenchmarkCommand
|
||||
import net.woggioni.gbcs.cli.impl.commands.ClientCommand
|
||||
import net.woggioni.gbcs.cli.impl.commands.PasswordHashCommand
|
||||
import net.woggioni.jwo.Application
|
||||
import net.woggioni.jwo.JWO
|
||||
@@ -22,7 +24,7 @@ import java.nio.file.Path
|
||||
@CommandLine.Command(
|
||||
name = "gbcs", versionProvider = GradleBuildCacheServerCli.VersionProvider::class
|
||||
)
|
||||
class GradleBuildCacheServerCli(application : Application, private val log : Logger) : GbcsCommand() {
|
||||
class GradleBuildCacheServerCli(application: Application, private val log: Logger) : GbcsCommand() {
|
||||
|
||||
class VersionProvider : AbstractVersionProvider()
|
||||
companion object {
|
||||
@@ -42,6 +44,15 @@ class GradleBuildCacheServerCli(application : Application, private val log : Log
|
||||
CommandLine.ExitCode.SOFTWARE
|
||||
}
|
||||
commandLine.addSubcommand(PasswordHashCommand())
|
||||
val clientApp = Application.builder("gbcs-client")
|
||||
.configurationDirectoryEnvVar("GBCS_CLIENT_CONFIGURATION_DIR")
|
||||
.configurationDirectoryPropertyKey("net.woggioni.gbcs.client.conf.dir")
|
||||
.build()
|
||||
|
||||
commandLine.addSubcommand(
|
||||
CommandLine(ClientCommand(clientApp)).apply {
|
||||
addSubcommand(BenchmarkCommand())
|
||||
})
|
||||
System.exit(commandLine.execute(*args))
|
||||
}
|
||||
}
|
||||
@@ -60,13 +71,13 @@ class GradleBuildCacheServerCli(application : Application, private val log : Log
|
||||
@CommandLine.Spec
|
||||
private lateinit var spec: CommandSpec
|
||||
|
||||
private fun findConfigurationFile(app : Application): Path {
|
||||
private fun findConfigurationFile(app: Application): Path {
|
||||
val confDir = app.computeConfigurationDirectory()
|
||||
val configurationFile = confDir.resolve("gbcs.xml")
|
||||
return configurationFile
|
||||
}
|
||||
|
||||
private fun createDefaultConfigurationFile(configurationFile : Path) {
|
||||
private fun createDefaultConfigurationFile(configurationFile: Path) {
|
||||
log.info {
|
||||
"Creating default configuration file at '$configurationFile'"
|
||||
}
|
||||
|
@@ -0,0 +1,119 @@
|
||||
package net.woggioni.gbcs.cli.impl.commands
|
||||
|
||||
import net.woggioni.gbcs.base.contextLogger
|
||||
import net.woggioni.gbcs.base.error
|
||||
import net.woggioni.gbcs.base.info
|
||||
import net.woggioni.gbcs.cli.impl.GbcsCommand
|
||||
import net.woggioni.gbcs.client.GbcsClient
|
||||
import picocli.CommandLine
|
||||
import java.security.SecureRandom
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.util.Base64
|
||||
import java.util.concurrent.ExecutionException
|
||||
import java.util.concurrent.Future
|
||||
import java.util.concurrent.LinkedBlockingQueue
|
||||
import kotlin.random.Random
|
||||
|
||||
@CommandLine.Command(
|
||||
name = "benchmark",
|
||||
description = ["Run a load test against the server"],
|
||||
showDefaultValues = true
|
||||
)
|
||||
class BenchmarkCommand : GbcsCommand() {
|
||||
private val log = contextLogger()
|
||||
|
||||
@CommandLine.Spec
|
||||
private lateinit var spec: CommandLine.Model.CommandSpec
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["-e", "--entries"],
|
||||
description = ["Total number of elements to be added to the cache"],
|
||||
paramLabel = "NUMBER_OF_ENTRIES"
|
||||
)
|
||||
private var numberOfEntries = 1000
|
||||
|
||||
override fun run() {
|
||||
val clientCommand = spec.parent().userObject() as ClientCommand
|
||||
val profile = clientCommand.profileName.let { profileName ->
|
||||
clientCommand.configuration.profiles[profileName]
|
||||
?: throw IllegalArgumentException("Profile $profileName does not exist in configuration")
|
||||
}
|
||||
val client = GbcsClient(profile)
|
||||
|
||||
val entryGenerator = sequence {
|
||||
val random = Random(SecureRandom.getInstance("NativePRNGNonBlocking").nextLong())
|
||||
while (true) {
|
||||
val key = Base64.getUrlEncoder().encode(random.nextBytes(16)).toString(Charsets.UTF_8)
|
||||
val value = random.nextBytes(0x1000)
|
||||
yield(key to value)
|
||||
}
|
||||
}
|
||||
|
||||
val entries = let {
|
||||
val completionQueue = LinkedBlockingQueue<Future<Pair<String, ByteArray>>>(numberOfEntries)
|
||||
val start = Instant.now()
|
||||
entryGenerator.take(numberOfEntries).forEach { entry ->
|
||||
val future = client.put(entry.first, entry.second).thenApply { entry }
|
||||
future.whenComplete { _, _ ->
|
||||
completionQueue.put(future)
|
||||
}
|
||||
}
|
||||
|
||||
val inserted = sequence<Pair<String, ByteArray>> {
|
||||
var completionCounter = 0
|
||||
while (completionCounter < numberOfEntries) {
|
||||
val future = completionQueue.take()
|
||||
try {
|
||||
yield(future.get())
|
||||
} catch (ee: ExecutionException) {
|
||||
val cause = ee.cause ?: ee
|
||||
log.error(cause.message, cause)
|
||||
}
|
||||
completionCounter += 1
|
||||
}
|
||||
}.toList()
|
||||
val end = Instant.now()
|
||||
log.info {
|
||||
val elapsed = Duration.between(start, end).toMillis()
|
||||
"Insertion rate: ${numberOfEntries.toDouble() / elapsed * 1000} ops/s"
|
||||
}
|
||||
inserted
|
||||
}
|
||||
log.info {
|
||||
"Inserted ${entries.size} entries"
|
||||
}
|
||||
if (entries.isNotEmpty()) {
|
||||
val completionQueue = LinkedBlockingQueue<Future<Unit>>(entries.size)
|
||||
val start = Instant.now()
|
||||
entries.forEach { entry ->
|
||||
val future = client.get(entry.first).thenApply {
|
||||
if (it == null) {
|
||||
log.error {
|
||||
"Missing entry for key '${entry.first}'"
|
||||
}
|
||||
} else if (!entry.second.contentEquals(it)) {
|
||||
log.error {
|
||||
"Retrieved a value different from what was inserted for key '${entry.first}'"
|
||||
}
|
||||
}
|
||||
}
|
||||
future.whenComplete { _, _ ->
|
||||
completionQueue.put(future)
|
||||
}
|
||||
}
|
||||
var completionCounter = 0
|
||||
while (completionCounter < entries.size) {
|
||||
completionQueue.take()
|
||||
completionCounter += 1
|
||||
}
|
||||
val end = Instant.now()
|
||||
log.info {
|
||||
val elapsed = Duration.between(start, end).toMillis()
|
||||
"Retrieval rate: ${entries.size.toDouble() / elapsed * 1000} ops/s"
|
||||
}
|
||||
} else {
|
||||
log.error("Skipping retrieval benchmark as it was not possible to insert any entry in the cache")
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,50 @@
|
||||
package net.woggioni.gbcs.cli.impl.commands
|
||||
|
||||
import net.woggioni.gbcs.client.GbcsClient
|
||||
|
||||
import net.woggioni.gbcs.cli.impl.GbcsCommand
|
||||
import net.woggioni.jwo.Application
|
||||
import picocli.CommandLine
|
||||
import java.nio.file.Path
|
||||
|
||||
@CommandLine.Command(
|
||||
name = "client",
|
||||
description = ["GBCS client"],
|
||||
showDefaultValues = true
|
||||
)
|
||||
class ClientCommand(app : Application) : GbcsCommand() {
|
||||
|
||||
companion object {
|
||||
private fun findConfigurationFile(app: Application): Path {
|
||||
val confDir = app.computeConfigurationDirectory()
|
||||
val configurationFile = confDir.resolve("gbcs-client.xml")
|
||||
return configurationFile
|
||||
}
|
||||
}
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["-c", "--configuration"],
|
||||
description = ["Path to the client configuration file"],
|
||||
paramLabel = "CONFIGURATION_FILE"
|
||||
)
|
||||
private var configurationFile : Path = findConfigurationFile(app)
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["-p", "--profile"],
|
||||
description = ["Name of the client profile to be used"],
|
||||
paramLabel = "PROFILE",
|
||||
required = true
|
||||
)
|
||||
var profileName : String? = null
|
||||
|
||||
val configuration : GbcsClient.Configuration by lazy {
|
||||
GbcsClient.Configuration.parse(configurationFile)
|
||||
}
|
||||
|
||||
override fun run() {
|
||||
println("Available profiles:")
|
||||
configuration.profiles.forEach { (profileName, _) ->
|
||||
println(profileName)
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user