Initial API
This commit is contained in:
15
api/src/main/kotlin/DotEnv.kt
Normal file
15
api/src/main/kotlin/DotEnv.kt
Normal file
@ -0,0 +1,15 @@
|
||||
package com.jaytux.simd
|
||||
|
||||
import io.github.cdimascio.dotenv.dotenv
|
||||
|
||||
object DotEnv {
|
||||
val env = dotenv()
|
||||
|
||||
operator fun get(name: String) = env[name]
|
||||
|
||||
class DotEnvException(missing: String) : RuntimeException("Missing environment variable: $missing") {
|
||||
constructor(missing: String, cause: Throwable) : this(missing) {
|
||||
initCause(cause)
|
||||
}
|
||||
}
|
||||
}
|
55
api/src/main/kotlin/Main.kt
Normal file
55
api/src/main/kotlin/Main.kt
Normal file
@ -0,0 +1,55 @@
|
||||
package com.jaytux.simd
|
||||
|
||||
import com.jaytux.simd.data.Database
|
||||
import com.jaytux.simd.data.Loader
|
||||
import com.jaytux.simd.server.configureHTTP
|
||||
import com.jaytux.simd.server.configureRouting
|
||||
import com.jaytux.simd.server.configureSerialization
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.netty.*
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
if(args.size > 3 && args[1] == "-reload") {
|
||||
val xmlFile = args[2]
|
||||
val jsonFile = args[3]
|
||||
dbSetup(xmlFile, jsonFile)
|
||||
}
|
||||
else if(args.size >= 1 && args[1] == "-h") {
|
||||
println("Usage: ${args[0]} -reload <xmlFile> <jsonFile> (to reload the database)")
|
||||
println(" ${args[0]} (to start the server)")
|
||||
}
|
||||
else {
|
||||
EngineMain.main(args)
|
||||
}
|
||||
}
|
||||
|
||||
fun dbSetup(xmlFile: String, jsonFile: String) {
|
||||
runBlocking {
|
||||
val xml = Loader.loadXml("/home/jay/intrinsics/data.xml")
|
||||
val perf = Loader.loadJson("/home/jay/intrinsics/perf2.js")
|
||||
Loader.importToDb(xml, perf)
|
||||
}
|
||||
}
|
||||
|
||||
fun Application.module() {
|
||||
Database.db
|
||||
configureSerialization()
|
||||
configureHTTP()
|
||||
configureRouting()
|
||||
}
|
||||
|
||||
// API: (everything except /details/ is paginated per 100)
|
||||
// - 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)
|
32
api/src/main/kotlin/data/Database.kt
Normal file
32
api/src/main/kotlin/data/Database.kt
Normal file
@ -0,0 +1,32 @@
|
||||
package com.jaytux.simd.data
|
||||
|
||||
import com.jaytux.simd.DotEnv
|
||||
import org.jetbrains.exposed.sql.Database
|
||||
import org.jetbrains.exposed.sql.SchemaUtils
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
|
||||
object Database {
|
||||
val db by lazy {
|
||||
val db = Database.connect(
|
||||
url = DotEnv["DATABASE_URL"] ?: throw DotEnv.DotEnvException("DATABASE_URL"),
|
||||
driver = DotEnv["DATABASE_DRIVER"] ?: throw DotEnv.DotEnvException("DATABASE_DRIVER"),
|
||||
user = DotEnv["DATABASE_USER"] ?: "",
|
||||
password = DotEnv["DATABASE_PASSWORD"] ?: "",
|
||||
)
|
||||
transaction {
|
||||
SchemaUtils.create(Techs, CppTypes, Categories, CPUIDs, Intrinsics,
|
||||
IntrinsicArguments, IntrinsicInstructions, Platforms, Performances)
|
||||
}
|
||||
|
||||
db
|
||||
}
|
||||
|
||||
fun reset() {
|
||||
transaction {
|
||||
SchemaUtils.drop(Techs, CppTypes, Categories, CPUIDs, Intrinsics,
|
||||
IntrinsicArguments, IntrinsicInstructions, Platforms, Performances)
|
||||
SchemaUtils.create(Techs, CppTypes, Categories, CPUIDs, Intrinsics,
|
||||
IntrinsicArguments, IntrinsicInstructions, Platforms, Performances)
|
||||
}
|
||||
}
|
||||
}
|
82
api/src/main/kotlin/data/Entities.kt
Normal file
82
api/src/main/kotlin/data/Entities.kt
Normal file
@ -0,0 +1,82 @@
|
||||
package com.jaytux.simd.data
|
||||
|
||||
import com.jaytux.simd.data.CppType.Companion.optionalReferrersOn
|
||||
import org.jetbrains.exposed.dao.*
|
||||
import org.jetbrains.exposed.dao.id.CompositeID
|
||||
import org.jetbrains.exposed.dao.id.EntityID
|
||||
import java.util.*
|
||||
|
||||
class Tech(id: EntityID<UUID>) : UUIDEntity(id) {
|
||||
companion object : UUIDEntityClass<Tech>(Techs)
|
||||
|
||||
var name by Techs.name
|
||||
}
|
||||
|
||||
class CppType(id: EntityID<UUID>) : UUIDEntity(id) {
|
||||
companion object : UUIDEntityClass<CppType>(CppTypes)
|
||||
|
||||
var name by CppTypes.name
|
||||
}
|
||||
|
||||
class Category(id: EntityID<UUID>) : UUIDEntity(id) {
|
||||
companion object : UUIDEntityClass<Category>(Categories)
|
||||
|
||||
var name by Categories.name
|
||||
}
|
||||
|
||||
class CPUID(id: EntityID<UUID>) : UUIDEntity(id) {
|
||||
companion object : UUIDEntityClass<CPUID>(CPUIDs)
|
||||
|
||||
var name by CPUIDs.name
|
||||
}
|
||||
|
||||
class Intrinsic(id: EntityID<UUID>) : UUIDEntity(id) {
|
||||
companion object : UUIDEntityClass<Intrinsic>(Intrinsics)
|
||||
|
||||
var mnemonic by Intrinsics.mnemonic
|
||||
var returnType by CppType referencedOn Intrinsics.returnType
|
||||
var returnVar by Intrinsics.returnVar
|
||||
var description by Intrinsics.description
|
||||
var operations by Intrinsics.operations
|
||||
var category by Category referencedOn Intrinsics.category
|
||||
var cpuid by CPUID optionalReferencedOn Intrinsics.cpuid
|
||||
var tech by Tech referencedOn Intrinsics.tech
|
||||
|
||||
val arguments by IntrinsicArgument referrersOn IntrinsicArguments.intrinsic
|
||||
val instructions by IntrinsicInstruction referrersOn IntrinsicInstructions.intrinsic
|
||||
}
|
||||
|
||||
class IntrinsicArgument(id: EntityID<UUID>) : UUIDEntity(id) {
|
||||
companion object : UUIDEntityClass<IntrinsicArgument>(IntrinsicArguments)
|
||||
|
||||
var intrinsic by Intrinsic referencedOn IntrinsicArguments.intrinsic
|
||||
var name by IntrinsicArguments.name
|
||||
var type by CppType referencedOn IntrinsicArguments.type
|
||||
var index by IntrinsicArguments.index
|
||||
}
|
||||
|
||||
class IntrinsicInstruction(id: EntityID<UUID>) : UUIDEntity(id) {
|
||||
companion object : UUIDEntityClass<IntrinsicInstruction>(IntrinsicInstructions)
|
||||
|
||||
var intrinsic by Intrinsic referencedOn IntrinsicInstructions.intrinsic
|
||||
var mnemonic by IntrinsicInstructions.mnemonic
|
||||
var xed by IntrinsicInstructions.xed
|
||||
var form by IntrinsicInstructions.form
|
||||
|
||||
val performance by Performance referrersOn Performances.instruction
|
||||
}
|
||||
|
||||
class Platform(id: EntityID<UUID>) : UUIDEntity(id) {
|
||||
companion object : UUIDEntityClass<Platform>(Platforms)
|
||||
|
||||
var name by Platforms.name
|
||||
}
|
||||
|
||||
class Performance(id: EntityID<CompositeID>) : CompositeEntity(id) {
|
||||
companion object : CompositeEntityClass<Performance>(Performances)
|
||||
|
||||
var instruction by IntrinsicInstruction referencedOn Performances.instruction
|
||||
var platform by Platform referencedOn Performances.platform
|
||||
var latency by Performances.latency
|
||||
var throughput by Performances.throughput
|
||||
}
|
205
api/src/main/kotlin/data/Loader.kt
Normal file
205
api/src/main/kotlin/data/Loader.kt
Normal file
@ -0,0 +1,205 @@
|
||||
package com.jaytux.simd.data
|
||||
|
||||
import com.fleeksoft.ksoup.Ksoup
|
||||
import com.jaytux.simd.data.IntrinsicInstructions.xed
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import org.jetbrains.exposed.sql.batchInsert
|
||||
import org.jetbrains.exposed.sql.insert
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import java.io.File
|
||||
import org.json.JSONObject
|
||||
|
||||
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)
|
||||
|
||||
data class XmlData(
|
||||
val types: Set<String>, val techs: Set<String>, val cpuids: Set<String>, val categories: Set<String>,
|
||||
val intrinsics: List<XmlIntrinsic>
|
||||
)
|
||||
|
||||
data class Performance(val latency: Float?, val throughput: Float?)
|
||||
|
||||
data class JsonData(val platforms: Set<String>, val data: Map<String, Map<String, Performance>>)
|
||||
|
||||
suspend fun loadXml(xmlFile: String): XmlData = coroutineScope {
|
||||
val xml = Ksoup.parseXml(File(xmlFile).readText(Charsets.UTF_8))
|
||||
|
||||
val cppTypes = mutableSetOf<String>()
|
||||
val techs = mutableSetOf<String>()
|
||||
val cpuids = mutableSetOf<String>()
|
||||
val categories = mutableSetOf<String>()
|
||||
val intrins = mutableListOf<XmlIntrinsic>()
|
||||
|
||||
val errors = mutableListOf<String>()
|
||||
|
||||
xml.getElementsByTag("intrinsic").forEachIndexed { i, it ->
|
||||
val name = it.attribute("name")?.value
|
||||
if(name == null) {
|
||||
errors += "Missing name attribute in intrinsic element"
|
||||
return@forEachIndexed
|
||||
}
|
||||
val tech = it.attribute("tech")?.value
|
||||
if(tech == null) {
|
||||
errors += "Missing tech attribute for intrinsic $name"
|
||||
return@forEachIndexed
|
||||
}
|
||||
|
||||
val ret = it.getElementsByTag("return").firstOrNull()
|
||||
if(ret == null) {
|
||||
errors += "Missing return element for intrinsic $name"
|
||||
return@forEachIndexed
|
||||
}
|
||||
val retType = ret.attribute("type")?.value
|
||||
if(retType == null) {
|
||||
errors += "Missing type attribute for return element in intrinsic $name"
|
||||
return@forEachIndexed
|
||||
}
|
||||
val retVar = ret.attribute("varname")?.value
|
||||
|
||||
val args = mutableListOf<Pair<String, String>>()
|
||||
it.getElementsByTag("parameter").forEachIndexed { i, p ->
|
||||
val argName = p.attribute("varname")?.value
|
||||
val type = p.attribute("type")?.value
|
||||
|
||||
if(type != null && type == "void") return@forEachIndexed //ignore
|
||||
|
||||
if(argName == null) {
|
||||
errors += "Missing varname attribute for parameter $i in intrinsic $name"
|
||||
return@forEachIndexed
|
||||
}
|
||||
if(type == null) {
|
||||
errors += "Missing type attribute for parameter $argName in intrinsic $name"
|
||||
return@forEachIndexed
|
||||
}
|
||||
cppTypes += type
|
||||
args += argName to type
|
||||
}
|
||||
|
||||
val desc = it.getElementsByTag("description").firstOrNull()?.text()
|
||||
if(desc == null) {
|
||||
errors += "Missing description element for intrinsic $name"
|
||||
return@forEachIndexed
|
||||
}
|
||||
|
||||
val op = it.getElementsByTag("operation").firstOrNull()?.text()
|
||||
|
||||
val insn = mutableListOf<Triple<String, String, String?>>()
|
||||
it.getElementsByTag("instruction").forEachIndexed { i, ins ->
|
||||
val insnName = ins.attribute("xed")?.value ?: ins.attribute("name")?.value
|
||||
if(insnName == null) {
|
||||
errors += "Missing both xed and name attribute for instruction $i in intrinsic $name"
|
||||
return@forEachIndexed
|
||||
}
|
||||
val insnMnemonic = ins.attribute("name")?.value
|
||||
if(insnMnemonic == null) {
|
||||
errors += "Missing name attribute for instruction $insnName in intrinsic $name"
|
||||
return@forEachIndexed
|
||||
}
|
||||
val insnForm = ins.attribute("form")?.value
|
||||
insn += Triple(insnName, insnMnemonic, insnForm)
|
||||
}
|
||||
|
||||
val cpuid = it.getElementsByTag("cpuid").firstOrNull()?.text()
|
||||
|
||||
val category = it.getElementsByTag("category").firstOrNull()?.text()
|
||||
if(category == null) {
|
||||
errors += "Missing category element for intrinsic $name"
|
||||
return@forEachIndexed
|
||||
}
|
||||
|
||||
val intrinsic = XmlIntrinsic(name, tech, retType, retVar, args, desc, op, insn, cpuid, category)
|
||||
intrins += intrinsic
|
||||
techs += tech
|
||||
cpuid?.let { c -> cpuids += c }
|
||||
categories += category
|
||||
cppTypes += retType
|
||||
}
|
||||
|
||||
if(errors.isNotEmpty()) {
|
||||
errors.forEach { System.err.println(it) }
|
||||
throw Exception("XML file is (partially) invalid")
|
||||
}
|
||||
|
||||
XmlData(types = cppTypes, techs = techs, cpuids = cpuids, categories = categories, intrinsics = intrins)
|
||||
}
|
||||
|
||||
suspend fun loadJson(jsonFile: String): JsonData = coroutineScope {
|
||||
val json = File(jsonFile).readText(Charsets.UTF_8)
|
||||
val schema = JSONObject(json)
|
||||
val pSet = mutableSetOf<String>()
|
||||
val res = mutableMapOf<String, MutableMap<String, Performance>>()
|
||||
|
||||
schema.keys().forEach { opcode ->
|
||||
val pMap = mutableMapOf<String, Performance>()
|
||||
val platforms = schema.getJSONArray(opcode)
|
||||
for (i in 0 until platforms.length()) {
|
||||
val platform = platforms.getJSONObject(i)
|
||||
|
||||
platform.keys().forEach { k ->
|
||||
pSet += k
|
||||
val latency = platform.getJSONObject(k).getString("l").toFloatOrNull()
|
||||
val throughput = platform.getJSONObject(k).getString("t").toFloatOrNull()
|
||||
pMap += k to Performance(latency, throughput)
|
||||
}
|
||||
}
|
||||
res += opcode to pMap
|
||||
}
|
||||
|
||||
JsonData(pSet, res)
|
||||
}
|
||||
|
||||
suspend fun importToDb(xml: XmlData, json: JsonData) = coroutineScope {
|
||||
val db = Database.db
|
||||
transaction {
|
||||
val techMap = xml.techs.associateWith { tech -> Tech.new { name = tech } }
|
||||
val typeMap = xml.types.associateWith { type -> CppType.new { name = type } }
|
||||
val catMap = xml.categories.associateWith { cat -> Category.new { name = cat } }
|
||||
val cpuidMap = xml.cpuids.associateWith { cpuid -> CPUID.new { name = cpuid } }
|
||||
val platformMap = json.platforms.associateWith { platform -> Platform.new { name = platform } }
|
||||
|
||||
xml.intrinsics.forEach { intr ->
|
||||
val dbIn = Intrinsic.new {
|
||||
mnemonic = intr.name
|
||||
returnType = typeMap[intr.retType] ?: throw Exception("Type ${intr.retType} not found")
|
||||
returnVar = intr.retVar
|
||||
description = intr.desc
|
||||
operations = intr.op
|
||||
category = catMap[intr.category] ?: throw Exception("Category ${intr.category} not found")
|
||||
cpuid = intr.cpuid?.let { cpuidMap[it] ?: throw Exception("CPUID ${intr.cpuid} not found") }
|
||||
tech = techMap[intr.tech] ?: throw Exception("Tech ${intr.tech} not found")
|
||||
}
|
||||
|
||||
intr.args.forEachIndexed { i, arg ->
|
||||
IntrinsicArgument.new {
|
||||
intrinsic = dbIn
|
||||
name = arg.first
|
||||
type = typeMap[arg.second] ?: throw Exception("Type ${arg.second} not found")
|
||||
index = i
|
||||
}
|
||||
}
|
||||
|
||||
intr.insn.forEach { insn ->
|
||||
val dbInsn = IntrinsicInstruction.new {
|
||||
intrinsic = dbIn
|
||||
xed = insn.first
|
||||
mnemonic = insn.second
|
||||
insn.third?.let { form = it }
|
||||
}
|
||||
|
||||
json.data[insn.first]?.forEach { (pl, perf) ->
|
||||
val dbPl = platformMap[pl] ?: throw Exception("Platform $pl not found")
|
||||
Performances.insert {
|
||||
it[instruction] = dbInsn.id
|
||||
it[platform] = dbPl.id
|
||||
it[latency] = perf.latency
|
||||
it[throughput] = perf.throughput
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
63
api/src/main/kotlin/data/Tables.kt
Normal file
63
api/src/main/kotlin/data/Tables.kt
Normal file
@ -0,0 +1,63 @@
|
||||
package com.jaytux.simd.data
|
||||
|
||||
import org.jetbrains.exposed.dao.id.UUIDTable
|
||||
import org.jetbrains.exposed.dao.id.CompositeIdTable
|
||||
|
||||
object Techs : UUIDTable() {
|
||||
val name = varchar("name", 255).uniqueIndex()
|
||||
}
|
||||
|
||||
object CppTypes : UUIDTable() {
|
||||
val name = varchar("name", 255).uniqueIndex()
|
||||
}
|
||||
|
||||
object Categories : UUIDTable() {
|
||||
val name = varchar("name", 255).uniqueIndex()
|
||||
}
|
||||
|
||||
object CPUIDs : UUIDTable() {
|
||||
val name = varchar("name", 255).uniqueIndex()
|
||||
}
|
||||
|
||||
object Intrinsics : UUIDTable() {
|
||||
val mnemonic = varchar("mnemonic", 255)
|
||||
val returnType = reference("return_type", CppTypes)
|
||||
val returnVar = varchar("return_var", 255).nullable()
|
||||
val description = text("description")
|
||||
val operations = text("operations").nullable()
|
||||
val category = reference("category", Categories)
|
||||
val cpuid = reference("cpuid", CPUIDs).nullable()
|
||||
val tech = reference("tech", Techs)
|
||||
}
|
||||
|
||||
object IntrinsicArguments : UUIDTable() {
|
||||
val intrinsic = reference("intrinsic", Intrinsics)
|
||||
val name = varchar("name", 255)
|
||||
val type = reference("type", CppTypes)
|
||||
val index = integer("index")
|
||||
|
||||
init {
|
||||
uniqueIndex(intrinsic, name)
|
||||
uniqueIndex(intrinsic, index)
|
||||
}
|
||||
}
|
||||
|
||||
object IntrinsicInstructions : UUIDTable() {
|
||||
val intrinsic = reference("intrinsic", Intrinsics)
|
||||
val mnemonic = varchar("mnemonic", 255)
|
||||
val xed = varchar("xed", 255)
|
||||
val form = text("form").nullable()
|
||||
}
|
||||
|
||||
object Platforms : UUIDTable() {
|
||||
val name = varchar("name", 255).uniqueIndex()
|
||||
}
|
||||
|
||||
object Performances : CompositeIdTable() {
|
||||
val instruction = reference("instruction", IntrinsicInstructions)
|
||||
val platform = reference("platform", Platforms)
|
||||
val latency = float("latency").nullable()
|
||||
val throughput = float("throughput").nullable()
|
||||
|
||||
override val primaryKey: PrimaryKey = PrimaryKey(instruction, platform)
|
||||
}
|
122
api/src/main/kotlin/server/Endpoints.kt
Normal file
122
api/src/main/kotlin/server/Endpoints.kt
Normal file
@ -0,0 +1,122 @@
|
||||
package com.jaytux.simd.server
|
||||
|
||||
import com.jaytux.simd.data.*
|
||||
import io.ktor.server.routing.*
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.like
|
||||
import org.jetbrains.exposed.sql.selectAll
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import java.util.*
|
||||
|
||||
@Serializable
|
||||
data class IntrinsicSummary(@Serializable(with = UUIDSerializer::class) val id: UUID, val name: String)
|
||||
|
||||
@Serializable
|
||||
data class Param(val name: String, val type: String)
|
||||
|
||||
@Serializable
|
||||
data class Instruction(val mnemonic: String, val xed: String, val form: String?)
|
||||
|
||||
@Serializable
|
||||
data class PlatformPerformance(val platform: String, val latency: Float?, val throughput: Float?)
|
||||
|
||||
@Serializable
|
||||
data class IntrinsicDetails(
|
||||
@Serializable(with = UUIDSerializer::class) val id: UUID,
|
||||
val name: String,
|
||||
val returnType: String,
|
||||
val returnVar: String?,
|
||||
val description: String,
|
||||
val operations: String?,
|
||||
val category: String,
|
||||
val cpuid: String?,
|
||||
val tech: String,
|
||||
val params: List<Param>,
|
||||
val instructions: List<Instruction>?,
|
||||
val performance: List<PlatformPerformance>?
|
||||
)
|
||||
|
||||
fun Routing.installGetAll() {
|
||||
getPagedUrl("/all", { 100 }, { IntrinsicSummary(it.id.value, it.mnemonic) }) {
|
||||
Intrinsic.all().orderAsc(Intrinsics.mnemonic)
|
||||
}
|
||||
|
||||
getPagedUrl("/cpuid", { 100 }, { it.name }) { CPUID.all().orderAsc(CPUIDs.name) }
|
||||
getPagedUrl("/tech", { 100 }, { it.name }) { Tech.all().orderAsc(Techs.name) }
|
||||
getPagedUrl("/category", { 100 }, { it.name }) { Category.all().orderAsc(Categories.name) }
|
||||
getPagedUrl("/types", { 100 }, { it.name }) { CppType.all().orderAsc(CppTypes.name) }
|
||||
}
|
||||
|
||||
fun Routing.installSearch() {
|
||||
getPagedRequest("/search", { 100 }, { IntrinsicSummary(it[Intrinsics.id].value, it[Intrinsics.mnemonic]) }) {
|
||||
val name = call.request.queryParameters["name"]
|
||||
val returnType = call.request.queryParameters["return"]?.let {
|
||||
CppType.find { CppTypes.name eq it }.firstOrNull()
|
||||
?: throw HttpError("Unknown return type: $it")
|
||||
}
|
||||
val cpuid = call.request.queryParameters["cpuid"]?.let {
|
||||
CPUID.find { CPUIDs.name eq it }.firstOrNull()
|
||||
?: throw HttpError("Unknown CPUID: $it")
|
||||
}
|
||||
val tech = call.request.queryParameters["tech"]?.let {
|
||||
Tech.find { Techs.name eq it }.firstOrNull()
|
||||
?: throw HttpError("Unknown tech: $it")
|
||||
}
|
||||
val category = call.request.queryParameters["category"]?.let {
|
||||
Category.find { Categories.name eq it }.firstOrNull()
|
||||
?: throw HttpError("Unknown category: $it")
|
||||
}
|
||||
val desc = call.request.queryParameters["desc"]
|
||||
|
||||
var results = Intrinsics.selectAll()
|
||||
name?.let { results = results.where { Intrinsics.mnemonic like "%$it%" } }
|
||||
returnType?.let { results = results.where { Intrinsics.returnType eq it.id } }
|
||||
cpuid?.let { results = results.where { Intrinsics.cpuid eq it.id } }
|
||||
tech?.let { results = results.where { Intrinsics.tech eq it.id } }
|
||||
category?.let { results = results.where { Intrinsics.category eq it.id } }
|
||||
desc?.let { results = results.where { Intrinsics.description like "%$it%" } }
|
||||
|
||||
results.orderAsc(Intrinsics.mnemonic)
|
||||
}
|
||||
}
|
||||
|
||||
fun Routing.installDetails() {
|
||||
get("/details/{id}") {
|
||||
runCatching {
|
||||
transaction {
|
||||
val id = call.parameters["id"]?.let { UUID.fromString(it) }
|
||||
?: throw HttpError("Missing or invalid ID")
|
||||
val intrinsic = Intrinsic.findById(id)
|
||||
?: throw HttpError("Unknown intrinsic: $id")
|
||||
|
||||
IntrinsicDetails(
|
||||
id = intrinsic.id.value,
|
||||
name = intrinsic.mnemonic,
|
||||
returnType = intrinsic.returnType.name,
|
||||
returnVar = intrinsic.returnVar,
|
||||
description = intrinsic.description,
|
||||
operations = intrinsic.operations,
|
||||
category = intrinsic.category.name,
|
||||
cpuid = intrinsic.cpuid?.name,
|
||||
tech = intrinsic.tech.name,
|
||||
params = intrinsic.arguments.orderAsc(IntrinsicArguments.index)
|
||||
.map { Param(it.name, it.type.name) },
|
||||
instructions = intrinsic.instructions.emptyToNull()
|
||||
?.map { Instruction(it.mnemonic, it.xed, it.form) },
|
||||
performance = intrinsic.instructions.firstOrNull()?.let {
|
||||
(Performances innerJoin Platforms).selectAll().where {
|
||||
Performances.instruction eq it.id
|
||||
}.map {
|
||||
PlatformPerformance(
|
||||
platform = it[Platforms.name],
|
||||
latency = it[Performances.latency],
|
||||
throughput = it[Performances.throughput]
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
10
api/src/main/kotlin/server/HTTP.kt
Normal file
10
api/src/main/kotlin/server/HTTP.kt
Normal file
@ -0,0 +1,10 @@
|
||||
package com.jaytux.simd.server
|
||||
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.routing.*
|
||||
|
||||
fun Application.configureHTTP() {
|
||||
routing {
|
||||
//
|
||||
}
|
||||
}
|
18
api/src/main/kotlin/server/Pagination.kt
Normal file
18
api/src/main/kotlin/server/Pagination.kt
Normal file
@ -0,0 +1,18 @@
|
||||
package com.jaytux.simd.server
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.jetbrains.exposed.sql.Expression
|
||||
import org.jetbrains.exposed.sql.SizedIterable
|
||||
import org.jetbrains.exposed.sql.SortOrder
|
||||
|
||||
@Serializable data class Paginated<T>(val page: Long, val totalPages: Long, val items: List<T>)
|
||||
|
||||
inline fun <reified T, reified R> SizedIterable<T>.paginated(page: Long, perPage: Int, crossinline mapper: (T) -> R): Paginated<R> {
|
||||
val total = this.count()
|
||||
val subset = this.offset(page * perPage).limit(perPage).map { item -> mapper(item) }
|
||||
return Paginated(page, total / perPage + if (total % perPage > 0) 1 else 0, subset)
|
||||
}
|
||||
|
||||
fun <T> SizedIterable<T>.orderAsc(expr: Expression<*>) = this.orderBy(expr to SortOrder.ASC)
|
||||
|
||||
fun <T> SizedIterable<T>.emptyToNull() = if(empty()) null else this
|
66
api/src/main/kotlin/server/Routing.kt
Normal file
66
api/src/main/kotlin/server/Routing.kt
Normal file
@ -0,0 +1,66 @@
|
||||
package com.jaytux.simd.server
|
||||
|
||||
import com.jaytux.simd.data.*
|
||||
import io.ktor.http.*
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.plugins.autohead.*
|
||||
import io.ktor.server.response.*
|
||||
import io.ktor.server.routing.*
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import java.util.*
|
||||
|
||||
@Serializable data class ErrorResponse(val error: String)
|
||||
class HttpError(msg: String, val status: HttpStatusCode = HttpStatusCode.BadRequest) : Exception(msg)
|
||||
|
||||
inline suspend fun <reified R: Any> RoutingContext.runCatching(crossinline block: suspend RoutingContext.() -> R) {
|
||||
try {
|
||||
call.respond(block())
|
||||
}
|
||||
catch(err: HttpError) {
|
||||
call.respond(err.status, ErrorResponse(err.message ?: "<no error message given>"))
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <reified T, reified R> Route.getPagedUrl(
|
||||
path: String,
|
||||
crossinline perPage: RoutingContext.() -> Int,
|
||||
crossinline mapper: (T) -> R,
|
||||
crossinline getSet: RoutingContext.() -> SizedIterable<T>,
|
||||
) {
|
||||
get(path) {
|
||||
runCatching {
|
||||
transaction { getSet().paginated(0, perPage(), mapper) }
|
||||
}
|
||||
}
|
||||
get("$path/{page}") {
|
||||
runCatching {
|
||||
val page = call.parameters["page"]?.toLongOrNull() ?: 0
|
||||
transaction { getSet().paginated(page, perPage(), mapper) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <reified T, reified R> Route.getPagedRequest(
|
||||
path: String,
|
||||
crossinline perPage: RoutingContext.() -> Int,
|
||||
crossinline mapper: (T) -> R,
|
||||
crossinline getSet: RoutingContext.() -> SizedIterable<T>,
|
||||
) {
|
||||
get(path) {
|
||||
runCatching {
|
||||
val page = call.request.queryParameters["page"]?.toLongOrNull() ?: 0
|
||||
transaction { getSet().paginated(page, perPage(), mapper) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Application.configureRouting() {
|
||||
install(AutoHeadResponse)
|
||||
routing {
|
||||
installGetAll()
|
||||
installSearch()
|
||||
installDetails()
|
||||
}
|
||||
}
|
28
api/src/main/kotlin/server/Serialization.kt
Normal file
28
api/src/main/kotlin/server/Serialization.kt
Normal file
@ -0,0 +1,28 @@
|
||||
package com.jaytux.simd.server
|
||||
|
||||
import io.ktor.serialization.kotlinx.json.*
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.plugins.contentnegotiation.*
|
||||
import io.ktor.server.routing.*
|
||||
import io.ktor.util.*
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import java.util.*
|
||||
|
||||
fun Application.configureSerialization() {
|
||||
install(ContentNegotiation) {
|
||||
json()
|
||||
}
|
||||
}
|
||||
|
||||
object UUIDSerializer : KSerializer<UUID> {
|
||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("UUID", PrimitiveKind.STRING)
|
||||
|
||||
override fun deserialize(decoder: Decoder): UUID = UUID.fromString(decoder.decodeString())
|
||||
|
||||
override fun serialize(encoder: Encoder, value: UUID) = encoder.encodeString(value.toString())
|
||||
}
|
5
api/src/main/resources/application.yaml
Normal file
5
api/src/main/resources/application.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
ktor:
|
||||
application:
|
||||
modules: [ com.jaytux.simd.MainKt.module ]
|
||||
deployment:
|
||||
port: 42024
|
10
api/src/main/resources/logback.xml
Normal file
10
api/src/main/resources/logback.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<configuration>
|
||||
<appender name="APPENDER" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
<root level="trace">
|
||||
<appender-ref ref="APPENDER"/>
|
||||
</root>
|
||||
</configuration>
|
Reference in New Issue
Block a user