Initial Dever 0.0.1
This commit is contained in:
commit
81233d098c
1479 files changed
+163782
No files matched your search
@@ -0,0 +1,13 @@
|
||||
type Identity {
|
||||
id: Text
|
||||
user_id: model.id?
|
||||
tenant_id: model.id?
|
||||
}
|
||||
|
||||
verify(claims: dever.auth.Claims) (identity: Identity) {
|
||||
identity = Identity {
|
||||
id = claims.subject
|
||||
user_id = null
|
||||
tenant_id = null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
global type Auth {
|
||||
label: Text(1, 64)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
public post echo = app.echo
|
||||
|
||||
public post raw = app.raw
|
||||
@@ -0,0 +1,19 @@
|
||||
type Row {
|
||||
id: Int
|
||||
text: Text
|
||||
weight: Float
|
||||
note: Text?
|
||||
}
|
||||
|
||||
type Payload {
|
||||
rows: List<Row>
|
||||
raw: Json
|
||||
}
|
||||
|
||||
echo(payload: Payload) (value: Payload) {
|
||||
value = payload
|
||||
}
|
||||
|
||||
raw(source: Json) (value: Json) {
|
||||
value = source
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
|
||||
use dever_backend_bridge::{Target, emit_object};
|
||||
use dever_core::source::SourceMap;
|
||||
use dever_runtime::config::Settings;
|
||||
|
||||
fn main() -> Result<(), String> {
|
||||
let arguments = std::env::args_os().skip(1).collect::<Vec<_>>();
|
||||
let (project, output, ir) = match arguments.as_slice() {
|
||||
[project, output] => (project, output, None),
|
||||
[project, output, ir] => (project, output, Some(ir)),
|
||||
_ => {
|
||||
return Err("usage: api-json-object <project> <new-object-file> [new-ir-file]".into());
|
||||
}
|
||||
};
|
||||
let project = Path::new(project);
|
||||
let sources =
|
||||
SourceMap::load(&project.join("module")).map_err(|errors| format!("{errors:?}"))?;
|
||||
let settings = Settings::load_project(project).map_err(|error| error.to_string())?;
|
||||
let program = dever_core::check_with_settings(&sources, &settings).map_err(|errors| {
|
||||
errors
|
||||
.iter()
|
||||
.map(|error| error.render(&sources))
|
||||
.collect::<String>()
|
||||
})?;
|
||||
let errors = program.application_errors()?;
|
||||
if !errors.is_empty() {
|
||||
return Err(errors
|
||||
.iter()
|
||||
.map(|error| error.render(&sources))
|
||||
.collect::<String>());
|
||||
}
|
||||
let module = dever_core::llvm::emit_executable(&program, &sources)?;
|
||||
if let Some(ir) = ir {
|
||||
let mut ir = std::fs::OpenOptions::new()
|
||||
.write(true)
|
||||
.create_new(true)
|
||||
.open(ir)
|
||||
.map_err(|error| error.to_string())?;
|
||||
ir.write_all(module.as_bytes())
|
||||
.map_err(|error| error.to_string())?;
|
||||
}
|
||||
let object = emit_object(&module, Target::LinuxX86_64)?;
|
||||
let mut output = std::fs::OpenOptions::new()
|
||||
.write(true)
|
||||
.create_new(true)
|
||||
.open(output)
|
||||
.map_err(|error| error.to_string())?;
|
||||
output.write_all(&object).map_err(|error| error.to_string())
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
type row struct {
|
||||
ID int64 `json:"id"`
|
||||
Text string `json:"text"`
|
||||
Weight float64 `json:"weight"`
|
||||
Note *string `json:"note"`
|
||||
}
|
||||
|
||||
type payload struct {
|
||||
Rows []row `json:"rows"`
|
||||
Raw json.RawMessage `json:"raw"`
|
||||
}
|
||||
|
||||
type envelope struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data any `json:"data"`
|
||||
}
|
||||
|
||||
func handle(writer http.ResponseWriter, request *http.Request) {
|
||||
typed := request.URL.Path == "/benchmark/echo/echo"
|
||||
if request.Method != http.MethodPost || (!typed && request.URL.Path != "/benchmark/echo/raw") {
|
||||
http.NotFound(writer, request)
|
||||
return
|
||||
}
|
||||
body := http.MaxBytesReader(writer, request.Body, 16*1024*1024)
|
||||
defer body.Close()
|
||||
decoder := json.NewDecoder(body)
|
||||
decoder.DisallowUnknownFields()
|
||||
var result any
|
||||
var err error
|
||||
if typed {
|
||||
var input struct {
|
||||
Payload payload `json:"payload"`
|
||||
}
|
||||
err = decoder.Decode(&input)
|
||||
result = input.Payload
|
||||
} else {
|
||||
var input struct {
|
||||
Source json.RawMessage `json:"source"`
|
||||
}
|
||||
err = decoder.Decode(&input)
|
||||
result = input.Source
|
||||
}
|
||||
var extra any
|
||||
if err != nil || decoder.Decode(&extra) != io.EOF {
|
||||
http.Error(writer, "invalid JSON", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
encoded, err := json.Marshal(envelope{0, "ok", result})
|
||||
if err != nil {
|
||||
http.Error(writer, "invalid response", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
writer.Header().Set("Content-Type", "application/json")
|
||||
writer.Write(encoded)
|
||||
}
|
||||
|
||||
func run() error {
|
||||
runtime.GOMAXPROCS(1)
|
||||
content, err := os.ReadFile("config/setting.json")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var settings struct {
|
||||
HTTP struct {
|
||||
Port uint16 `json:"port"`
|
||||
} `json:"http"`
|
||||
}
|
||||
if err := json.Unmarshal(content, &settings); err != nil {
|
||||
return err
|
||||
}
|
||||
listener, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", settings.HTTP.Port))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
server := &http.Server{Handler: http.HandlerFunc(handle), ReadHeaderTimeout: 10 * time.Second, IdleTimeout: 10 * time.Second}
|
||||
stopping := make(chan os.Signal, 1)
|
||||
signal.Notify(stopping, syscall.SIGTERM)
|
||||
defer signal.Stop(stopping)
|
||||
closed := make(chan error, 1)
|
||||
go func() {
|
||||
<-stopping
|
||||
context, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
defer cancel()
|
||||
closed <- server.Shutdown(context)
|
||||
}()
|
||||
if err := server.Serve(listener); !errors.Is(err, http.ErrServerClosed) {
|
||||
return err
|
||||
}
|
||||
return <-closed
|
||||
}
|
||||
|
||||
func main() {
|
||||
if err := run(); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
use std::convert::Infallible;
|
||||
use std::time::Duration;
|
||||
|
||||
use bytes::Bytes;
|
||||
use http_body_util::{BodyExt, Full, Limited};
|
||||
use hyper::{
|
||||
Request, Response, StatusCode, body::Incoming, server::conn::http1, service::service_fn,
|
||||
};
|
||||
use hyper_util::rt::TokioIo;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::value::RawValue;
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::signal::unix::{SignalKind, signal};
|
||||
use tokio::sync::watch;
|
||||
use tokio::task::JoinSet;
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct Row {
|
||||
id: i64,
|
||||
text: String,
|
||||
weight: f64,
|
||||
note: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct Payload {
|
||||
rows: Vec<Row>,
|
||||
raw: Box<RawValue>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct TypedRequest {
|
||||
payload: Payload,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct RawRequest {
|
||||
source: Box<RawValue>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Envelope<Value> {
|
||||
code: u8,
|
||||
message: &'static str,
|
||||
data: Value,
|
||||
}
|
||||
|
||||
async fn handle(request: Request<Incoming>) -> Result<Response<Full<Bytes>>, Infallible> {
|
||||
let typed = request.uri().path() == "/benchmark/echo/echo";
|
||||
if request.method() != hyper::Method::POST
|
||||
|| !typed && request.uri().path() != "/benchmark/echo/raw"
|
||||
{
|
||||
return Ok(Response::builder()
|
||||
.status(StatusCode::NOT_FOUND)
|
||||
.body(Full::new(Bytes::new()))
|
||||
.unwrap());
|
||||
}
|
||||
let body = Limited::new(request.into_body(), 16 * 1024 * 1024)
|
||||
.collect()
|
||||
.await;
|
||||
let encoded = body.map_err(|error| error.to_string()).and_then(|body| {
|
||||
let bytes = body.to_bytes();
|
||||
if typed {
|
||||
serde_json::from_slice::<TypedRequest>(&bytes).and_then(|value| {
|
||||
serde_json::to_vec(&Envelope {
|
||||
code: 0,
|
||||
message: "ok",
|
||||
data: value.payload,
|
||||
})
|
||||
})
|
||||
} else {
|
||||
serde_json::from_slice::<RawRequest>(&bytes).and_then(|value| {
|
||||
serde_json::to_vec(&Envelope {
|
||||
code: 0,
|
||||
message: "ok",
|
||||
data: value.source,
|
||||
})
|
||||
})
|
||||
}
|
||||
.map_err(|error| error.to_string())
|
||||
});
|
||||
let (status, body) = match encoded {
|
||||
Ok(body) => (StatusCode::OK, body),
|
||||
Err(_) => (StatusCode::BAD_REQUEST, Vec::new()),
|
||||
};
|
||||
Ok(Response::builder()
|
||||
.status(status)
|
||||
.header("Content-Type", "application/json")
|
||||
.body(Full::new(Bytes::from(body)))
|
||||
.unwrap())
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
tokio::runtime::Builder::new_current_thread().enable_all().build()?.block_on(async {
|
||||
let settings: serde_json::Value = serde_json::from_slice(&std::fs::read("config/setting.json")?)?;
|
||||
let port = u16::try_from(settings["http"]["port"].as_u64().ok_or("missing port")?)?;
|
||||
let listener = TcpListener::bind(("127.0.0.1", port)).await?;
|
||||
let mut terminate = signal(SignalKind::terminate())?;
|
||||
let (shutdown, stopping) = watch::channel(false);
|
||||
let mut connections = JoinSet::new();
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = terminate.recv() => break,
|
||||
Some(result) = connections.join_next(), if !connections.is_empty() => { result?; },
|
||||
accepted = listener.accept() => {
|
||||
let (stream, _) = accepted?;
|
||||
stream.set_nodelay(true)?;
|
||||
let mut stopping = stopping.clone();
|
||||
connections.spawn(async move {
|
||||
let connection = http1::Builder::new().serve_connection(TokioIo::new(stream), service_fn(handle));
|
||||
tokio::pin!(connection);
|
||||
tokio::select! {
|
||||
_ = &mut connection => {},
|
||||
_ = stopping.changed() => {
|
||||
connection.as_mut().graceful_shutdown();
|
||||
let _ = tokio::time::timeout(Duration::from_secs(2), connection).await;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
shutdown.send(true)?;
|
||||
while let Some(result) = connections.join_next().await { result?; }
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
use dever_runtime::api;
|
||||
use dever_runtime::application::Session;
|
||||
use dever_runtime::auth;
|
||||
use dever_runtime::config::RuntimeProfile;
|
||||
use dever_runtime::http::{Request, Response};
|
||||
use dever_runtime::wire::decode::{self, Fields, Reader};
|
||||
use dever_runtime::wire::{Encoded, Encoder};
|
||||
use dever_runtime::{lifecycle, task};
|
||||
|
||||
#[derive(Default)]
|
||||
struct Row {
|
||||
id: i64,
|
||||
text: String,
|
||||
weight: f64,
|
||||
note: Option<String>,
|
||||
}
|
||||
|
||||
impl Fields for Row {
|
||||
fn count(&self) -> usize {
|
||||
4
|
||||
}
|
||||
|
||||
fn index(&self, name: &str) -> Result<usize, String> {
|
||||
match name {
|
||||
"id" => Ok(0),
|
||||
"text" => Ok(1),
|
||||
"weight" => Ok(2),
|
||||
"note" => Ok(3),
|
||||
_ => Err("unknown wire record field".into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn required(&self, index: usize) -> bool {
|
||||
index != 3
|
||||
}
|
||||
|
||||
fn read(&mut self, index: usize, reader: &mut Reader<'_, '_>) -> Result<(), String> {
|
||||
match index {
|
||||
0 => self.id = reader.int()?,
|
||||
1 => self.text = reader.text()?.into_owned(),
|
||||
2 => self.weight = reader.float()?,
|
||||
3 => {
|
||||
reader.nullable(&mut |reader| {
|
||||
self.note = Some(reader.text()?.into_owned());
|
||||
Ok(())
|
||||
})?;
|
||||
}
|
||||
_ => return Err("invalid wire field index".into()),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Payload {
|
||||
rows: Vec<Row>,
|
||||
raw: Option<Encoded>,
|
||||
}
|
||||
|
||||
impl Fields for Payload {
|
||||
fn count(&self) -> usize {
|
||||
2
|
||||
}
|
||||
|
||||
fn index(&self, name: &str) -> Result<usize, String> {
|
||||
match name {
|
||||
"rows" => Ok(0),
|
||||
"raw" => Ok(1),
|
||||
_ => Err("unknown wire record field".into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn required(&self, _: usize) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn read(&mut self, index: usize, reader: &mut Reader<'_, '_>) -> Result<(), String> {
|
||||
match index {
|
||||
0 => reader.list(&mut |reader| {
|
||||
let mut row = Row::default();
|
||||
reader.record(&mut row)?;
|
||||
self.rows.push(row);
|
||||
Ok(())
|
||||
}),
|
||||
1 => {
|
||||
self.raw = Some(reader.json()?);
|
||||
Ok(())
|
||||
}
|
||||
_ => Err("invalid wire field index".into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Payload {
|
||||
fn encode(self) -> Result<Encoded, String> {
|
||||
let mut encoder = Encoder::default();
|
||||
encoder.compiled_record(2, |encoder| {
|
||||
encoder.compiled_key("\"rows\":")?;
|
||||
encoder.begin_array()?;
|
||||
for row in self.rows {
|
||||
encoder.compiled_record(4, |encoder| {
|
||||
encoder.compiled_key("\"id\":")?;
|
||||
encoder.int(row.id)?;
|
||||
encoder.compiled_key(",\"text\":")?;
|
||||
encoder.text(&row.text)?;
|
||||
encoder.compiled_key(",\"weight\":")?;
|
||||
encoder.float(row.weight)?;
|
||||
encoder.compiled_key(",\"note\":")?;
|
||||
match row.note {
|
||||
Some(note) => encoder.text(¬e),
|
||||
None => encoder.null(),
|
||||
}
|
||||
})?;
|
||||
}
|
||||
encoder.end()?;
|
||||
encoder.compiled_key(",\"raw\":")?;
|
||||
encoder.encoded(&self.raw.ok_or("missing required wire field")?)
|
||||
})?;
|
||||
encoder.finish()
|
||||
}
|
||||
}
|
||||
|
||||
enum Value {
|
||||
Typed(Payload),
|
||||
Raw(Encoded),
|
||||
}
|
||||
|
||||
struct Arguments {
|
||||
typed: bool,
|
||||
value: Option<Value>,
|
||||
}
|
||||
|
||||
impl Fields for Arguments {
|
||||
fn count(&self) -> usize {
|
||||
1
|
||||
}
|
||||
|
||||
fn index(&self, name: &str) -> Result<usize, String> {
|
||||
if name == if self.typed { "payload" } else { "source" } {
|
||||
Ok(0)
|
||||
} else {
|
||||
Err("unknown wire record field".into())
|
||||
}
|
||||
}
|
||||
|
||||
fn required(&self, _: usize) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn read(&mut self, _: usize, reader: &mut Reader<'_, '_>) -> Result<(), String> {
|
||||
self.value = Some(if self.typed {
|
||||
let mut payload = Payload::default();
|
||||
reader.record(&mut payload)?;
|
||||
Value::Typed(payload)
|
||||
} else {
|
||||
Value::Raw(reader.json()?)
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
async fn call(mut request: Request, typed: bool) -> Response {
|
||||
if let Err(error) = auth::require_components(&[], &[]).await {
|
||||
return api::auth_error(error);
|
||||
}
|
||||
if let Err(error) = api::upload::read_body(&mut request).await {
|
||||
return api::invalid_input(error);
|
||||
}
|
||||
let body = match api::Inputs::json_body(&request) {
|
||||
Ok(body) => body,
|
||||
Err(error) => return api::invalid_input(error),
|
||||
};
|
||||
let mut arguments = Arguments { typed, value: None };
|
||||
if let Err(error) = decode::read(body, &mut |reader| reader.record(&mut arguments)) {
|
||||
return api::invalid_input(api::InputError(error));
|
||||
}
|
||||
let encoded = match arguments.value {
|
||||
Some(Value::Typed(payload)) => payload.encode(),
|
||||
Some(Value::Raw(value)) => Ok(value),
|
||||
None => Err("missing required wire field".into()),
|
||||
};
|
||||
match encoded {
|
||||
Ok(value) => {
|
||||
if api::commit_response_metadata().is_err() {
|
||||
return api::internal_error();
|
||||
}
|
||||
api::success(value)
|
||||
}
|
||||
Err(_) => api::internal_error(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn route(request: Request) -> Result<Response, String> {
|
||||
let typed = match request.target.split('?').next().unwrap_or("") {
|
||||
"/benchmark/echo/echo" => true,
|
||||
"/benchmark/echo/raw" => false,
|
||||
_ => return Ok(api::not_found()),
|
||||
};
|
||||
if request.method != "POST" {
|
||||
return Ok(api::method_not_allowed(&["POST"]));
|
||||
}
|
||||
let prepared = match auth::prepare(&[], &request, true) {
|
||||
Ok(prepared) => prepared,
|
||||
Err(error) => return Ok(api::auth_error(error)),
|
||||
};
|
||||
let identity = prepared.claims().map(|claims| auth::Identity {
|
||||
id: claims.subject.clone(),
|
||||
user_id: None,
|
||||
tenant_id: None,
|
||||
});
|
||||
Ok(
|
||||
match auth::scope(prepared, identity, call(request, typed)).await {
|
||||
Ok(response) => response,
|
||||
Err(error) => api::auth_error(error),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn main() -> Result<(), String> {
|
||||
let session = Session::load(
|
||||
RuntimeProfile {
|
||||
sqlite: true,
|
||||
postgres: false,
|
||||
},
|
||||
&[(None, "benchmark", false)],
|
||||
&[],
|
||||
)?;
|
||||
session.initialize_authorization(false)?;
|
||||
let cleanup = session.clone();
|
||||
task::run_entry_with_typed_cleanup(
|
||||
task::RuntimeConfig::default(),
|
||||
session.clone().scope(async move {
|
||||
session
|
||||
.databases()
|
||||
.prepare()
|
||||
.await
|
||||
.map_err(|error| error.to_string())?;
|
||||
lifecycle::configure(true, false)?;
|
||||
lifecycle::run(api::serve(route)).await
|
||||
}),
|
||||
|result| async move {
|
||||
let closed = cleanup.close().await;
|
||||
result.and(closed)
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
#include "runtime.h"
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
extern long long dever_test_allocations(void);
|
||||
|
||||
static void text_clone(const void *source, void *target) {
|
||||
*(void **)target = dever_rt_v1_text_retain(*(void *const *)source);
|
||||
}
|
||||
|
||||
static void text_drop(void *value) {
|
||||
dever_rt_v1_text_release(*(void **)value);
|
||||
*(void **)value = NULL;
|
||||
}
|
||||
|
||||
static uint8_t text_equal(const void *left, const void *right) {
|
||||
return dever_rt_v1_text_equal(*(void *const *)left, *(void *const *)right);
|
||||
}
|
||||
|
||||
static const DeverRtType text_type = {
|
||||
sizeof(void *), _Alignof(void *), text_clone, text_drop, text_equal, NULL
|
||||
};
|
||||
|
||||
static uint32_t input_field(void *inputs, void *out, DeverRtBuffer *error) {
|
||||
const DeverRtWireName name = {(const uint8_t *)"source", 6};
|
||||
uint8_t present = 0;
|
||||
return dever_rt_v1_cmd_input_field(inputs, &name, 1, out, &present, error);
|
||||
}
|
||||
|
||||
static uint32_t decode(const void *reader, void *out, DeverRtBuffer *error) {
|
||||
return dever_rt_v1_wire_read_raw(reader, out, error);
|
||||
}
|
||||
|
||||
static uint32_t encode(const void *value, void *encoder, DeverRtBuffer *error) {
|
||||
return dever_rt_v1_wire_encoder_json(encoder, *(void *const *)value, error);
|
||||
}
|
||||
|
||||
static uint32_t operation(unsigned phase, void *body, void *source, void **out, DeverRtBuffer *error) {
|
||||
if (phase == 0) return dever_rt_v1_cmd_decode(body, &text_type, input_field, out, error);
|
||||
if (phase == 1) return dever_rt_v1_wire_decode(source, &text_type, decode, out, error);
|
||||
if (phase == 2) return dever_rt_v1_wire_encode(&source, &text_type, encode, out, error);
|
||||
return dever_rt_v1_json_from_text(source, out, error);
|
||||
}
|
||||
|
||||
int dever_test_run(void) {
|
||||
const char *setting = getenv("DEVER_WIRE_BENCH_ROWS");
|
||||
size_t rows = setting == NULL ? 128 : (size_t)strtoul(setting, NULL, 10);
|
||||
int wide = getenv("DEVER_WIRE_BENCH_WIDE") != NULL;
|
||||
if (rows != 1 && rows != 16 && rows != 128 && !(wide && rows == 1024)) return 1;
|
||||
const char *row = "{\"id\":9007199254740993,\"text\":\"雪😀\\n\\\"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\",\"weight\":1.25,\"note\":null}";
|
||||
const char *suffix = "],\"raw\":{\"large\":123456789012345678901234567890}}";
|
||||
size_t capacity = rows * (strlen(row) + 1) + strlen(suffix) + 64;
|
||||
char *raw = malloc(capacity);
|
||||
if (raw == NULL) return 2;
|
||||
strcpy(raw, wide ? "{" : "{\"rows\":[");
|
||||
for (size_t index = 0; index < rows; ++index) {
|
||||
if (index != 0) strcat(raw, ",");
|
||||
if (wide) {
|
||||
char field[64];
|
||||
snprintf(field, sizeof(field), "\"field_%zu\":%zu", index, index);
|
||||
strcat(raw, field);
|
||||
} else {
|
||||
strcat(raw, row);
|
||||
}
|
||||
}
|
||||
strcat(raw, wide ? "}" : suffix);
|
||||
size_t length = strlen(raw);
|
||||
DeverRtBuffer error = {0};
|
||||
void *source = NULL, *body = NULL;
|
||||
if (dever_rt_v1_text_new((const uint8_t *)raw, length, &source, &error)) return 3;
|
||||
if (getenv("DEVER_WIRE_BENCH_VALIDATED") != NULL) {
|
||||
void *validated = NULL;
|
||||
if (dever_rt_v1_wire_decode(source, &text_type, decode, &validated, &error)) return 11;
|
||||
dever_rt_v1_text_release(source);
|
||||
source = validated;
|
||||
}
|
||||
char *request = malloc(length + 12);
|
||||
if (request == NULL) return 4;
|
||||
snprintf(request, length + 12, "{\"source\":%s}", raw);
|
||||
if (dever_rt_v1_text_new((const uint8_t *)request, strlen(request), &body, &error)) return 5;
|
||||
free(request);
|
||||
for (unsigned phase = 0; phase < 4; ++phase) {
|
||||
void *output = NULL;
|
||||
if (operation(phase, body, source, &output, &error)) return 6;
|
||||
DeverRtBuffer bytes = {0};
|
||||
if (dever_rt_v1_text_export(output, &bytes) || bytes.len != length || memcmp(bytes.ptr, raw, length) != 0) return 7;
|
||||
dever_rt_v1_buffer_free(bytes.ptr, bytes.len);
|
||||
dever_rt_v1_text_release(output);
|
||||
struct timespec started, finished;
|
||||
if (clock_gettime(CLOCK_MONOTONIC, &started)) return 8;
|
||||
long long allocations = dever_test_allocations();
|
||||
const size_t iterations = 500;
|
||||
for (size_t iteration = 0; iteration < iterations; ++iteration) {
|
||||
if (operation(phase, body, source, &output, &error)) return 9;
|
||||
dever_rt_v1_text_release(output);
|
||||
}
|
||||
allocations = dever_test_allocations() - allocations;
|
||||
if (clock_gettime(CLOCK_MONOTONIC, &finished)) return 10;
|
||||
int64_t elapsed = (int64_t)(finished.tv_sec - started.tv_sec) * INT64_C(1000000000)
|
||||
+ finished.tv_nsec - started.tv_nsec;
|
||||
printf("{\"phase\":%u,\"rows\":%zu,\"iterations\":%zu,\"elapsed_ns\":%lld,\"allocations\":%lld}\n",
|
||||
phase, rows, iterations, (long long)elapsed, allocations);
|
||||
}
|
||||
dever_rt_v1_text_release(source);
|
||||
dever_rt_v1_text_release(body);
|
||||
free(raw);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
#include "runtime.h"
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
extern long long dever_test_allocations(void);
|
||||
|
||||
#ifndef WIRE_TEXT_KEY
|
||||
#define WIRE_TEXT_KEY "text"
|
||||
#endif
|
||||
#ifndef WIRE_NUMBER_KEY
|
||||
#define WIRE_NUMBER_KEY "number"
|
||||
#endif
|
||||
#ifndef WIRE_RECORD
|
||||
#define WIRE_RECORD 0
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
void *text;
|
||||
int64_t rows;
|
||||
} Payload;
|
||||
|
||||
#if WIRE_RECORD != 2
|
||||
static const DeverRtWireName fields[] = {
|
||||
{(const uint8_t *)WIRE_TEXT_KEY, sizeof(WIRE_TEXT_KEY) - 1},
|
||||
{(const uint8_t *)WIRE_NUMBER_KEY, sizeof(WIRE_NUMBER_KEY) - 1}
|
||||
};
|
||||
#endif
|
||||
|
||||
static void payload_clone(const void *source, void *destination) {
|
||||
const Payload *input = source;
|
||||
Payload *output = destination;
|
||||
*output = *input;
|
||||
output->text = dever_rt_v1_text_retain(input->text);
|
||||
}
|
||||
|
||||
static void payload_drop(void *value) {
|
||||
Payload *payload = value;
|
||||
dever_rt_v1_text_release(payload->text);
|
||||
payload->text = NULL;
|
||||
}
|
||||
|
||||
static uint8_t payload_equal(const void *left, const void *right) {
|
||||
const Payload *first = left;
|
||||
const Payload *second = right;
|
||||
return first->rows == second->rows && dever_rt_v1_text_equal(first->text, second->text);
|
||||
}
|
||||
|
||||
static const DeverRtType payload_type = {
|
||||
sizeof(Payload), _Alignof(Payload), payload_clone, payload_drop, payload_equal, NULL
|
||||
};
|
||||
|
||||
#if WIRE_RECORD == 2
|
||||
static const DeverRtWireName prefixes[] = {
|
||||
{(const uint8_t *)"\"" WIRE_TEXT_KEY "\":", sizeof("\"" WIRE_TEXT_KEY "\":") - 1},
|
||||
{(const uint8_t *)",\"" WIRE_NUMBER_KEY "\":", sizeof(",\"" WIRE_NUMBER_KEY "\":") - 1}
|
||||
};
|
||||
static uint32_t encode_fields(const void *value, void *writer, DeverRtBuffer *error) {
|
||||
const Payload *payload = value;
|
||||
uint32_t status = dever_rt_v1_wire_encoder_compiled_key(writer, &prefixes[0], error);
|
||||
if (status == 0) status = dever_rt_v1_wire_encoder_text(writer, payload->text, error);
|
||||
if (status == 0) status = dever_rt_v1_wire_encoder_compiled_key(writer, &prefixes[1], error);
|
||||
if (status == 0) status = dever_rt_v1_wire_encoder_int(writer, INT64_C(9007199254740993), error);
|
||||
return status;
|
||||
}
|
||||
#elif WIRE_RECORD
|
||||
static uint32_t encode_field(uint64_t index, const void *value, void *writer, DeverRtBuffer *error) {
|
||||
const Payload *payload = value;
|
||||
if (index == 0) return dever_rt_v1_wire_encoder_text(writer, payload->text, error);
|
||||
if (index == 1) return dever_rt_v1_wire_encoder_int(writer, INT64_C(9007199254740993), error);
|
||||
return 2;
|
||||
}
|
||||
#endif
|
||||
|
||||
static uint32_t encode(const void *value, void *writer, DeverRtBuffer *error) {
|
||||
const Payload *payload = value;
|
||||
uint32_t status = dever_rt_v1_wire_encoder_begin_array(writer, error);
|
||||
for (int64_t index = 0; index < payload->rows && status == 0; ++index) {
|
||||
#if WIRE_RECORD == 2
|
||||
status = dever_rt_v1_wire_encoder_compiled_record(writer, value, &payload_type, 2, encode_fields, error);
|
||||
#elif WIRE_RECORD
|
||||
status = dever_rt_v1_wire_encoder_record(writer, value, &payload_type, fields, 2, encode_field, error);
|
||||
#else
|
||||
status = dever_rt_v1_wire_encoder_begin_object(writer, error);
|
||||
if (status == 0) status = dever_rt_v1_wire_encoder_key(writer, &fields[0], error);
|
||||
if (status == 0) status = dever_rt_v1_wire_encoder_text(writer, payload->text, error);
|
||||
if (status == 0) status = dever_rt_v1_wire_encoder_key(writer, &fields[1], error);
|
||||
if (status == 0) status = dever_rt_v1_wire_encoder_int(writer, INT64_C(9007199254740993), error);
|
||||
if (status == 0) status = dever_rt_v1_wire_encoder_end(writer, error);
|
||||
#endif
|
||||
}
|
||||
if (status == 0) status = dever_rt_v1_wire_encoder_end(writer, error);
|
||||
return status;
|
||||
}
|
||||
|
||||
int dever_test_run(void) {
|
||||
const char *configured = getenv("DEVER_WIRE_BENCH_ROWS");
|
||||
int64_t rows = configured == NULL ? 128 : strtoll(configured, NULL, 10);
|
||||
if (rows != 4 && rows != 128) return 1;
|
||||
const char *text = "Unicode 雪😀 and newline\n";
|
||||
const char *row = "{\"" WIRE_TEXT_KEY "\":\"Unicode 雪😀 and newline\\n\",\"" WIRE_NUMBER_KEY "\":9007199254740993}";
|
||||
Payload payload = {NULL, rows};
|
||||
DeverRtBuffer error = {0};
|
||||
if (dever_rt_v1_text_new((const uint8_t *)text, strlen(text), &payload.text, &error)) return 2;
|
||||
void *output = NULL;
|
||||
if (dever_rt_v1_wire_encode(&payload, &payload_type, encode, &output, &error)) return 3;
|
||||
DeverRtBuffer bytes = {0};
|
||||
if (dever_rt_v1_text_export(output, &bytes)) return 4;
|
||||
size_t row_length = strlen(row);
|
||||
if (bytes.len != 1 + (row_length + 1) * (size_t)rows || bytes.ptr[0] != '[' || bytes.ptr[bytes.len - 1] != ']') return 5;
|
||||
for (int64_t index = 0; index < rows; ++index) {
|
||||
if (memcmp(bytes.ptr + 1 + (size_t)index * (row_length + 1), row, row_length) != 0) return 6;
|
||||
}
|
||||
dever_rt_v1_buffer_free(bytes.ptr, bytes.len);
|
||||
dever_rt_v1_text_release(output);
|
||||
struct timespec started, finished;
|
||||
if (clock_gettime(CLOCK_MONOTONIC, &started)) return 7;
|
||||
long long allocations = dever_test_allocations();
|
||||
const int64_t iterations = 500;
|
||||
for (int64_t iteration = 0; iteration < iterations; ++iteration) {
|
||||
if (dever_rt_v1_wire_encode(&payload, &payload_type, encode, &output, &error)) return 8;
|
||||
dever_rt_v1_text_release(output);
|
||||
}
|
||||
allocations = dever_test_allocations() - allocations;
|
||||
if (clock_gettime(CLOCK_MONOTONIC, &finished)) return 9;
|
||||
int64_t elapsed = (int64_t)(finished.tv_sec - started.tv_sec) * INT64_C(1000000000)
|
||||
+ finished.tv_nsec - started.tv_nsec;
|
||||
payload_drop(&payload);
|
||||
printf("{\"rows\":%lld,\"iterations\":%lld,\"elapsed_ns\":%lld,\"allocations\":%lld}\n",
|
||||
(long long)rows, (long long)iterations, (long long)elapsed, allocations);
|
||||
return 0;
|
||||
}
|
||||
Reference in new issue
Block a user