Compare commits
4 Commits
5f91256c31
...
d440dacdd6
Author | SHA1 | Date |
---|---|---|
|
d440dacdd6 | |
|
04d97eb4f8 | |
|
9279e1b776 | |
|
f18351f9f0 |
|
@ -43,3 +43,4 @@ bin/
|
||||||
|
|
||||||
.env
|
.env
|
||||||
intrinsics.sqlite
|
intrinsics.sqlite
|
||||||
|
com.jaytux.simd.api.tar.gz
|
|
@ -45,3 +45,14 @@ tasks.test {
|
||||||
kotlin {
|
kotlin {
|
||||||
jvmToolchain(21)
|
jvmToolchain(21)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
application {
|
||||||
|
mainClass.set("com.jaytux.simd.MainKt")
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType<Jar> {
|
||||||
|
manifest {
|
||||||
|
attributes["Main-Class"] = application.mainClass
|
||||||
|
}
|
||||||
|
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
./gradlew clean
|
||||||
|
./gradlew shadowJar
|
||||||
|
|
||||||
|
cp build/libs/api-all.jar .
|
||||||
|
|
||||||
|
rm -fr build/dist/ com.jaytux.simd.api.tar.gz
|
||||||
|
jpackage --type app-image --input build/libs/ --dest build/dist/ --name com.jaytux.simd.api \
|
||||||
|
--main-jar ./api-all.jar --main-class com.jaytux.simd.MainKt
|
||||||
|
|
||||||
|
rm api-all.jar
|
||||||
|
tar -czf com.jaytux.simd.api.tar.gz -C build/dist com.jaytux.simd.api
|
|
@ -2,6 +2,7 @@ package com.jaytux.simd
|
||||||
|
|
||||||
import com.jaytux.simd.data.Database
|
import com.jaytux.simd.data.Database
|
||||||
import com.jaytux.simd.data.Loader
|
import com.jaytux.simd.data.Loader
|
||||||
|
import com.jaytux.simd.server.configureFallback
|
||||||
import com.jaytux.simd.server.configureHTTP
|
import com.jaytux.simd.server.configureHTTP
|
||||||
import com.jaytux.simd.server.configureRouting
|
import com.jaytux.simd.server.configureRouting
|
||||||
import com.jaytux.simd.server.configureSerialization
|
import com.jaytux.simd.server.configureSerialization
|
||||||
|
@ -10,26 +11,39 @@ import io.ktor.server.netty.*
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
if(args.size > 3 && args[1] == "-reload") {
|
println("Arguments (${args.size}): [${args.joinToString(", ")}]")
|
||||||
val xmlFile = args[2]
|
|
||||||
val jsonFile = args[3]
|
if(args.size >= 3 && args[0] == "-reload") {
|
||||||
dbSetup(xmlFile, jsonFile)
|
val xmlFile = args[1]
|
||||||
|
val jsonFile = args[2]
|
||||||
|
val force = args.size > 3 && args[3] == "-force"
|
||||||
|
dbSetup(xmlFile, jsonFile, force)
|
||||||
}
|
}
|
||||||
else if(args.size >= 1 && args[1] == "-h") {
|
else if(args.size >= 1 && args[0] == "-h") {
|
||||||
println("Usage: ${args[0]} -reload <xmlFile> <jsonFile> (to reload the database)")
|
println("Usage: -reload <xmlFile> <jsonFile> (to reload the database)")
|
||||||
println(" ${args[0]} (to start the server)")
|
println(" -reload <xmlFile> <jsonFile> -force (to force reload the database; overwriting data even if the database has newer data)")
|
||||||
|
println(" -h (to show this help message)")
|
||||||
|
println(" (no arguments) (to start the server)")
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
EngineMain.main(args)
|
EngineMain.main(args)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun dbSetup(xmlFile: String, jsonFile: String) {
|
fun dbSetup(xmlFile: String, jsonFile: String, force: Boolean = false) {
|
||||||
runBlocking {
|
runBlocking {
|
||||||
val xml = Loader.loadXml("/home/jay/intrinsics/data.xml")
|
val xml = Loader.loadXml("/home/jay/intrinsics/data.xml")
|
||||||
val perf = Loader.loadJson("/home/jay/intrinsics/perf2.js")
|
val perf = Loader.loadJson("/home/jay/intrinsics/perf2.js")
|
||||||
|
|
||||||
|
val current = Loader.DatedVersion.fromPrefs()
|
||||||
|
if(force || (xml.version.version isNewerThan current.version)) {
|
||||||
|
Database.reset()
|
||||||
Loader.importToDb(xml, perf)
|
Loader.importToDb(xml, perf)
|
||||||
}
|
}
|
||||||
|
if(!(xml.version.version isNewerThan current.version)) {
|
||||||
|
System.err.println("The XML file (${xml.version}) is not more up-to-date than the database ($current). Use -force to force importing.")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Application.module() {
|
fun Application.module() {
|
||||||
|
@ -37,19 +51,6 @@ fun Application.module() {
|
||||||
configureSerialization()
|
configureSerialization()
|
||||||
configureHTTP()
|
configureHTTP()
|
||||||
configureRouting()
|
configureRouting()
|
||||||
}
|
|
||||||
|
|
||||||
// API: (everything except /details/ is paginated per 100)
|
configureFallback()
|
||||||
// - GET /all (list of SIMD intrinsics (name + ID))
|
}
|
||||||
// - GET /cpuid (list of CPUID values)
|
|
||||||
// - GET /tech (list of techs)
|
|
||||||
// - GET /category (list of categories)
|
|
||||||
// - GET /types (list of types)
|
|
||||||
// - GET /search (search for SIMD intrinsics); query params:
|
|
||||||
// - name (string, optional, partial matching)
|
|
||||||
// - return (string, optional, full match)
|
|
||||||
// - cpuid (string, optional, full match)
|
|
||||||
// - tech (string, optional, full match)
|
|
||||||
// - category (string, optional, full match)
|
|
||||||
// - desc (string, optional, partial matching)
|
|
||||||
// - GET /details/<id> (details of a SIMD intrinsic)
|
|
|
@ -4,6 +4,7 @@ import com.jaytux.simd.DotEnv
|
||||||
import org.jetbrains.exposed.sql.Database
|
import org.jetbrains.exposed.sql.Database
|
||||||
import org.jetbrains.exposed.sql.SchemaUtils
|
import org.jetbrains.exposed.sql.SchemaUtils
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
|
import java.util.prefs.Preferences
|
||||||
|
|
||||||
object Database {
|
object Database {
|
||||||
val db by lazy {
|
val db by lazy {
|
||||||
|
@ -22,6 +23,7 @@ object Database {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun reset() {
|
fun reset() {
|
||||||
|
db
|
||||||
transaction {
|
transaction {
|
||||||
SchemaUtils.drop(Techs, CppTypes, Categories, CPUIDs, Intrinsics,
|
SchemaUtils.drop(Techs, CppTypes, Categories, CPUIDs, Intrinsics,
|
||||||
IntrinsicArguments, IntrinsicInstructions, Platforms, Performances)
|
IntrinsicArguments, IntrinsicInstructions, Platforms, Performances)
|
||||||
|
|
|
@ -11,11 +11,57 @@ import org.jetbrains.exposed.sql.insert
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
import org.junit.jupiter.api.DisplayNameGenerator.Simple
|
||||||
|
import java.text.DateFormat
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Date
|
||||||
|
import java.util.prefs.Preferences
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
object Loader {
|
object Loader {
|
||||||
data class XmlIntrinsic(val name: String, val tech: String, val retType: String, val retVar: String?, val args: List<Pair<String, String>>, val desc: String, val op: String?, val insn: List<Triple<String, String, String?>>, val cpuid: String?, val category: String)
|
private val cache = Preferences.userNodeForPackage(this::class.java)
|
||||||
|
private val intelDateTimeFormat = SimpleDateFormat("MM/dd/yyyy")
|
||||||
|
private val dateFormat = SimpleDateFormat("dd-MMMM-yyyy")
|
||||||
|
|
||||||
|
data class XmlIntrinsic(val name: String, val tech: String, val retType: String, val retVar: String?,
|
||||||
|
val args: List<Pair<String, String>>, val desc: String, val op: String?,
|
||||||
|
val insn: List<Triple<String, String, String?>>, val cpuid: String?, val category: String
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Version(val major: Int, val minor: Int, val patch: Int) {
|
||||||
|
override fun toString(): String = "$major.$minor.$patch"
|
||||||
|
|
||||||
|
infix fun isNewerThan(other: Version): Boolean {
|
||||||
|
if(major > other.major) return true
|
||||||
|
if(major == other.major && minor > other.minor) return true
|
||||||
|
if(major == other.major && minor == other.minor && patch > other.patch) return true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromString(version: String): Version {
|
||||||
|
val parts = version.split(".")
|
||||||
|
return Version(parts[0].toInt(), parts[1].toInt(), parts[2].toInt())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class DatedVersion(val version: Version, val releaseDate: Date, val updateDate: Date) {
|
||||||
|
override fun toString(): String = "$version (remote released ${dateFormat.format(releaseDate)}; local updated ${dateFormat.format(updateDate)})"
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromPrefs(): DatedVersion {
|
||||||
|
val v = cache.get("version", null)?.let { Version.fromString(it) } ?: Version(0, 0, 0)
|
||||||
|
val r = cache.get("release", null)?.let { dateFormat.parse(it) } ?: Date.from(Instant.ofEpochMilli(0))
|
||||||
|
val u = cache.get("update", null)?.let { dateFormat.parse(it) } ?: Date.from(Instant.ofEpochMilli(0))
|
||||||
|
|
||||||
|
return DatedVersion(v, r, u)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
data class XmlData(
|
data class XmlData(
|
||||||
|
val version: DatedVersion,
|
||||||
val types: Set<String>, val techs: Set<String>, val cpuids: Set<String>, val categories: Set<String>,
|
val types: Set<String>, val techs: Set<String>, val cpuids: Set<String>, val categories: Set<String>,
|
||||||
val intrinsics: List<XmlIntrinsic>
|
val intrinsics: List<XmlIntrinsic>
|
||||||
)
|
)
|
||||||
|
@ -24,6 +70,12 @@ object Loader {
|
||||||
|
|
||||||
data class JsonData(val platforms: Set<String>, val data: Map<String, Map<String, Performance>>)
|
data class JsonData(val platforms: Set<String>, val data: Map<String, Map<String, Performance>>)
|
||||||
|
|
||||||
|
private suspend fun updateVersion(version: DatedVersion) = coroutineScope {
|
||||||
|
cache.put("version", version.version.toString())
|
||||||
|
cache.put("release", dateFormat.format(version.releaseDate))
|
||||||
|
cache.put("update", dateFormat.format(version.updateDate))
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun loadXml(xmlFile: String): XmlData = coroutineScope {
|
suspend fun loadXml(xmlFile: String): XmlData = coroutineScope {
|
||||||
val xml = Ksoup.parseXml(File(xmlFile).readText(Charsets.UTF_8))
|
val xml = Ksoup.parseXml(File(xmlFile).readText(Charsets.UTF_8))
|
||||||
|
|
||||||
|
@ -35,6 +87,28 @@ object Loader {
|
||||||
|
|
||||||
val errors = mutableListOf<String>()
|
val errors = mutableListOf<String>()
|
||||||
|
|
||||||
|
val (version, release) = xml.getElementsByTag("intrinsics_list").firstOrNull()?.let {
|
||||||
|
val version = it.attribute("version")?.value?.let { v -> Version.fromString(v) }
|
||||||
|
if(version == null) {
|
||||||
|
errors += "Missing version attribute in intrinsics_list element"
|
||||||
|
return@let null
|
||||||
|
}
|
||||||
|
val date = it.attribute("date")?.value?.let { d -> intelDateTimeFormat.parse(d) }
|
||||||
|
if(date == null) {
|
||||||
|
errors += "Missing release_date attribute in intrinsics_list element"
|
||||||
|
return@let null
|
||||||
|
}
|
||||||
|
|
||||||
|
version to date
|
||||||
|
} ?: Version(0, 0, 0) to Date()
|
||||||
|
|
||||||
|
if(errors.isNotEmpty()) {
|
||||||
|
errors.forEach { System.err.println(it) }
|
||||||
|
throw Exception("XML file is (partially) invalid")
|
||||||
|
}
|
||||||
|
|
||||||
|
val update = Date()
|
||||||
|
|
||||||
xml.getElementsByTag("intrinsic").forEachIndexed { i, it ->
|
xml.getElementsByTag("intrinsic").forEachIndexed { i, it ->
|
||||||
val name = it.attribute("name")?.value
|
val name = it.attribute("name")?.value
|
||||||
if(name == null) {
|
if(name == null) {
|
||||||
|
@ -123,7 +197,10 @@ object Loader {
|
||||||
throw Exception("XML file is (partially) invalid")
|
throw Exception("XML file is (partially) invalid")
|
||||||
}
|
}
|
||||||
|
|
||||||
XmlData(types = cppTypes, techs = techs, cpuids = cpuids, categories = categories, intrinsics = intrins)
|
XmlData(version = DatedVersion(version, release, update),
|
||||||
|
types = cppTypes, techs = techs, cpuids = cpuids, categories = categories,
|
||||||
|
intrinsics = intrins
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun loadJson(jsonFile: String): JsonData = coroutineScope {
|
suspend fun loadJson(jsonFile: String): JsonData = coroutineScope {
|
||||||
|
@ -201,5 +278,6 @@ object Loader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
updateVersion(xml.version)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -85,8 +85,14 @@ fun Routing.installDetails() {
|
||||||
get("/details/{id}") {
|
get("/details/{id}") {
|
||||||
runCatching {
|
runCatching {
|
||||||
transaction {
|
transaction {
|
||||||
val id = call.parameters["id"]?.let { UUID.fromString(it) }
|
val id = call.parameters["id"]?.let {
|
||||||
?: throw HttpError("Missing or invalid ID")
|
try {
|
||||||
|
UUID.fromString(it)
|
||||||
|
}
|
||||||
|
catch(e: IllegalArgumentException) {
|
||||||
|
throw HttpError("Invalid UUID: $it")
|
||||||
|
}
|
||||||
|
} ?: throw HttpError("Missing or invalid ID")
|
||||||
val intrinsic = Intrinsic.findById(id)
|
val intrinsic = Intrinsic.findById(id)
|
||||||
?: throw HttpError("Unknown intrinsic: $id")
|
?: throw HttpError("Unknown intrinsic: $id")
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import com.jaytux.simd.data.*
|
||||||
import io.ktor.http.*
|
import io.ktor.http.*
|
||||||
import io.ktor.server.application.*
|
import io.ktor.server.application.*
|
||||||
import io.ktor.server.plugins.autohead.*
|
import io.ktor.server.plugins.autohead.*
|
||||||
|
import io.ktor.server.request.*
|
||||||
import io.ktor.server.response.*
|
import io.ktor.server.response.*
|
||||||
import io.ktor.server.routing.*
|
import io.ktor.server.routing.*
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
@ -64,3 +65,13 @@ fun Application.configureRouting() {
|
||||||
installDetails()
|
installDetails()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Application.configureFallback() {
|
||||||
|
routing {
|
||||||
|
route("{...}") {
|
||||||
|
handle {
|
||||||
|
call.respond(HttpStatusCode.NotFound, ErrorResponse("Endpoint not found: ${call.request.httpMethod.value} ${call.request.path()}"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,7 +22,10 @@ fun Application.configureSerialization() {
|
||||||
object UUIDSerializer : KSerializer<UUID> {
|
object UUIDSerializer : KSerializer<UUID> {
|
||||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("UUID", PrimitiveKind.STRING)
|
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("UUID", PrimitiveKind.STRING)
|
||||||
|
|
||||||
override fun deserialize(decoder: Decoder): UUID = UUID.fromString(decoder.decodeString())
|
override fun deserialize(decoder: Decoder): UUID = try { UUID.fromString(decoder.decodeString()) }
|
||||||
|
catch (e: IllegalArgumentException) {
|
||||||
|
throw HttpError("Invalid UUID format")
|
||||||
|
}
|
||||||
|
|
||||||
override fun serialize(encoder: Encoder, value: UUID) = encoder.encodeString(value.toString())
|
override fun serialize(encoder: Encoder, value: UUID) = encoder.encodeString(value.toString())
|
||||||
}
|
}
|
|
@ -4,6 +4,10 @@
|
||||||
<pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
<pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||||
</encoder>
|
</encoder>
|
||||||
</appender>
|
</appender>
|
||||||
|
|
||||||
|
<logger name="io.netty" level="warn" />
|
||||||
|
<logger name="ch.qos.logback" level="warn" />
|
||||||
|
|
||||||
<root level="trace">
|
<root level="trace">
|
||||||
<appender-ref ref="APPENDER"/>
|
<appender-ref ref="APPENDER"/>
|
||||||
</root>
|
</root>
|
||||||
|
|
Loading…
Reference in New Issue