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;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
ready(port: Int) () {
|
||||
dever.io.println("READY|" + int.toText(port))
|
||||
}
|
||||
|
||||
pause(millis: Int) () {
|
||||
dever.task.sleep(millis)
|
||||
}
|
||||
|
||||
report(
|
||||
begin: Int,
|
||||
finish: Int,
|
||||
name: Text,
|
||||
count: Int,
|
||||
checksum: Int
|
||||
) () recover("基准失败输出由运行器检查") {
|
||||
dever.io.println(
|
||||
"SAMPLE|" + name + "|" + int.toText(count) + "|" + int.toText(finish - begin) + "|" + int.toText(
|
||||
checksum
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
long cmd run = app.run
|
||||
|
||||
long cmd continuous = app.continuous
|
||||
@@ -0,0 +1,39 @@
|
||||
type InputResult {
|
||||
Parsed(value: Decimal)
|
||||
error Invalid
|
||||
}
|
||||
|
||||
type State {
|
||||
total: Decimal
|
||||
factor: Decimal
|
||||
}
|
||||
|
||||
type Sample {
|
||||
elapsed_ns: Int
|
||||
checksum: Text
|
||||
}
|
||||
|
||||
parsed(value: Decimal) (result: Decimal) { result = value }
|
||||
parsed(value: null) (result: Decimal) { fail(InputResult.Invalid) }
|
||||
|
||||
step(value: Int, state: State) (next: State) {
|
||||
next = State {
|
||||
total = state.total + decimal.fromInt(value) * state.factor
|
||||
factor = state.factor
|
||||
}
|
||||
}
|
||||
|
||||
run(ticks: Text, initial: Text, factor: Text) (sample: Sample) {
|
||||
bytes = dever.bytes.fromText(ticks)
|
||||
state = State { total = parsed(decimal.parse(initial))
|
||||
factor = parsed(decimal.parse(factor)) }
|
||||
begin = dever.time.monotonicNanos()
|
||||
final = reduce(step, bytes, state)
|
||||
end = dever.time.monotonicNanos()
|
||||
sample = Sample { elapsed_ns = end - begin
|
||||
checksum = decimal.toText(final.total) }
|
||||
}
|
||||
|
||||
continuous(ticks: Text, initial: Text, factor: Text) (samples: List<Sample>) {
|
||||
samples = [run(ticks, initial, factor), run(ticks, initial, factor), run(ticks, initial, factor), run(ticks, initial, factor), run(ticks, initial, factor), run(ticks, initial, factor), run(ticks, initial, factor), run(ticks, initial, factor), run(ticks, initial, factor), run(ticks, initial, factor)]
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
use dec::{Context, Decimal128, Rounding};
|
||||
use dever_runtime::number::DecimalValue;
|
||||
use std::hint::black_box;
|
||||
use std::time::Instant;
|
||||
|
||||
fn finite(context: &Context<Decimal128>, value: Decimal128) -> Decimal128 {
|
||||
let status = context.status();
|
||||
assert!(!status.division_by_zero() && !status.division_undefined());
|
||||
assert!(!status.overflow() && !status.underflow());
|
||||
assert!(!status.invalid_operation() && !status.invalid_context() && value.is_finite());
|
||||
value
|
||||
}
|
||||
|
||||
fn measure(mode: &str, ticks: &str, initial: &str, factor: &str) -> serde_json::Value {
|
||||
let bytes = black_box(ticks.as_bytes());
|
||||
let (checksum, elapsed) = if mode == "runtime" {
|
||||
let mut total = DecimalValue::parse(initial).expect("initial");
|
||||
let factor = DecimalValue::parse(factor).expect("factor");
|
||||
let begin = Instant::now();
|
||||
for value in bytes {
|
||||
let product = DecimalValue::from_int(i64::from(*value))
|
||||
.checked_mul(factor)
|
||||
.expect("multiply");
|
||||
total = total.checked_add(product).expect("add");
|
||||
}
|
||||
black_box(total);
|
||||
let elapsed = begin.elapsed();
|
||||
(total.to_string(), elapsed)
|
||||
} else {
|
||||
assert_eq!(mode, "decimal128");
|
||||
let mut context = Context::<Decimal128>::default();
|
||||
let mut total = context.parse(initial).expect("initial");
|
||||
assert!(!context.status().inexact() && total.is_finite());
|
||||
context.clear_status();
|
||||
let factor = context.parse(factor).expect("factor");
|
||||
assert!(!context.status().inexact() && factor.is_finite());
|
||||
context.set_rounding(Rounding::HalfEven);
|
||||
let begin = Instant::now();
|
||||
for value in bytes {
|
||||
let item = Decimal128::from(i64::from(*value));
|
||||
context.clear_status();
|
||||
let product = context.mul(item, factor);
|
||||
let product = finite(&context, product);
|
||||
context.clear_status();
|
||||
let sum = context.add(total, product);
|
||||
total = finite(&context, sum);
|
||||
}
|
||||
black_box(total);
|
||||
let elapsed = begin.elapsed();
|
||||
(context.reduce(total).to_string(), elapsed)
|
||||
};
|
||||
serde_json::json!({"checksum": checksum, "elapsed_ns": elapsed.as_nanos()})
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let arguments = std::env::args().collect::<Vec<_>>();
|
||||
let input: serde_json::Value = serde_json::from_str(&arguments[3]).expect("input object");
|
||||
let ticks = input["ticks"].as_str().expect("ticks");
|
||||
let initial = input["initial"].as_str().expect("initial");
|
||||
let factor = input["factor"].as_str().expect("factor");
|
||||
let output = match arguments[2].as_str() {
|
||||
"run" => measure(&arguments[1], ticks, initial, factor),
|
||||
"continuous" => serde_json::Value::Array(
|
||||
(0..10)
|
||||
.map(|_| measure(&arguments[1], ticks, initial, factor))
|
||||
.collect(),
|
||||
),
|
||||
_ => panic!("unknown measurement"),
|
||||
};
|
||||
println!("{output}");
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
type Content {
|
||||
body: Bytes
|
||||
kind: Text
|
||||
}
|
||||
|
||||
listening(result: dever.net.ListenResult.Listening(listener)) (
|
||||
value: dever.system.Listener
|
||||
) {
|
||||
value = listener
|
||||
}
|
||||
|
||||
listening(result: dever.net.ListenResult.Failed(message)) (value: dever.system.Listener) {
|
||||
fail(app.WorkerFailure.Failed(message))
|
||||
}
|
||||
|
||||
bound(result: dever.net.PortResult.Bound(port)) (value: Int) {
|
||||
value = port
|
||||
}
|
||||
|
||||
bound(result: dever.net.PortResult.Failed(message)) (value: Int) {
|
||||
fail(app.WorkerFailure.Failed(message))
|
||||
}
|
||||
|
||||
paused(result: dever.time.SleepResult.Done) () {}
|
||||
|
||||
paused(result: dever.time.SleepResult.Failed(message)) () {
|
||||
fail(app.WorkerFailure.Failed(message))
|
||||
}
|
||||
|
||||
closed(result: dever.io.CloseResult.Closed) () {}
|
||||
|
||||
closed(result: dever.io.CloseResult.Failed(message)) () {
|
||||
fail(app.WorkerFailure.Failed(message))
|
||||
}
|
||||
|
||||
serverTls(result: dever.tls.ServerResult.Ready(tls)) (value: dever.system.ServerTls) {
|
||||
value = tls
|
||||
}
|
||||
|
||||
serverTls(result: dever.tls.ServerResult.Failed(message)) (
|
||||
value: dever.system.ServerTls
|
||||
) {
|
||||
fail(app.WorkerFailure.Failed(message))
|
||||
}
|
||||
|
||||
responseValue(content: Content) (result: dever.http.Response) {
|
||||
result = dever.http.Response {
|
||||
status = 200
|
||||
headers = [dever.http.header("Content-Type", content.kind)]
|
||||
body = content.body
|
||||
}
|
||||
}
|
||||
|
||||
route(request: dever.http.Request, large: Bytes) (response: dever.http.Response) {
|
||||
response = responseValue(select(request.target, large))
|
||||
}
|
||||
|
||||
select(path: "/plain", large: Bytes) (result: Content) {
|
||||
result = Content {
|
||||
body = dever.bytes.fromText("hello")
|
||||
kind = "text/plain"
|
||||
}
|
||||
}
|
||||
|
||||
select(path: "/json", large: Bytes) (result: Content) {
|
||||
result = Content {
|
||||
body = dever.bytes.fromText("{\"ok\":true}")
|
||||
kind = "application/json"
|
||||
}
|
||||
}
|
||||
|
||||
select(path: "/bytes", large: Bytes) (result: Content) {
|
||||
result = Content {
|
||||
body = large
|
||||
kind = "application/octet-stream"
|
||||
}
|
||||
}
|
||||
|
||||
select(path: other, large: Bytes) (result: Content) {
|
||||
result = Content {
|
||||
body = dever.bytes.fromText("unknown benchmark path")
|
||||
kind = "text/plain"
|
||||
}
|
||||
}
|
||||
|
||||
serve(
|
||||
listener: dever.system.Listener,
|
||||
tls: null,
|
||||
limits: dever.http.Limits,
|
||||
large: Bytes
|
||||
) () {
|
||||
dever.system.httpServe(route, listener, limits, large)
|
||||
}
|
||||
|
||||
serve(
|
||||
listener: dever.system.Listener,
|
||||
tls: dever.system.ServerTls,
|
||||
limits: dever.http.Limits,
|
||||
large: Bytes
|
||||
) () {
|
||||
dever.system.httpServeTls(route, listener, limits, tls, large)
|
||||
}
|
||||
|
||||
worker(
|
||||
tls: dever.system.ServerTls?,
|
||||
limits: dever.http.Limits,
|
||||
large: Bytes,
|
||||
lifetime: Int
|
||||
) () {
|
||||
listener = listening(result(dever.net.listen("127.0.0.1", 0)))
|
||||
port = bound(result(dever.net.port(listener)))
|
||||
server = run(serve(listener, tls, limits, large))
|
||||
blocking(dever.io.println("READY|" + int.toText(port)))
|
||||
paused(result(dever.task.sleep(lifetime)))
|
||||
closed(result(dever.net.closeListener(listener)))
|
||||
stop(server)
|
||||
}
|
||||
|
||||
port.plain(limits: dever.http.Limits, large: Bytes, lifetime: Int) () {
|
||||
worker(null, limits, large, lifetime)
|
||||
}
|
||||
|
||||
port.secure(
|
||||
limits: dever.http.Limits,
|
||||
large: Bytes,
|
||||
lifetime: Int,
|
||||
certificate: Bytes,
|
||||
key: Bytes
|
||||
) () {
|
||||
worker(
|
||||
serverTls(result(dever.tls.server(certificate, key))),
|
||||
limits,
|
||||
large,
|
||||
lifetime
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
cmd http = app.http
|
||||
|
||||
cmd https = app.https
|
||||
|
||||
cmd http2 = app.http2
|
||||
|
||||
cmd https2 = app.https2
|
||||
@@ -0,0 +1,35 @@
|
||||
type WorkerFailure {
|
||||
error Failed(message: Text)
|
||||
}
|
||||
|
||||
http() (result: Bool) {
|
||||
port.plain(config.limits(), config.largeBody(), config.lifetime())
|
||||
result = true
|
||||
}
|
||||
|
||||
https() (result: Bool) {
|
||||
port.secure(
|
||||
config.limits(),
|
||||
config.largeBody(),
|
||||
config.lifetime(),
|
||||
config.certificate(),
|
||||
config.key()
|
||||
)
|
||||
result = true
|
||||
}
|
||||
|
||||
http2() (result: Bool) {
|
||||
port.plain(config.http2Limits(), config.largeBody(), config.lifetime())
|
||||
result = true
|
||||
}
|
||||
|
||||
https2() (result: Bool) {
|
||||
port.secure(
|
||||
config.http2Limits(),
|
||||
config.largeBody(),
|
||||
config.lifetime(),
|
||||
config.certificate(),
|
||||
config.key()
|
||||
)
|
||||
result = true
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
//! 独立 Axum 路由;用官方 Hyper 入口统一连接、报文和超时上限。
|
||||
use axum::{
|
||||
Router,
|
||||
body::Bytes,
|
||||
extract::DefaultBodyLimit,
|
||||
http::header,
|
||||
routing::{get, post},
|
||||
};
|
||||
use hyper_util::{
|
||||
rt::{TokioIo, TokioTimer},
|
||||
service::TowerToHyperService,
|
||||
};
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use tokio::{net::TcpListener, sync::Semaphore};
|
||||
|
||||
async fn serve(capacity: usize) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let router = Router::new()
|
||||
.route(
|
||||
"/json",
|
||||
get(async |_: Bytes| {
|
||||
(
|
||||
[(header::CONTENT_TYPE, "application/json")],
|
||||
"{\"ok\":true}",
|
||||
)
|
||||
}),
|
||||
)
|
||||
.route(
|
||||
"/echo",
|
||||
post(async |body: Bytes| ([(header::CONTENT_TYPE, "application/octet-stream")], body)),
|
||||
)
|
||||
.layer(DefaultBodyLimit::max(65536));
|
||||
let listener = TcpListener::bind("127.0.0.1:0").await?;
|
||||
let slots = Arc::new(Semaphore::new(capacity));
|
||||
println!("READY|{}", listener.local_addr()?.port());
|
||||
loop {
|
||||
let permit = slots.clone().acquire_owned().await?;
|
||||
let (stream, _) = listener.accept().await?;
|
||||
stream.set_nodelay(true)?;
|
||||
let service = TowerToHyperService::new(router.clone());
|
||||
tokio::spawn(async move {
|
||||
let _permit = permit;
|
||||
let mut builder = hyper::server::conn::http1::Builder::new();
|
||||
builder
|
||||
.timer(TokioTimer::new())
|
||||
.header_read_timeout(Duration::from_secs(5))
|
||||
.max_buf_size(8192);
|
||||
if let Err(error) = builder
|
||||
.serve_connection(TokioIo::new(stream), service)
|
||||
.await
|
||||
{
|
||||
if !error.is_incomplete_message() && !error.is_closed() {
|
||||
eprintln!("connection: {error}");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let capacity = std::env::args().nth(1).unwrap().parse()?;
|
||||
tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(2)
|
||||
.enable_all()
|
||||
.build()?
|
||||
.block_on(serve(capacity))
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
route(request: dever.http.Request) (response: dever.http.Response) {
|
||||
response = selected(request.target, request.body)
|
||||
}
|
||||
|
||||
selected(path: "/json", body: Bytes) (response: dever.http.Response) {
|
||||
response = dever.http.Response { status = 200, headers = [dever.http.header("content-type", "application/json")], body = dever.bytes.fromText("{\"ok\":true}") }
|
||||
}
|
||||
selected(path: "/echo", body: Bytes) (response: dever.http.Response) {
|
||||
response = dever.http.Response { status = 200, headers = [dever.http.header("content-type", "application/octet-stream")], body = body }
|
||||
}
|
||||
selected(path: other, body: Bytes) (response: dever.http.Response) {
|
||||
response = dever.http.Response { status = 404, headers = [], body = dever.bytes.fromText("") }
|
||||
}
|
||||
|
||||
serve(connections: Int) () {
|
||||
listener = dever.net.listen("127.0.0.1", 0)
|
||||
port = dever.net.port(listener)
|
||||
limits = dever.http.defaultLimits()
|
||||
limits.connections = connections
|
||||
limits.body_bytes = 65536
|
||||
blocking(dever.io.println("READY|" + int.toText(port)))
|
||||
dever.http.serve(route, listener, limits)
|
||||
}
|
||||
|
||||
main() () {
|
||||
serve(2048)
|
||||
}
|
||||
|
||||
overload() () {
|
||||
serve(8192)
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type listener struct {
|
||||
net.Listener
|
||||
slots chan struct{}
|
||||
}
|
||||
|
||||
type connection struct {
|
||||
net.Conn
|
||||
slots chan struct{}
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
func (value *connection) Close() error {
|
||||
err := value.Conn.Close()
|
||||
value.once.Do(func() { <-value.slots })
|
||||
return err
|
||||
}
|
||||
|
||||
func (value *listener) Accept() (net.Conn, error) {
|
||||
value.slots <- struct{}{}
|
||||
stream, err := value.Listener.Accept()
|
||||
if err != nil {
|
||||
<-value.slots
|
||||
return nil, err
|
||||
}
|
||||
return &connection{Conn: stream, slots: value.slots}, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
capacity, err := strconv.Atoi(os.Args[1])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
runtime.GOMAXPROCS(2)
|
||||
bound, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
server := http.Server{
|
||||
ReadTimeout: 5 * time.Second, WriteTimeout: 5 * time.Second,
|
||||
IdleTimeout: 5 * time.Second, MaxHeaderBytes: 8192,
|
||||
Handler: http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||||
defer request.Body.Close()
|
||||
body, err := io.ReadAll(http.MaxBytesReader(writer, request.Body, 65536))
|
||||
if err != nil {
|
||||
http.Error(writer, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
switch request.URL.RequestURI() {
|
||||
case "/json":
|
||||
writer.Header().Set("Content-Type", "application/json")
|
||||
writer.Write([]byte(`{"ok":true}`))
|
||||
case "/echo":
|
||||
writer.Header().Set("Content-Type", "application/octet-stream")
|
||||
writer.Write(body)
|
||||
default:
|
||||
writer.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
}),
|
||||
}
|
||||
fmt.Printf("READY|%d\n", bound.Addr().(*net.TCPAddr).Port)
|
||||
if err := server.Serve(&listener{Listener: bound, slots: make(chan struct{}, capacity)}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
//! 独立 Hyper 服务及三种语言共用的 HTTP/1.1 客户端,不链接 Dever 运行库。
|
||||
use bytes::Bytes;
|
||||
use http_body_util::{BodyExt, Full, Limited};
|
||||
use hyper::{Request, Response, body::Incoming, service::service_fn};
|
||||
use hyper_util::rt::{TokioIo, TokioTimer};
|
||||
use std::{convert::Infallible, sync::Arc, time::Duration};
|
||||
use tokio::{net::TcpListener, sync::{Barrier, Semaphore}, time::Instant};
|
||||
|
||||
async fn route(request: Request<Incoming>) -> Result<Response<Full<Bytes>>, Infallible> {
|
||||
let path = request.uri().path().to_owned();
|
||||
let body = match Limited::new(request.into_body(), 65536).collect().await {
|
||||
Ok(body) => body.to_bytes(),
|
||||
Err(_) => return Ok(Response::builder().status(400).body(Full::new(Bytes::new())).unwrap()),
|
||||
};
|
||||
let (status, kind, body) = match path.as_str() {
|
||||
"/json" => (200, "application/json", Bytes::from_static(b"{\"ok\":true}")),
|
||||
"/echo" => (200, "application/octet-stream", body),
|
||||
_ => (404, "text/plain", Bytes::new()),
|
||||
};
|
||||
Ok(Response::builder().status(status).header("content-type", kind).body(Full::new(body)).unwrap())
|
||||
}
|
||||
|
||||
async fn serve(capacity: usize) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let listener = TcpListener::bind("127.0.0.1:0").await?;
|
||||
let slots = Arc::new(Semaphore::new(capacity));
|
||||
println!("READY|{}", listener.local_addr()?.port());
|
||||
loop {
|
||||
let permit = slots.clone().acquire_owned().await?;
|
||||
let (stream, _) = listener.accept().await?;
|
||||
stream.set_nodelay(true)?;
|
||||
tokio::spawn(async move {
|
||||
let _permit = permit;
|
||||
let mut builder = hyper::server::conn::http1::Builder::new();
|
||||
builder.timer(TokioTimer::new()).header_read_timeout(Duration::from_secs(5)).max_buf_size(8192);
|
||||
if let Err(error) = builder.serve_connection(TokioIo::new(stream), service_fn(route)).await {
|
||||
if !error.is_incomplete_message() && !error.is_closed() {
|
||||
eprintln!("connection: {error}");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async fn load(port: u16, path: String, concurrency: usize, seconds: f64) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let payload = if path == "/echo" { Bytes::from(vec![b'x'; 65536]) } else { Bytes::new() };
|
||||
let expected = if path == "/echo" { payload.clone() } else { Bytes::from_static(b"{\"ok\":true}") };
|
||||
let kind = if path == "/echo" { "application/octet-stream" } else { "application/json" };
|
||||
let barrier = Arc::new(Barrier::new(concurrency + 1));
|
||||
let mut workers = Vec::new();
|
||||
for _ in 0..concurrency {
|
||||
let stream = tokio::net::TcpStream::connect(("127.0.0.1", port)).await?;
|
||||
stream.set_nodelay(true)?;
|
||||
let (mut sender, connection) = hyper::client::conn::http1::handshake(TokioIo::new(stream)).await?;
|
||||
let driver = tokio::spawn(connection);
|
||||
let barrier = barrier.clone();
|
||||
let payload = payload.clone();
|
||||
let expected = expected.clone();
|
||||
let path = path.clone();
|
||||
workers.push(tokio::spawn(async move {
|
||||
barrier.wait().await;
|
||||
let deadline = Instant::now() + Duration::from_secs_f64(seconds);
|
||||
let mut samples = Vec::new();
|
||||
let mut errors = 0_u64;
|
||||
while Instant::now() < deadline {
|
||||
let request = Request::builder().method(if path == "/echo" { "POST" } else { "GET" })
|
||||
.uri(&path).header("host", "localhost").body(Full::new(payload.clone())).unwrap();
|
||||
let started = Instant::now();
|
||||
let exchange = async {
|
||||
let response = sender.send_request(request).await.map_err(|error| error.to_string())?;
|
||||
if response.status() != 200 || response.headers().get("content-type").and_then(|value| value.to_str().ok()) != Some(kind) {
|
||||
return Err("status or content type differs".to_owned());
|
||||
}
|
||||
let body = response.into_body().collect().await.map_err(|error| error.to_string())?.to_bytes();
|
||||
if body != expected { return Err("response body differs".to_owned()); }
|
||||
Ok(())
|
||||
};
|
||||
match tokio::time::timeout(Duration::from_secs(5), exchange).await {
|
||||
Ok(Ok(())) => samples.push(started.elapsed().as_micros() as u64),
|
||||
result => {
|
||||
eprintln!("request: {result:?}");
|
||||
errors += 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
drop(sender);
|
||||
driver.abort();
|
||||
let _ = driver.await;
|
||||
(samples, errors)
|
||||
}));
|
||||
}
|
||||
let started = Instant::now();
|
||||
barrier.wait().await;
|
||||
let mut samples = Vec::new();
|
||||
let mut errors = 0;
|
||||
for worker in workers {
|
||||
let (mut values, failed) = worker.await?;
|
||||
samples.append(&mut values);
|
||||
errors += failed;
|
||||
}
|
||||
let elapsed = started.elapsed().as_secs_f64();
|
||||
samples.sort_unstable();
|
||||
let percentile = |percent: usize| -> Option<f64> {
|
||||
if samples.is_empty() { None } else { Some(samples[(samples.len() * percent).div_ceil(100) - 1] as f64 / 1000.0) }
|
||||
};
|
||||
println!("{}", serde_json::json!({"concurrency":concurrency,"seconds":seconds,"elapsed_seconds":elapsed,
|
||||
"succeeded":samples.len(),"errors":errors,"qps":samples.len() as f64 / elapsed,
|
||||
"p50_ms":percentile(50),"p95_ms":percentile(95),"p99_ms":percentile(99)}));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let arguments = std::env::args().skip(1).collect::<Vec<_>>();
|
||||
let server = arguments[0] == "serve";
|
||||
let runtime = tokio::runtime::Builder::new_multi_thread().worker_threads(if server { 2 } else { 4 }).enable_all().build()?;
|
||||
if server {
|
||||
runtime.block_on(serve(arguments[1].parse()?))
|
||||
} else {
|
||||
runtime.block_on(load(arguments[1].parse()?, arguments[2].clone(), arguments[3].parse()?, arguments[4].parse()?))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
)
|
||||
|
||||
func main() {
|
||||
capacity, err := strconv.Atoi(os.Args[1])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
runtime.GOMAXPROCS(2)
|
||||
app := fiber.New(fiber.Config{
|
||||
Concurrency: capacity,
|
||||
BodyLimit: 65536,
|
||||
ReadBufferSize: 8192,
|
||||
ReadTimeout: 5 * time.Second,
|
||||
WriteTimeout: 5 * time.Second,
|
||||
IdleTimeout: 5 * time.Second,
|
||||
CaseSensitive: true,
|
||||
StrictRouting: true,
|
||||
})
|
||||
app.Get("/json", func(context fiber.Ctx) error {
|
||||
context.Set("Content-Type", "application/json")
|
||||
return context.SendString(`{"ok":true}`)
|
||||
})
|
||||
app.Post("/echo", func(context fiber.Ctx) error {
|
||||
context.Set("Content-Type", "application/octet-stream")
|
||||
return context.Send(context.BodyRaw())
|
||||
})
|
||||
bound, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Printf("READY|%d\n", bound.Addr().(*net.TCPAddr).Port)
|
||||
if err := app.Listener(bound, fiber.ListenConfig{DisableStartupMessage: true}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type settings struct {
|
||||
Workers int `json:"workers"`
|
||||
Connections int `json:"connections"`
|
||||
TimeoutMS int `json:"timeout_ms"`
|
||||
LifetimeMS int `json:"lifetime_ms"`
|
||||
Certificate string `json:"cert_path"`
|
||||
Key string `json:"key_path"`
|
||||
}
|
||||
|
||||
type limitedListener struct {
|
||||
net.Listener
|
||||
slots chan struct{}
|
||||
closed chan struct{}
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
type limitedConnection struct {
|
||||
net.Conn
|
||||
slots chan struct{}
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
func (connection *limitedConnection) Close() error {
|
||||
err := connection.Conn.Close()
|
||||
connection.once.Do(func() { <-connection.slots })
|
||||
return err
|
||||
}
|
||||
|
||||
func (listener *limitedListener) Accept() (net.Conn, error) {
|
||||
connection, err := listener.Listener.Accept()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
select {
|
||||
case listener.slots <- struct{}{}:
|
||||
return &limitedConnection{Conn: connection, slots: listener.slots}, nil
|
||||
case <-listener.closed:
|
||||
connection.Close()
|
||||
return nil, net.ErrClosed
|
||||
}
|
||||
}
|
||||
|
||||
func (listener *limitedListener) Close() error {
|
||||
listener.once.Do(func() { close(listener.closed) })
|
||||
return listener.Listener.Close()
|
||||
}
|
||||
|
||||
func run() error {
|
||||
if len(os.Args) != 2 || (os.Args[1] != "http" && os.Args[1] != "https") {
|
||||
return errors.New("usage: go_http http|https")
|
||||
}
|
||||
executable, err := os.Executable()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
content, err := os.ReadFile(filepath.Join(filepath.Dir(executable), "config", "setting.json"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var document struct {
|
||||
Benchmark settings `json:"benchmark"`
|
||||
}
|
||||
if err := json.Unmarshal(content, &document); err != nil {
|
||||
return err
|
||||
}
|
||||
config := document.Benchmark
|
||||
if config.Workers < 1 || config.Connections < 1 || config.TimeoutMS < 1 || config.LifetimeMS < 1 {
|
||||
return errors.New("benchmark requires positive workers, connections, timeout_ms and lifetime_ms")
|
||||
}
|
||||
runtime.GOMAXPROCS(config.Workers)
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bounded := &limitedListener{Listener: listener, slots: make(chan struct{}, config.Connections), closed: make(chan struct{})}
|
||||
defer bounded.Close()
|
||||
large := bytes.Repeat([]byte{'x'}, 65536)
|
||||
deadline := time.Duration(config.TimeoutMS) * time.Millisecond
|
||||
server := &http.Server{
|
||||
ReadTimeout: deadline, ReadHeaderTimeout: deadline, WriteTimeout: deadline,
|
||||
IdleTimeout: deadline, MaxHeaderBytes: 8192,
|
||||
TLSNextProto: map[string]func(*http.Server, *tls.Conn, http.Handler){},
|
||||
Handler: http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||||
body := http.MaxBytesReader(writer, request.Body, 65536)
|
||||
defer body.Close()
|
||||
if _, err := io.Copy(io.Discard, body); err != nil {
|
||||
http.Error(writer, "invalid request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
switch request.URL.RequestURI() {
|
||||
case "/plain":
|
||||
writer.Header().Set("Content-Type", "text/plain")
|
||||
io.WriteString(writer, "hello")
|
||||
case "/json":
|
||||
writer.Header().Set("Content-Type", "application/json")
|
||||
io.WriteString(writer, `{"ok":true}`)
|
||||
case "/bytes":
|
||||
writer.Header().Set("Content-Type", "application/octet-stream")
|
||||
writer.Write(large)
|
||||
default:
|
||||
http.NotFound(writer, request)
|
||||
}
|
||||
}),
|
||||
}
|
||||
timer := time.AfterFunc(time.Duration(config.LifetimeMS)*time.Millisecond, func() { server.Close() })
|
||||
defer timer.Stop()
|
||||
fmt.Printf("READY|%d\n", listener.Addr().(*net.TCPAddr).Port)
|
||||
if os.Args[1] == "https" {
|
||||
err = server.ServeTLS(bounded, config.Certificate, config.Key)
|
||||
} else {
|
||||
err = server.Serve(bounded)
|
||||
}
|
||||
if errors.Is(err, http.ErrServerClosed) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func main() {
|
||||
if err := run(); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
plain(limits: dever.http.Limits, large: Bytes, lifetime: Int) () fails app.WorkerFailure
|
||||
|
||||
secure(
|
||||
limits: dever.http.Limits,
|
||||
large: Bytes,
|
||||
lifetime: Int,
|
||||
certificate: Bytes,
|
||||
key: Bytes
|
||||
) () fails app.WorkerFailure
|
||||
@@ -0,0 +1 @@
|
||||
long cmd run = app.run
|
||||
@@ -0,0 +1,56 @@
|
||||
type State {
|
||||
source: Json
|
||||
update: Bool
|
||||
checksum: Int
|
||||
}
|
||||
|
||||
run(source: Json, update: Bool, ticks: Text) (sample: Sample) {
|
||||
input = dever.bytes.fromText(ticks)
|
||||
initial = State {
|
||||
source = source
|
||||
update = update
|
||||
checksum = 0
|
||||
}
|
||||
output = transform(source, update)
|
||||
begin = dever.time.monotonicNanos()
|
||||
final = reduce(step, input, initial)
|
||||
end = dever.time.monotonicNanos()
|
||||
sample = Sample {
|
||||
elapsed_ns = end - begin
|
||||
checksum = final.checksum
|
||||
output = output
|
||||
}
|
||||
}
|
||||
|
||||
type Sample {
|
||||
elapsed_ns: Int
|
||||
checksum: Int
|
||||
output: Text
|
||||
}
|
||||
|
||||
step(unused: Int, state: State) (next: State) {
|
||||
output = transform(state.source, state.update)
|
||||
next = State {
|
||||
source = state.source
|
||||
update = state.update
|
||||
checksum = state.checksum + length(dever.bytes.fromText(output))
|
||||
}
|
||||
}
|
||||
|
||||
transform(source: Json, update: false) (output: Text) {
|
||||
output = selected(dever.json.propertyValue(source, "data"))
|
||||
}
|
||||
|
||||
transform(source: Json, update: true) (output: Text) {
|
||||
output = dever.json.toText(
|
||||
dever.json.setProperty(source, "active", dever.json.boolean(true))
|
||||
)
|
||||
}
|
||||
|
||||
selected(value: Json) (output: Text) {
|
||||
output = dever.json.toText(value)
|
||||
}
|
||||
|
||||
selected(value: null) (output: Text) {
|
||||
output = "null"
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
type input struct {
|
||||
Source json.RawMessage `json:"source"`
|
||||
Update bool `json:"update"`
|
||||
Ticks string `json:"ticks"`
|
||||
}
|
||||
|
||||
type sample struct {
|
||||
Elapsed int64 `json:"elapsed_ns"`
|
||||
Checksum int `json:"checksum"`
|
||||
Output string `json:"output"`
|
||||
}
|
||||
|
||||
func transform(source []byte, update bool) []byte {
|
||||
decoder := json.NewDecoder(bytes.NewReader(source))
|
||||
decoder.UseNumber()
|
||||
var object map[string]any
|
||||
if err := decoder.Decode(&object); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var selected any = object["data"]
|
||||
if update {
|
||||
object["active"] = true
|
||||
selected = object
|
||||
}
|
||||
output, err := json.Marshal(selected)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
func main() {
|
||||
var arguments input
|
||||
if len(os.Args) != 2 {
|
||||
panic("expected one JSON input")
|
||||
}
|
||||
if err := json.Unmarshal([]byte(os.Args[1]), &arguments); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if !json.Valid(arguments.Source) {
|
||||
panic("invalid source")
|
||||
}
|
||||
output := transform(arguments.Source, arguments.Update)
|
||||
checksum := 0
|
||||
begin := time.Now()
|
||||
for range []byte(arguments.Ticks) {
|
||||
checksum += len(transform(arguments.Source, arguments.Update))
|
||||
}
|
||||
result := sample{time.Since(begin).Nanoseconds(), checksum, string(output)}
|
||||
encoded, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(string(encoded))
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Value, value::RawValue};
|
||||
use std::hint::black_box;
|
||||
use std::time::Instant;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Input {
|
||||
source: Box<RawValue>,
|
||||
update: bool,
|
||||
ticks: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Sample {
|
||||
elapsed_ns: u128,
|
||||
checksum: usize,
|
||||
output: String,
|
||||
}
|
||||
|
||||
fn transform(source: &str, update: bool) -> String {
|
||||
let mut value: Value = serde_json::from_str(source).expect("valid JSON input");
|
||||
let object = value.as_object_mut().expect("object input");
|
||||
if update {
|
||||
object.insert("active".into(), Value::Bool(true));
|
||||
serde_json::to_string(&value).expect("serializable object")
|
||||
} else {
|
||||
serde_json::to_string(object.get("data").expect("data field")).expect("serializable value")
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let argument = std::env::args().nth(1).expect("one JSON argument");
|
||||
let input: Input = serde_json::from_str(&argument).expect("valid input fields");
|
||||
let output = transform(input.source.get(), input.update);
|
||||
let mut checksum = 0;
|
||||
let started = Instant::now();
|
||||
for _ in input.ticks.bytes() {
|
||||
checksum += transform(black_box(input.source.get()), input.update).len();
|
||||
}
|
||||
let sample = Sample {
|
||||
elapsed_ns: started.elapsed().as_nanos(),
|
||||
checksum,
|
||||
output,
|
||||
};
|
||||
println!("{}", serde_json::to_string(&sample).unwrap());
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
long cmd record_update = app.recordUpdate
|
||||
|
||||
long cmd continuous_record_update = app.continuousRecordUpdate
|
||||
|
||||
long cmd integer = app.integer
|
||||
|
||||
long cmd record_inspect = app.recordInspect
|
||||
|
||||
long cmd continuous_record_inspect = app.continuousRecordInspect
|
||||
|
||||
long cmd map_lookup = app.mapLookup
|
||||
|
||||
long cmd continuous_map_lookup = app.continuousMapLookup
|
||||
|
||||
long cmd map_text_lookup = app.mapTextLookup
|
||||
|
||||
long cmd continuous_map_text_lookup = app.continuousMapTextLookup
|
||||
|
||||
long cmd text_equal = app.textEqual
|
||||
|
||||
long cmd continuous_text_equal = app.continuousTextEqual
|
||||
|
||||
long cmd text_contains = app.textContains
|
||||
|
||||
long cmd continuous_text_contains = app.continuousTextContains
|
||||
|
||||
long cmd float = app.float
|
||||
|
||||
long cmd bytes_concat = app.bytesConcat
|
||||
|
||||
long cmd text_concat = app.textConcat
|
||||
|
||||
long cmd list = app.list
|
||||
|
||||
long cmd map = app.map
|
||||
|
||||
long cmd list_read = app.listRead
|
||||
|
||||
long cmd map_read = app.mapRead
|
||||
|
||||
long cmd list_record = app.listRecord
|
||||
|
||||
long cmd list_record_read = app.listRecordRead
|
||||
|
||||
long cmd continuous_integer = app.continuousInteger
|
||||
|
||||
long cmd continuous_float = app.continuousFloat
|
||||
|
||||
long cmd continuous_list = app.continuousList
|
||||
|
||||
long cmd continuous_map = app.continuousMap
|
||||
|
||||
long cmd continuous_list_read = app.continuousListRead
|
||||
|
||||
long cmd continuous_map_read = app.continuousMapRead
|
||||
|
||||
long cmd continuous_list_record = app.continuousListRecord
|
||||
|
||||
long cmd continuous_list_record_read = app.continuousListRecordRead
|
||||
|
||||
long cmd continuous_text_concat = app.continuousTextConcat
|
||||
|
||||
long cmd continuous_bytes_concat = app.continuousBytesConcat
|
||||
|
||||
long cmd call_read = app.callRead
|
||||
|
||||
long cmd continuous_call_read = app.continuousCallRead
|
||||
@@ -0,0 +1,393 @@
|
||||
type Lookup {
|
||||
values: Map<Int, Int>
|
||||
index: Int
|
||||
total: Int
|
||||
}
|
||||
|
||||
selected(value: Int) (answer: Int) { answer = value }
|
||||
selected(value: null) (answer: Int) { answer = 0 }
|
||||
|
||||
lookup(value: Int, state: Lookup) (next: Lookup) {
|
||||
found = selected(get(state.values, state.index))
|
||||
next = Lookup { values = state.values
|
||||
index = state.index + 1
|
||||
total = state.total + found }
|
||||
}
|
||||
|
||||
mapLookup(ticks: Text) (sample: Sample) {
|
||||
input = dever.bytes.fromText(ticks)
|
||||
values = reduce(putValue, input, {})
|
||||
initial = Lookup { values = values
|
||||
index = 0
|
||||
total = 0 }
|
||||
begin = dever.time.monotonicNanos()
|
||||
final = reduce(lookup, input, initial)
|
||||
end = dever.time.monotonicNanos()
|
||||
sample = Sample { elapsed_ns = end - begin
|
||||
checksum = final.total }
|
||||
}
|
||||
|
||||
continuousMapLookup(ticks: Text) (samples: List<Sample>) {
|
||||
samples = each(mapLookup, [ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks])
|
||||
}
|
||||
|
||||
type TextLookup {
|
||||
values: Map<Text, Int>
|
||||
total: Int
|
||||
}
|
||||
|
||||
type TextPair {
|
||||
left: Text
|
||||
right: Text
|
||||
}
|
||||
|
||||
textEntry(entry: MapEntry<Int, Int>, state: Map<Text, Int>) (next: Map<Text, Int>) {
|
||||
next = put(state, "lookup-key-" + int.toText(entry.key), entry.value)
|
||||
}
|
||||
|
||||
textKey(entry: MapEntry<Text, Int>) (key: Text) { key = entry.key }
|
||||
|
||||
textLookup(key: Text, state: TextLookup) (next: TextLookup) {
|
||||
found = selected(get(state.values, key))
|
||||
next = TextLookup { values = state.values
|
||||
total = state.total + found }
|
||||
}
|
||||
|
||||
mapTextLookup(ticks: Text) (sample: Sample) {
|
||||
input = dever.bytes.fromText(ticks)
|
||||
values = reduce(textEntry, entries(reduce(putValue, input, {})), {})
|
||||
keys = each(textKey, entries(values))
|
||||
initial = TextLookup { values = values
|
||||
total = 0 }
|
||||
begin = dever.time.monotonicNanos()
|
||||
final = reduce(textLookup, keys, initial)
|
||||
end = dever.time.monotonicNanos()
|
||||
sample = Sample { elapsed_ns = end - begin
|
||||
checksum = final.total + length(keys) - length(input) }
|
||||
}
|
||||
|
||||
continuousMapTextLookup(ticks: Text) (samples: List<Sample>) {
|
||||
samples = each(mapTextLookup, [ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks])
|
||||
}
|
||||
|
||||
textPair(value: Int) (pair: TextPair) {
|
||||
left = "compare-value-" + int.toText(value)
|
||||
pair = TextPair { left = left
|
||||
right = text.lower(text.upper(left)) }
|
||||
}
|
||||
|
||||
matchedValue(matched: true) (value: Int) { value = 120 }
|
||||
matchedValue(matched: false) (value: Int) { value = 0 }
|
||||
|
||||
equalTextPair(row: TextPair, total: Int) (next: Int) {
|
||||
next = total + matchedValue(row.left == row.right)
|
||||
}
|
||||
|
||||
containsTextPair(row: TextPair, total: Int) (next: Int) {
|
||||
next = total + matchedValue(text.contains(row.left, row.right))
|
||||
}
|
||||
|
||||
textEqual(ticks: Text) (sample: Sample) {
|
||||
input = dever.bytes.fromText(ticks)
|
||||
rows = each(textPair, input)
|
||||
begin = dever.time.monotonicNanos()
|
||||
total = reduce(equalTextPair, rows, 0)
|
||||
end = dever.time.monotonicNanos()
|
||||
sample = Sample { elapsed_ns = end - begin
|
||||
checksum = total + length(rows) - length(input) }
|
||||
}
|
||||
|
||||
continuousTextEqual(ticks: Text) (samples: List<Sample>) {
|
||||
samples = each(textEqual, [ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks])
|
||||
}
|
||||
|
||||
textContains(ticks: Text) (sample: Sample) {
|
||||
input = dever.bytes.fromText(ticks)
|
||||
rows = each(textPair, input)
|
||||
begin = dever.time.monotonicNanos()
|
||||
total = reduce(containsTextPair, rows, 0)
|
||||
end = dever.time.monotonicNanos()
|
||||
sample = Sample { elapsed_ns = end - begin
|
||||
checksum = total + length(rows) - length(input) }
|
||||
}
|
||||
|
||||
continuousTextContains(ticks: Text) (samples: List<Sample>) {
|
||||
samples = each(textContains, [ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks])
|
||||
}
|
||||
|
||||
type Sample {
|
||||
elapsed_ns: Int
|
||||
checksum: Int
|
||||
}
|
||||
|
||||
type FloatSample {
|
||||
elapsed_ns: Int
|
||||
checksum: Float
|
||||
}
|
||||
|
||||
addFloat(value: Int, state: Float) (next: Float) {
|
||||
next = state + float.fromInt(value)
|
||||
}
|
||||
|
||||
float(ticks: Text) (sample: FloatSample) {
|
||||
bytes = dever.bytes.fromText(ticks)
|
||||
zero = float.fromInt(0)
|
||||
begin = dever.time.monotonicNanos()
|
||||
total = reduce(addFloat, bytes, zero)
|
||||
end = dever.time.monotonicNanos()
|
||||
sample = FloatSample { elapsed_ns = end - begin
|
||||
checksum = total }
|
||||
}
|
||||
|
||||
joinBytes(value: Bytes, state: Bytes) (next: Bytes) {
|
||||
next = dever.bytes.concat(state, value)
|
||||
}
|
||||
|
||||
bytesConcat(ticks: Text) (sample: Sample) {
|
||||
chunks = each(dever.bytes.fromText, text.split(ticks, ""))
|
||||
empty = dever.bytes.fromText("")
|
||||
begin = dever.time.monotonicNanos()
|
||||
output = reduce(joinBytes, chunks, empty)
|
||||
end = dever.time.monotonicNanos()
|
||||
total = reduce(add, output, 0)
|
||||
sample = Sample { elapsed_ns = end - begin
|
||||
checksum = total }
|
||||
}
|
||||
|
||||
joinText(value: Text, state: Text) (next: Text) {
|
||||
next = state + value
|
||||
}
|
||||
|
||||
textConcat(ticks: Text) (sample: Sample) {
|
||||
chunks = text.split(ticks, ",")
|
||||
begin = dever.time.monotonicNanos()
|
||||
output = reduce(joinText, chunks, "")
|
||||
end = dever.time.monotonicNanos()
|
||||
sample = Sample {
|
||||
elapsed_ns = end - begin
|
||||
checksum = reduce(add, dever.bytes.fromText(output), 0)
|
||||
}
|
||||
}
|
||||
|
||||
add(value: Int, state: Int) (next: Int) {
|
||||
next = state + value
|
||||
}
|
||||
|
||||
appendValue(value: Int, state: List<Int>) (next: List<Int>) {
|
||||
next = append(state, value)
|
||||
}
|
||||
|
||||
putValue(value: Int, state: Map<Int, Int>) (next: Map<Int, Int>) {
|
||||
key = length(state)
|
||||
next = put(state, key, value)
|
||||
}
|
||||
|
||||
mapped(entry: MapEntry<Int, Int>) (value: Int) {
|
||||
value = entry.value
|
||||
}
|
||||
|
||||
integer(ticks: Text) (sample: Sample) {
|
||||
input = dever.bytes.fromText(ticks)
|
||||
begin = dever.time.monotonicNanos()
|
||||
value = reduce(add, input, 0)
|
||||
end = dever.time.monotonicNanos()
|
||||
sample = Sample {
|
||||
elapsed_ns = end - begin
|
||||
checksum = value
|
||||
}
|
||||
}
|
||||
|
||||
list(ticks: Text) (sample: Sample) {
|
||||
input = dever.bytes.fromText(ticks)
|
||||
begin = dever.time.monotonicNanos()
|
||||
value = reduce(appendValue, input, [])
|
||||
end = dever.time.monotonicNanos()
|
||||
sample = Sample {
|
||||
elapsed_ns = end - begin
|
||||
checksum = sum(value)
|
||||
}
|
||||
}
|
||||
|
||||
map(ticks: Text) (sample: Sample) {
|
||||
input = dever.bytes.fromText(ticks)
|
||||
begin = dever.time.monotonicNanos()
|
||||
value = reduce(putValue, input, {})
|
||||
end = dever.time.monotonicNanos()
|
||||
sample = Sample {
|
||||
elapsed_ns = end - begin
|
||||
checksum = sum(each(mapped, entries(value)))
|
||||
}
|
||||
}
|
||||
|
||||
listRead(ticks: Text) (sample: Sample) {
|
||||
values = reduce(appendValue, dever.bytes.fromText(ticks), [])
|
||||
begin = dever.time.monotonicNanos()
|
||||
total = sum(values)
|
||||
end = dever.time.monotonicNanos()
|
||||
sample = Sample { elapsed_ns = end - begin
|
||||
checksum = total }
|
||||
}
|
||||
|
||||
mapRead(ticks: Text) (sample: Sample) {
|
||||
values = reduce(putValue, dever.bytes.fromText(ticks), {})
|
||||
begin = dever.time.monotonicNanos()
|
||||
total = sum(mapped, entries(values))
|
||||
end = dever.time.monotonicNanos()
|
||||
sample = Sample { elapsed_ns = end - begin
|
||||
checksum = total }
|
||||
}
|
||||
|
||||
type Record {
|
||||
first: Int
|
||||
second: Int
|
||||
third: Int
|
||||
}
|
||||
|
||||
appendRecord(value: Int, state: List<Record>) (next: List<Record>) {
|
||||
next = append(state, Record { first = value
|
||||
second = 0
|
||||
third = 0 })
|
||||
}
|
||||
|
||||
recordValue(row: Record) (value: Int) {
|
||||
value = row.first + row.second + row.third
|
||||
}
|
||||
|
||||
listRecord(ticks: Text) (sample: Sample) {
|
||||
input = dever.bytes.fromText(ticks)
|
||||
begin = dever.time.monotonicNanos()
|
||||
values = reduce(appendRecord, input, [])
|
||||
end = dever.time.monotonicNanos()
|
||||
sample = Sample { elapsed_ns = end - begin
|
||||
checksum = sum(recordValue, values) }
|
||||
}
|
||||
|
||||
listRecordRead(ticks: Text) (sample: Sample) {
|
||||
values = reduce(appendRecord, dever.bytes.fromText(ticks), [])
|
||||
begin = dever.time.monotonicNanos()
|
||||
total = sum(recordValue, values)
|
||||
end = dever.time.monotonicNanos()
|
||||
sample = Sample { elapsed_ns = end - begin
|
||||
checksum = total }
|
||||
}
|
||||
|
||||
continuousInteger(ticks: Text) (samples: List<Sample>) {
|
||||
samples = each(integer, [ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks])
|
||||
}
|
||||
|
||||
continuousFloat(ticks: Text) (samples: List<FloatSample>) {
|
||||
samples = each(float, [ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks])
|
||||
}
|
||||
|
||||
continuousList(ticks: Text) (samples: List<Sample>) {
|
||||
samples = each(list, [ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks])
|
||||
}
|
||||
|
||||
continuousMap(ticks: Text) (samples: List<Sample>) {
|
||||
samples = each(map, [ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks])
|
||||
}
|
||||
|
||||
continuousListRead(ticks: Text) (samples: List<Sample>) {
|
||||
samples = each(listRead, [ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks])
|
||||
}
|
||||
|
||||
continuousMapRead(ticks: Text) (samples: List<Sample>) {
|
||||
samples = each(mapRead, [ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks])
|
||||
}
|
||||
|
||||
continuousListRecord(ticks: Text) (samples: List<Sample>) {
|
||||
samples = each(listRecord, [ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks])
|
||||
}
|
||||
|
||||
continuousListRecordRead(ticks: Text) (samples: List<Sample>) {
|
||||
samples = each(listRecordRead, [ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks])
|
||||
}
|
||||
|
||||
continuousTextConcat(ticks: Text) (samples: List<Sample>) {
|
||||
samples = each(textConcat, [ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks])
|
||||
}
|
||||
|
||||
continuousBytesConcat(ticks: Text) (samples: List<Sample>) {
|
||||
samples = each(bytesConcat, [ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks])
|
||||
}
|
||||
|
||||
type OwnedRecord {
|
||||
name: Text
|
||||
tags: List<Text>
|
||||
count: Int
|
||||
}
|
||||
|
||||
ownedRecord(value: Int) (row: OwnedRecord) {
|
||||
name = "row-value-" + int.toText(value)
|
||||
row = OwnedRecord { name = name
|
||||
tags = [name]
|
||||
count = value - length(name) - 1 }
|
||||
}
|
||||
|
||||
inspectOwned(row: OwnedRecord) (value: Int) {
|
||||
value = row.count + length(row.name) + length(row.tags)
|
||||
}
|
||||
|
||||
forwardOwned(row: OwnedRecord) (value: Int) {
|
||||
value = inspectOwned(row)
|
||||
}
|
||||
|
||||
callRead(ticks: Text) (sample: Sample) {
|
||||
rows = each(ownedRecord, dever.bytes.fromText(ticks))
|
||||
begin = dever.time.monotonicNanos()
|
||||
total = sum(forwardOwned, rows)
|
||||
end = dever.time.monotonicNanos()
|
||||
preserved = length(rows)
|
||||
sample = Sample { elapsed_ns = end - begin
|
||||
checksum = total + preserved - length(rows) }
|
||||
}
|
||||
|
||||
continuousCallRead(ticks: Text) (samples: List<Sample>) {
|
||||
samples = each(callRead, [ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks])
|
||||
}
|
||||
|
||||
type Accumulator {
|
||||
values: List<Int>
|
||||
count: Int
|
||||
}
|
||||
|
||||
appendField(value: Int, state: Accumulator) (next: Accumulator) {
|
||||
values = append(state.values, value)
|
||||
next = Accumulator { values = values
|
||||
count = state.count + 1 }
|
||||
}
|
||||
|
||||
recordUpdate(ticks: Text) (sample: Sample) {
|
||||
input = dever.bytes.fromText(ticks)
|
||||
begin = dever.time.monotonicNanos()
|
||||
state = reduce(appendField, input, Accumulator { values = []
|
||||
count = 0 })
|
||||
end = dever.time.monotonicNanos()
|
||||
sample = Sample { elapsed_ns = end - begin
|
||||
checksum = sum(state.values) + state.count - length(state.values) }
|
||||
}
|
||||
|
||||
continuousRecordUpdate(ticks: Text) (samples: List<Sample>) {
|
||||
samples = each(recordUpdate, [ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks])
|
||||
}
|
||||
|
||||
appendInspected(value: Int, state: Accumulator) (next: Accumulator) {
|
||||
count = length(state.values) + 1
|
||||
values = append(state.values, value)
|
||||
next = Accumulator { values = values
|
||||
count = count }
|
||||
}
|
||||
|
||||
recordInspect(ticks: Text) (sample: Sample) {
|
||||
input = dever.bytes.fromText(ticks)
|
||||
begin = dever.time.monotonicNanos()
|
||||
state = reduce(appendInspected, input, Accumulator { values = []
|
||||
count = 0 })
|
||||
end = dever.time.monotonicNanos()
|
||||
sample = Sample { elapsed_ns = end - begin
|
||||
checksum = sum(state.values) + state.count - length(state.values) }
|
||||
}
|
||||
|
||||
continuousRecordInspect(ticks: Text) (samples: List<Sample>) {
|
||||
samples = each(recordInspect, [ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks])
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
use std::hint::black_box;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct Fault {
|
||||
message: String,
|
||||
locations: Vec<&'static str>,
|
||||
causes: Vec<String>,
|
||||
}
|
||||
|
||||
impl Fault {
|
||||
fn overflow(location: &'static str) -> Self {
|
||||
Self {
|
||||
message: "Int overflow".into(),
|
||||
locations: vec![location],
|
||||
causes: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn at(mut self, location: &'static str) -> Self {
|
||||
self.locations.push(location);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
struct Record {
|
||||
first: i64,
|
||||
second: i64,
|
||||
third: i64,
|
||||
}
|
||||
|
||||
struct OwnedRecord {
|
||||
name: String,
|
||||
tags: Vec<String>,
|
||||
count: i64,
|
||||
}
|
||||
|
||||
fn inspect_owned(row: &OwnedRecord) -> Result<i64, Fault> {
|
||||
let partial = row
|
||||
.count
|
||||
.checked_add(row.name.chars().count() as i64)
|
||||
.ok_or_else(|| Fault::overflow("inspect_owned"))?;
|
||||
partial
|
||||
.checked_add(row.tags.len() as i64)
|
||||
.ok_or_else(|| Fault::overflow("inspect_owned"))
|
||||
}
|
||||
|
||||
fn forward_owned(row: &OwnedRecord) -> Result<i64, Fault> {
|
||||
inspect_owned(row).map_err(|error| error.at("forward_owned"))
|
||||
}
|
||||
|
||||
fn owned_sum(rows: &[OwnedRecord]) -> Result<i64, Fault> {
|
||||
let mut total = 0_i64;
|
||||
for row in rows {
|
||||
let projected = forward_owned(row).map_err(|error| error.at("call_read"))?;
|
||||
total = total
|
||||
.checked_add(projected)
|
||||
.ok_or_else(|| Fault::overflow("call_read"))?;
|
||||
}
|
||||
Ok(total)
|
||||
}
|
||||
|
||||
fn add(total: i64, value: i64) -> Result<i64, Fault> {
|
||||
total
|
||||
.checked_add(value)
|
||||
.ok_or_else(|| Fault::overflow("add"))
|
||||
}
|
||||
|
||||
fn record_value(row: &Record) -> Result<i64, Fault> {
|
||||
let partial = row
|
||||
.first
|
||||
.checked_add(row.second)
|
||||
.ok_or_else(|| Fault::overflow("record_value"))?;
|
||||
partial
|
||||
.checked_add(row.third)
|
||||
.ok_or_else(|| Fault::overflow("record_value"))
|
||||
}
|
||||
|
||||
fn record_sum(values: &[Record]) -> Result<i64, Fault> {
|
||||
let mut total = 0i64;
|
||||
for row in values {
|
||||
let projected = record_value(row).map_err(|error| error.at("record_sum"))?;
|
||||
total = total
|
||||
.checked_add(projected)
|
||||
.ok_or_else(|| Fault::overflow("record_sum"))?;
|
||||
}
|
||||
Ok(total)
|
||||
}
|
||||
|
||||
fn measure(operation: &str, ticks: &str) -> Result<(i64, Duration), Fault> {
|
||||
let source = black_box(ticks.as_bytes());
|
||||
match operation {
|
||||
"call_read" => {
|
||||
let rows = source
|
||||
.iter()
|
||||
.map(|value| {
|
||||
let name = format!("row-value-{value}");
|
||||
OwnedRecord {
|
||||
count: i64::from(*value) - name.chars().count() as i64 - 1,
|
||||
tags: vec![name.clone()],
|
||||
name,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let borrowed = black_box(&rows);
|
||||
let begin = Instant::now();
|
||||
let total = owned_sum(borrowed)?;
|
||||
black_box(total);
|
||||
let elapsed = begin.elapsed();
|
||||
black_box(&rows);
|
||||
Ok((total, elapsed))
|
||||
}
|
||||
"integer" => {
|
||||
let begin = Instant::now();
|
||||
let mut total = 0;
|
||||
for value in source {
|
||||
total = add(total, i64::from(*value)).map_err(|error| error.at("integer"))?;
|
||||
}
|
||||
Ok((black_box(total), begin.elapsed()))
|
||||
}
|
||||
"list_record_read" => {
|
||||
let mut values = Vec::new();
|
||||
for value in source {
|
||||
values.push(Record {
|
||||
first: i64::from(*value),
|
||||
second: 0,
|
||||
third: 0,
|
||||
});
|
||||
}
|
||||
let borrowed = black_box(&values);
|
||||
let begin = Instant::now();
|
||||
let total = record_sum(borrowed)?;
|
||||
black_box(total);
|
||||
drop(values);
|
||||
Ok((total, begin.elapsed()))
|
||||
}
|
||||
_ => panic!("unknown checked kernel"),
|
||||
}
|
||||
}
|
||||
|
||||
fn verify_errors() {
|
||||
let fault = owned_sum(&[OwnedRecord {
|
||||
name: "overflow".into(),
|
||||
tags: vec!["preserved".into()],
|
||||
count: i64::MAX,
|
||||
}])
|
||||
.unwrap_err();
|
||||
assert_eq!(fault.message, "Int overflow");
|
||||
assert_eq!(
|
||||
fault.locations,
|
||||
["inspect_owned", "forward_owned", "call_read"]
|
||||
);
|
||||
assert!(fault.causes.is_empty());
|
||||
assert_eq!(add(i64::MAX, 0).unwrap(), i64::MAX);
|
||||
assert_eq!(add(i64::MIN, 0).unwrap(), i64::MIN);
|
||||
for (left, right) in [(i64::MAX, 1), (i64::MIN, -1)] {
|
||||
let fault = add(left, right).unwrap_err().at("integer");
|
||||
assert_eq!(fault.message, "Int overflow");
|
||||
assert_eq!(fault.locations, ["add", "integer"]);
|
||||
assert!(fault.causes.is_empty());
|
||||
}
|
||||
let fault = record_sum(&[Record {
|
||||
first: i64::MAX,
|
||||
second: 1,
|
||||
third: 0,
|
||||
}])
|
||||
.unwrap_err();
|
||||
assert_eq!(fault.locations, ["record_value", "record_sum"]);
|
||||
let fault = record_sum(&[
|
||||
Record {
|
||||
first: i64::MAX,
|
||||
second: 0,
|
||||
third: 0,
|
||||
},
|
||||
Record {
|
||||
first: 1,
|
||||
second: 0,
|
||||
third: 0,
|
||||
},
|
||||
])
|
||||
.unwrap_err();
|
||||
assert_eq!(fault.locations, ["record_sum"]);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let arguments = std::env::args().collect::<Vec<_>>();
|
||||
if arguments[1] == "--verify-errors" {
|
||||
verify_errors();
|
||||
println!("checked overflow and error frames verified");
|
||||
return;
|
||||
}
|
||||
let input: serde_json::Value = serde_json::from_str(&arguments[2]).expect("input object");
|
||||
let ticks = input["ticks"].as_str().expect("ticks string");
|
||||
let (operation, count) = arguments[1]
|
||||
.strip_prefix("continuous_")
|
||||
.map_or((arguments[1].as_str(), 1), |operation| (operation, 10));
|
||||
let mut samples = Vec::new();
|
||||
for _ in 0..count {
|
||||
match measure(operation, ticks) {
|
||||
Ok((checksum, elapsed)) => samples
|
||||
.push(serde_json::json!({"checksum": checksum, "elapsed_ns": elapsed.as_nanos()})),
|
||||
Err(error) => {
|
||||
eprintln!("{error:?}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
let output = if count == 1 {
|
||||
samples.pop().unwrap()
|
||||
} else {
|
||||
serde_json::Value::Array(samples)
|
||||
};
|
||||
println!("{output}");
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type input struct {
|
||||
Ticks string `json:"ticks"`
|
||||
}
|
||||
type sample struct {
|
||||
Elapsed int64 `json:"elapsed_ns"`
|
||||
Checksum int64 `json:"checksum"`
|
||||
}
|
||||
type record struct {
|
||||
First int64
|
||||
Second int64
|
||||
Third int64
|
||||
}
|
||||
|
||||
type ownedRecord struct {
|
||||
Name string
|
||||
Tags []string
|
||||
Count int64
|
||||
}
|
||||
|
||||
func inspectOwned(row ownedRecord) int64 {
|
||||
return checkedAdd(checkedAdd(row.Count, int64(utf8.RuneCountInString(row.Name))), int64(len(row.Tags)))
|
||||
}
|
||||
|
||||
func forwardOwned(row ownedRecord) int64 {
|
||||
return inspectOwned(row)
|
||||
}
|
||||
|
||||
func checkedAdd(total, value int64) int64 {
|
||||
if value > 0 && total > math.MaxInt64-value {
|
||||
panic("overflow")
|
||||
}
|
||||
return total + value
|
||||
}
|
||||
|
||||
func measure(operation string, ticks string) sample {
|
||||
source := []byte(ticks)
|
||||
total := int64(0)
|
||||
elapsed := int64(0)
|
||||
switch operation {
|
||||
case "record_update":
|
||||
var values []int64
|
||||
count := int64(0)
|
||||
begin := time.Now()
|
||||
for _, value := range source {
|
||||
values = append(values, int64(value))
|
||||
count = checkedAdd(count, 1)
|
||||
}
|
||||
elapsed = time.Since(begin).Nanoseconds()
|
||||
for _, value := range values {
|
||||
total = checkedAdd(total, value)
|
||||
}
|
||||
total = checkedAdd(total, count) - int64(len(values))
|
||||
case "record_inspect":
|
||||
var values []int64
|
||||
count := int64(0)
|
||||
begin := time.Now()
|
||||
for _, value := range source {
|
||||
count = checkedAdd(int64(len(values)), 1)
|
||||
values = append(values, int64(value))
|
||||
}
|
||||
elapsed = time.Since(begin).Nanoseconds()
|
||||
for _, value := range values {
|
||||
total = checkedAdd(total, value)
|
||||
}
|
||||
total = checkedAdd(total, count) - int64(len(values))
|
||||
case "map_lookup":
|
||||
values := make(map[int64]int64, len(source))
|
||||
for index, value := range source {
|
||||
values[int64(index)] = int64(value)
|
||||
}
|
||||
index := int64(0)
|
||||
begin := time.Now()
|
||||
for range source {
|
||||
total = checkedAdd(total, values[index])
|
||||
index = checkedAdd(index, 1)
|
||||
}
|
||||
elapsed = time.Since(begin).Nanoseconds()
|
||||
case "text_equal", "text_contains":
|
||||
rows := make([][2]string, len(source))
|
||||
for index, value := range source {
|
||||
left := "compare-value-" + strconv.Itoa(int(value))
|
||||
rows[index] = [2]string{left, strings.ToLower(strings.ToUpper(left))}
|
||||
}
|
||||
begin := time.Now()
|
||||
if operation == "text_equal" {
|
||||
for _, row := range rows {
|
||||
matched := int64(0)
|
||||
if row[0] == row[1] {
|
||||
matched = 120
|
||||
}
|
||||
total = checkedAdd(total, matched)
|
||||
}
|
||||
} else {
|
||||
for _, row := range rows {
|
||||
matched := int64(0)
|
||||
if strings.Contains(row[0], row[1]) {
|
||||
matched = 120
|
||||
}
|
||||
total = checkedAdd(total, matched)
|
||||
}
|
||||
}
|
||||
elapsed = time.Since(begin).Nanoseconds()
|
||||
runtime.KeepAlive(rows)
|
||||
total = checkedAdd(total, int64(len(rows))) - int64(len(source))
|
||||
case "map_text_lookup":
|
||||
keys := make([]string, len(source))
|
||||
values := make(map[string]int64, len(source))
|
||||
for index, value := range source {
|
||||
key := "lookup-key-" + strconv.Itoa(index)
|
||||
keys[index] = key
|
||||
values[key] = int64(value)
|
||||
}
|
||||
begin := time.Now()
|
||||
for _, key := range keys {
|
||||
total = checkedAdd(total, values[key])
|
||||
}
|
||||
elapsed = time.Since(begin).Nanoseconds()
|
||||
runtime.KeepAlive(values)
|
||||
runtime.KeepAlive(keys)
|
||||
total = checkedAdd(total, int64(len(keys))) - int64(len(source))
|
||||
case "call_read":
|
||||
rows := make([]ownedRecord, 0, len(source))
|
||||
for _, value := range source {
|
||||
name := "row-value-" + strconv.Itoa(int(value))
|
||||
rows = append(rows, ownedRecord{name, []string{name}, int64(value) - int64(utf8.RuneCountInString(name)) - 1})
|
||||
}
|
||||
begin := time.Now()
|
||||
for _, row := range rows {
|
||||
total = checkedAdd(total, forwardOwned(row))
|
||||
}
|
||||
elapsed = time.Since(begin).Nanoseconds()
|
||||
case "float":
|
||||
floating := 0.0
|
||||
begin := time.Now()
|
||||
for _, value := range source {
|
||||
floating += float64(value)
|
||||
}
|
||||
elapsed = time.Since(begin).Nanoseconds()
|
||||
total = int64(floating)
|
||||
case "bytes_concat":
|
||||
chunks := make([][]byte, len(source))
|
||||
for index := range source {
|
||||
chunks[index] = source[index : index+1]
|
||||
}
|
||||
var output []byte
|
||||
begin := time.Now()
|
||||
for _, chunk := range chunks {
|
||||
output = append(output, chunk...)
|
||||
}
|
||||
elapsed = time.Since(begin).Nanoseconds()
|
||||
for _, value := range output {
|
||||
total = checkedAdd(total, int64(value))
|
||||
}
|
||||
case "text_concat":
|
||||
chunks := strings.Split(ticks, ",")
|
||||
begin := time.Now()
|
||||
var output strings.Builder
|
||||
for _, chunk := range chunks {
|
||||
output.WriteString(chunk)
|
||||
}
|
||||
joined := output.String()
|
||||
elapsed = time.Since(begin).Nanoseconds()
|
||||
for index := 0; index < len(joined); index++ {
|
||||
total = checkedAdd(total, int64(joined[index]))
|
||||
}
|
||||
case "integer":
|
||||
begin := time.Now()
|
||||
for _, value := range source {
|
||||
total = checkedAdd(total, int64(value))
|
||||
}
|
||||
elapsed = time.Since(begin).Nanoseconds()
|
||||
case "list":
|
||||
var values []int64
|
||||
begin := time.Now()
|
||||
for _, value := range source {
|
||||
values = append(values, int64(value))
|
||||
}
|
||||
elapsed = time.Since(begin).Nanoseconds()
|
||||
for _, value := range values {
|
||||
total = checkedAdd(total, value)
|
||||
}
|
||||
case "map":
|
||||
values := make(map[int64]int64)
|
||||
var order []int64
|
||||
begin := time.Now()
|
||||
for _, value := range source {
|
||||
key := int64(len(values))
|
||||
values[key] = int64(value)
|
||||
order = append(order, key)
|
||||
}
|
||||
elapsed = time.Since(begin).Nanoseconds()
|
||||
for _, key := range order {
|
||||
total = checkedAdd(total, values[key])
|
||||
}
|
||||
case "list_read":
|
||||
var values []int64
|
||||
for _, value := range source {
|
||||
values = append(values, int64(value))
|
||||
}
|
||||
begin := time.Now()
|
||||
for _, value := range values {
|
||||
total = checkedAdd(total, value)
|
||||
}
|
||||
elapsed = time.Since(begin).Nanoseconds()
|
||||
case "map_read":
|
||||
values := make(map[int64]int64)
|
||||
var order []int64
|
||||
for _, value := range source {
|
||||
key := int64(len(values))
|
||||
values[key] = int64(value)
|
||||
order = append(order, key)
|
||||
}
|
||||
begin := time.Now()
|
||||
for _, key := range order {
|
||||
total = checkedAdd(total, values[key])
|
||||
}
|
||||
elapsed = time.Since(begin).Nanoseconds()
|
||||
case "list_record":
|
||||
var values []record
|
||||
begin := time.Now()
|
||||
for _, value := range source {
|
||||
values = append(values, record{int64(value), 0, 0})
|
||||
}
|
||||
elapsed = time.Since(begin).Nanoseconds()
|
||||
for _, value := range values {
|
||||
total = checkedAdd(total, checkedAdd(checkedAdd(value.First, value.Second), value.Third))
|
||||
}
|
||||
case "list_record_read":
|
||||
var values []record
|
||||
for _, value := range source {
|
||||
values = append(values, record{int64(value), 0, 0})
|
||||
}
|
||||
begin := time.Now()
|
||||
for _, value := range values {
|
||||
total = checkedAdd(total, checkedAdd(checkedAdd(value.First, value.Second), value.Third))
|
||||
}
|
||||
elapsed = time.Since(begin).Nanoseconds()
|
||||
default:
|
||||
panic("unknown kernel")
|
||||
}
|
||||
return sample{elapsed, total}
|
||||
}
|
||||
|
||||
func main() {
|
||||
var arguments input
|
||||
if err := json.Unmarshal([]byte(os.Args[2]), &arguments); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var result any
|
||||
if operation, continuous := strings.CutPrefix(os.Args[1], "continuous_"); continuous {
|
||||
samples := make([]sample, 0, 10)
|
||||
for range 10 {
|
||||
samples = append(samples, measure(operation, arguments.Ticks))
|
||||
}
|
||||
result = samples
|
||||
} else {
|
||||
result = measure(os.Args[1], arguments.Ticks)
|
||||
}
|
||||
output, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(string(output))
|
||||
}
|
||||
@@ -0,0 +1,306 @@
|
||||
use indexmap::IndexMap;
|
||||
use std::hint::black_box;
|
||||
use std::time::Instant;
|
||||
|
||||
struct Record {
|
||||
first: i64,
|
||||
second: i64,
|
||||
third: i64,
|
||||
}
|
||||
|
||||
struct OwnedRecord {
|
||||
name: String,
|
||||
tags: Vec<String>,
|
||||
count: i64,
|
||||
}
|
||||
|
||||
fn inspect_owned(row: &OwnedRecord) -> i64 {
|
||||
add(
|
||||
add(row.count, row.name.chars().count() as i64),
|
||||
row.tags.len() as i64,
|
||||
)
|
||||
}
|
||||
|
||||
fn forward_owned(row: &OwnedRecord) -> i64 {
|
||||
inspect_owned(row)
|
||||
}
|
||||
|
||||
fn add(total: i64, value: i64) -> i64 {
|
||||
total.checked_add(value).expect("integer overflow")
|
||||
}
|
||||
|
||||
fn record_value(row: &Record) -> i64 {
|
||||
add(add(row.first, row.second), row.third)
|
||||
}
|
||||
|
||||
fn measure(operation: &str, ticks: &str) -> serde_json::Value {
|
||||
let source = black_box(ticks.as_bytes());
|
||||
let (checksum, elapsed) = match operation {
|
||||
"record_update" => {
|
||||
let begin = Instant::now();
|
||||
let mut values = Vec::new();
|
||||
let mut count = 0;
|
||||
for value in source {
|
||||
values.push(i64::from(*value));
|
||||
count = add(count, 1);
|
||||
}
|
||||
black_box((&values, count));
|
||||
let elapsed = begin.elapsed();
|
||||
let total = values.iter().fold(0, |total, value| add(total, *value));
|
||||
let checksum = add(total, count)
|
||||
.checked_sub(values.len() as i64)
|
||||
.expect("integer overflow");
|
||||
(checksum, elapsed)
|
||||
}
|
||||
"record_inspect" => {
|
||||
let begin = Instant::now();
|
||||
let mut values = Vec::new();
|
||||
let mut count = 0;
|
||||
for value in source {
|
||||
count = add(values.len() as i64, 1);
|
||||
values.push(i64::from(*value));
|
||||
}
|
||||
black_box((&values, count));
|
||||
let elapsed = begin.elapsed();
|
||||
let total = values.iter().fold(0, |total, value| add(total, *value));
|
||||
let checksum = add(total, count)
|
||||
.checked_sub(values.len() as i64)
|
||||
.expect("integer overflow");
|
||||
(checksum, elapsed)
|
||||
}
|
||||
"map_lookup" => {
|
||||
let values = source
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, value)| (index as i64, i64::from(*value)))
|
||||
.collect::<IndexMap<_, _>>();
|
||||
let borrowed = black_box(&values);
|
||||
let mut index = 0;
|
||||
let mut total = 0;
|
||||
let begin = Instant::now();
|
||||
for _ in source {
|
||||
total = add(total, borrowed.get(&index).copied().unwrap_or(0));
|
||||
index = add(index, 1);
|
||||
}
|
||||
black_box(total);
|
||||
let elapsed = begin.elapsed();
|
||||
black_box(&values);
|
||||
(total, elapsed)
|
||||
}
|
||||
"text_equal" | "text_contains" => {
|
||||
let rows = source
|
||||
.iter()
|
||||
.map(|value| {
|
||||
let left = format!("compare-value-{value}");
|
||||
let right = left.to_uppercase().to_lowercase();
|
||||
(left, right)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let borrowed = black_box(&rows);
|
||||
let mut total = 0;
|
||||
let begin = Instant::now();
|
||||
if operation == "text_equal" {
|
||||
for (left, right) in borrowed {
|
||||
total = add(total, if left == right { 120 } else { 0 });
|
||||
}
|
||||
} else {
|
||||
for (left, right) in borrowed {
|
||||
total = add(
|
||||
total,
|
||||
if left.contains(right.as_str()) {
|
||||
120
|
||||
} else {
|
||||
0
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
black_box(total);
|
||||
let elapsed = begin.elapsed();
|
||||
black_box(&rows);
|
||||
let checksum = add(total, rows.len() as i64)
|
||||
.checked_sub(source.len() as i64)
|
||||
.expect("integer overflow");
|
||||
(checksum, elapsed)
|
||||
}
|
||||
"map_text_lookup" => {
|
||||
// 与 Dever 使用相同的拥有键和插入顺序;HashMap<&str, _> 是不同的数据结构。
|
||||
let values = source
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, value)| (format!("lookup-key-{index}"), i64::from(*value)))
|
||||
.collect::<IndexMap<_, _>>();
|
||||
let keys = values.keys().cloned().collect::<Vec<_>>();
|
||||
let borrowed = black_box(&values);
|
||||
let mut total = 0;
|
||||
let begin = Instant::now();
|
||||
for key in &keys {
|
||||
total = add(total, borrowed.get(key.as_str()).copied().unwrap_or(0));
|
||||
}
|
||||
black_box(total);
|
||||
let elapsed = begin.elapsed();
|
||||
black_box((&keys, &values));
|
||||
let checksum = add(total, keys.len() as i64)
|
||||
.checked_sub(source.len() as i64)
|
||||
.expect("integer overflow");
|
||||
(checksum, elapsed)
|
||||
}
|
||||
"call_read" => {
|
||||
let rows = source
|
||||
.iter()
|
||||
.map(|value| {
|
||||
let name = format!("row-value-{value}");
|
||||
OwnedRecord {
|
||||
count: i64::from(*value) - name.chars().count() as i64 - 1,
|
||||
tags: vec![name.clone()],
|
||||
name,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let borrowed = black_box(&rows);
|
||||
let begin = Instant::now();
|
||||
let total = borrowed
|
||||
.iter()
|
||||
.fold(0, |total, row| add(total, forward_owned(row)));
|
||||
black_box(total);
|
||||
let elapsed = begin.elapsed();
|
||||
black_box(&rows);
|
||||
(total, elapsed)
|
||||
}
|
||||
"float" => {
|
||||
let begin = Instant::now();
|
||||
let total = source
|
||||
.iter()
|
||||
.fold(0.0, |total, value| total + f64::from(*value));
|
||||
black_box(total);
|
||||
let elapsed = begin.elapsed();
|
||||
(total as i64, elapsed)
|
||||
}
|
||||
"bytes_concat" => {
|
||||
let chunks = source.chunks(1).map(<[u8]>::to_vec).collect::<Vec<_>>();
|
||||
let begin = Instant::now();
|
||||
let mut output = Vec::new();
|
||||
for chunk in &chunks {
|
||||
output.extend_from_slice(chunk);
|
||||
}
|
||||
black_box(&output);
|
||||
let elapsed = begin.elapsed();
|
||||
drop(chunks);
|
||||
let total = output
|
||||
.iter()
|
||||
.fold(0, |total, byte| add(total, i64::from(*byte)));
|
||||
(total, elapsed)
|
||||
}
|
||||
"text_concat" => {
|
||||
let chunks = std::str::from_utf8(source)
|
||||
.unwrap()
|
||||
.split(',')
|
||||
.map(str::to_owned)
|
||||
.collect::<Vec<_>>();
|
||||
let begin = Instant::now();
|
||||
let mut output = String::new();
|
||||
for chunk in &chunks {
|
||||
output.push_str(chunk);
|
||||
}
|
||||
black_box(&output);
|
||||
let elapsed = begin.elapsed();
|
||||
drop(chunks);
|
||||
let total = output
|
||||
.bytes()
|
||||
.fold(0, |total, byte| add(total, i64::from(byte)));
|
||||
(total, elapsed)
|
||||
}
|
||||
"integer" => {
|
||||
let begin = Instant::now();
|
||||
let total = source
|
||||
.iter()
|
||||
.fold(0, |total, value| add(total, i64::from(*value)));
|
||||
(black_box(total), begin.elapsed())
|
||||
}
|
||||
"list" | "list_read" => {
|
||||
let mut values = Vec::new();
|
||||
let begin = Instant::now();
|
||||
values.extend(source.iter().map(|value| i64::from(*value)));
|
||||
let construction = begin.elapsed();
|
||||
let borrowed = black_box(&values);
|
||||
let begin = Instant::now();
|
||||
let total = borrowed.iter().fold(0, |total, value| add(total, *value));
|
||||
black_box(total);
|
||||
let traversal = begin.elapsed();
|
||||
drop(values);
|
||||
(
|
||||
total,
|
||||
if operation == "list" {
|
||||
construction
|
||||
} else {
|
||||
traversal
|
||||
},
|
||||
)
|
||||
}
|
||||
"list_record" | "list_record_read" => {
|
||||
let mut values = Vec::new();
|
||||
let begin = Instant::now();
|
||||
for value in source {
|
||||
values.push(Record {
|
||||
first: i64::from(*value),
|
||||
second: 0,
|
||||
third: 0,
|
||||
});
|
||||
}
|
||||
let construction = begin.elapsed();
|
||||
let borrowed = black_box(&values);
|
||||
let begin = Instant::now();
|
||||
let total = borrowed
|
||||
.iter()
|
||||
.fold(0, |total, row| add(total, record_value(row)));
|
||||
black_box(total);
|
||||
let traversal = begin.elapsed();
|
||||
drop(values);
|
||||
(
|
||||
total,
|
||||
if operation == "list_record" {
|
||||
construction
|
||||
} else {
|
||||
traversal
|
||||
},
|
||||
)
|
||||
}
|
||||
"map" | "map_read" => {
|
||||
let mut values = IndexMap::new();
|
||||
let begin = Instant::now();
|
||||
for value in source {
|
||||
let key = i64::try_from(values.len()).expect("map size");
|
||||
values.insert(key, i64::from(*value));
|
||||
}
|
||||
let construction = begin.elapsed();
|
||||
let borrowed = black_box(&values);
|
||||
let begin = Instant::now();
|
||||
let total = borrowed.values().fold(0, |total, value| add(total, *value));
|
||||
black_box(total);
|
||||
drop(values);
|
||||
let traversal = begin.elapsed();
|
||||
(
|
||||
total,
|
||||
if operation == "map" {
|
||||
construction
|
||||
} else {
|
||||
traversal
|
||||
},
|
||||
)
|
||||
}
|
||||
_ => panic!("unknown kernel"),
|
||||
};
|
||||
serde_json::json!({"checksum": checksum, "elapsed_ns": elapsed.as_nanos()})
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let arguments = std::env::args().collect::<Vec<_>>();
|
||||
let input: serde_json::Value = serde_json::from_str(&arguments[2]).expect("input object");
|
||||
let ticks = input["ticks"].as_str().expect("ticks string");
|
||||
let output = if let Some(operation) = arguments[1].strip_prefix("continuous_") {
|
||||
serde_json::Value::Array((0..10).map(|_| measure(operation, ticks)).collect())
|
||||
} else {
|
||||
measure(&arguments[1], ticks)
|
||||
};
|
||||
println!("{output}");
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
setting {
|
||||
limits: dever.http.Limits
|
||||
lifetime_ms: Int
|
||||
}
|
||||
|
||||
type Service {
|
||||
Tcp
|
||||
Live
|
||||
}
|
||||
|
||||
listening(result: dever.net.ListenResult.Listening(listener)) (
|
||||
value: dever.system.Listener
|
||||
) {
|
||||
value = listener
|
||||
}
|
||||
|
||||
listening(result: dever.net.ListenResult.Failed(message)) (value: dever.system.Listener) {
|
||||
fail(app.WorkerFailure.Failed(message))
|
||||
}
|
||||
|
||||
bound(result: dever.net.PortResult.Bound(port)) (value: Int) {
|
||||
value = port
|
||||
}
|
||||
|
||||
bound(result: dever.net.PortResult.Failed(message)) (value: Int) {
|
||||
fail(app.WorkerFailure.Failed(message))
|
||||
}
|
||||
|
||||
written(result: dever.io.WriteResult.Done) () {}
|
||||
|
||||
written(result: dever.io.WriteResult.Failed(message)) () {
|
||||
fail(app.WorkerFailure.Failed(message))
|
||||
}
|
||||
|
||||
closed(result: dever.io.CloseResult.Closed) () {}
|
||||
|
||||
closed(result: dever.io.CloseResult.Failed(message)) () {
|
||||
fail(app.WorkerFailure.Failed(message))
|
||||
}
|
||||
|
||||
paused(result: dever.time.SleepResult.Done) () {}
|
||||
|
||||
paused(result: dever.time.SleepResult.Failed(message)) () {
|
||||
fail(app.WorkerFailure.Failed(message))
|
||||
}
|
||||
|
||||
tcpChunk(event: dever.io.ReadEvent.Chunk(bytes), socket: dever.system.Socket) () recover("TCP 连接错误由运行器检查") {
|
||||
written(result(dever.net.write(socket, bytes)))
|
||||
}
|
||||
|
||||
tcpChunk(event: dever.io.ReadEvent.Failed(message), socket: dever.system.Socket) () recover("TCP 连接错误由运行器检查") {
|
||||
blocking(dever.io.println("ERROR|" + message))
|
||||
}
|
||||
|
||||
tcpStream(
|
||||
result: dever.net.ReadStreamResult.Streaming(stream),
|
||||
socket: dever.system.Socket
|
||||
) () {
|
||||
each(tcpChunk, stream, socket)
|
||||
}
|
||||
|
||||
tcpStream(
|
||||
result: dever.net.ReadStreamResult.Failed(message),
|
||||
socket: dever.system.Socket
|
||||
) () {
|
||||
fail(app.WorkerFailure.Failed(message))
|
||||
}
|
||||
|
||||
tcpConnection(
|
||||
event: dever.net.ConnectResult.Connected(socket),
|
||||
limits: dever.http.Limits
|
||||
) () recover("TCP 连接错误由运行器检查") {
|
||||
written(result(dever.net.timeout(socket, limits.timeout_ms)))
|
||||
tcpStream(result(dever.net.chunks(socket, 4096)), socket)
|
||||
closed(result(dever.net.close(socket)))
|
||||
}
|
||||
|
||||
tcpConnection(
|
||||
event: dever.net.ConnectResult.Failed(message),
|
||||
limits: dever.http.Limits
|
||||
) () recover("TCP 连接错误由运行器检查") {
|
||||
blocking(dever.io.println("ERROR|" + message))
|
||||
}
|
||||
|
||||
tcpServer(listener: dever.system.Listener, limits: dever.http.Limits) () {
|
||||
stream = dever.net.connections(listener)
|
||||
parallelEach(tcpConnection, stream, limits.connections, limits)
|
||||
}
|
||||
|
||||
websocketMessage(
|
||||
event: dever.websocket.MessageEvent.Read(message),
|
||||
socket: dever.system.WebSocket
|
||||
) () recover("WebSocket 连接错误由运行器检查") {
|
||||
websocketEcho(message, socket)
|
||||
}
|
||||
|
||||
websocketMessage(
|
||||
event: dever.websocket.MessageEvent.Failed(message),
|
||||
socket: dever.system.WebSocket
|
||||
) () recover("WebSocket 连接错误由运行器检查") {
|
||||
blocking(dever.io.println("ERROR|" + message))
|
||||
}
|
||||
|
||||
websocketEcho(
|
||||
message: dever.websocket.Message.Text(value),
|
||||
socket: dever.system.WebSocket
|
||||
) () recover("WebSocket 连接错误由运行器检查") {
|
||||
written(result(dever.websocket.send(socket, dever.websocket.Message.Text(value))))
|
||||
}
|
||||
|
||||
websocketEcho(
|
||||
message: dever.websocket.Message.Binary(value),
|
||||
socket: dever.system.WebSocket
|
||||
) () recover("WebSocket 连接错误由运行器检查") {
|
||||
written(result(dever.websocket.send(socket, dever.websocket.Message.Binary(value))))
|
||||
}
|
||||
|
||||
websocketEcho(message: other, socket: dever.system.WebSocket) () recover("WebSocket 连接错误由运行器检查") {}
|
||||
|
||||
websocketLimits() (result: dever.websocket.Limits) {
|
||||
result = dever.websocket.Limits {
|
||||
message_bytes = 4096
|
||||
idle_ms = setting.lifetime_ms
|
||||
write_ms = setting.limits.timeout_ms
|
||||
}
|
||||
}
|
||||
|
||||
websocketConnection(result: dever.websocket.ConnectResult.Connected(socket)) () {
|
||||
each(websocketMessage, dever.websocket.messages(socket), socket)
|
||||
}
|
||||
|
||||
websocketConnection(result: dever.websocket.ConnectResult.Failed(message)) () {
|
||||
fail(app.WorkerFailure.Failed(message))
|
||||
}
|
||||
|
||||
route(request: dever.http.Request, reply: dever.system.HttpReply) () {
|
||||
livePath(request.target, reply)
|
||||
}
|
||||
|
||||
livePath(target: "/ws", reply: dever.system.HttpReply) () recover("实时连接错误由运行器检查") {
|
||||
websocketConnection(result(dever.websocket.accept(reply, websocketLimits())))
|
||||
}
|
||||
|
||||
livePath(target: "/events", reply: dever.system.HttpReply) () recover("实时连接错误由运行器检查") {
|
||||
written(result(dever.sse.start(reply, [])))
|
||||
written(
|
||||
result(
|
||||
dever.sse.send(
|
||||
reply,
|
||||
dever.sse.Event {
|
||||
event = "live"
|
||||
data = "ready"
|
||||
id = "1"
|
||||
retry_ms = null
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
paused(result(dever.task.sleep(setting.lifetime_ms)))
|
||||
}
|
||||
|
||||
livePath(target: other, reply: dever.system.HttpReply) () recover("实时连接错误由运行器检查") {
|
||||
written(
|
||||
result(
|
||||
dever.http.respond(
|
||||
reply,
|
||||
dever.http.Response {
|
||||
status = 404
|
||||
headers = []
|
||||
body = dever.bytes.fromText("unknown live benchmark path")
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
liveLimits() (result: dever.http.LiveLimits) {
|
||||
result = dever.http.LiveLimits {
|
||||
chunk_bytes = 4096
|
||||
idle_ms = setting.lifetime_ms
|
||||
heartbeat_ms = 100
|
||||
}
|
||||
}
|
||||
|
||||
liveServer(listener: dever.system.Listener) () {
|
||||
dever.system.httpServeLive(route, listener, setting.limits, liveLimits())
|
||||
}
|
||||
|
||||
serve(service: Service.Tcp, listener: dever.system.Listener) () {
|
||||
tcpServer(listener, setting.limits)
|
||||
}
|
||||
|
||||
serve(service: Service.Live, listener: dever.system.Listener) () {
|
||||
liveServer(listener)
|
||||
}
|
||||
|
||||
worker(service: Service) () {
|
||||
listener = listening(result(dever.net.listen("127.0.0.1", 0)))
|
||||
port = bound(result(dever.net.port(listener)))
|
||||
server = run(serve(service, listener))
|
||||
blocking(dever.io.println("READY|" + int.toText(port)))
|
||||
paused(result(dever.task.sleep(setting.lifetime_ms)))
|
||||
stop(server)
|
||||
closed(result(dever.net.closeListener(listener)))
|
||||
}
|
||||
|
||||
port.tcp() () {
|
||||
worker(Service.Tcp)
|
||||
}
|
||||
|
||||
port.live() () {
|
||||
worker(Service.Live)
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
cmd tcp = app.tcp
|
||||
|
||||
cmd ws = app.ws
|
||||
|
||||
cmd sse = app.sse
|
||||
@@ -0,0 +1,18 @@
|
||||
type WorkerFailure {
|
||||
error Failed(message: Text)
|
||||
}
|
||||
|
||||
tcp() (result: Bool) {
|
||||
port.tcp()
|
||||
result = true
|
||||
}
|
||||
|
||||
ws() (result: Bool) {
|
||||
port.live()
|
||||
result = true
|
||||
}
|
||||
|
||||
sse() (result: Bool) {
|
||||
port.live()
|
||||
result = true
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
tcp() () fails app.WorkerFailure
|
||||
|
||||
live() () fails app.WorkerFailure
|
||||
@@ -0,0 +1,144 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
ready() () {
|
||||
blocking(bench.ready(0))
|
||||
}
|
||||
|
||||
populateOne(value: Int) () {
|
||||
created = model.create({
|
||||
value = value
|
||||
label = "fixture"
|
||||
})
|
||||
}
|
||||
|
||||
populate() () {
|
||||
each(populateOne, config.ormRows())
|
||||
}
|
||||
|
||||
crudOnce(value: Int) () {
|
||||
begin = dever.time.monotonicNanos()
|
||||
created = model.create({
|
||||
value = value
|
||||
label = "created"
|
||||
})
|
||||
changed = model.update(created.id, { label = "updated" })
|
||||
removed = model.delete(created.id)
|
||||
end = dever.time.monotonicNanos()
|
||||
blocking(bench.report(begin, end, "crud", 1, changed + removed))
|
||||
}
|
||||
|
||||
listOnce(value: Int) () {
|
||||
begin = dever.time.monotonicNanos()
|
||||
page = model.list({
|
||||
order = id.asc
|
||||
page = 1
|
||||
size = 32
|
||||
})
|
||||
end = dever.time.monotonicNanos()
|
||||
blocking(bench.report(begin, end, "list", 1, page.total + length(page.items)))
|
||||
}
|
||||
|
||||
cursorOnce(value: Int) () {
|
||||
begin = dever.time.monotonicNanos()
|
||||
page = model.cursor({
|
||||
order = id.asc
|
||||
size = 32
|
||||
})
|
||||
end = dever.time.monotonicNanos()
|
||||
blocking(bench.report(begin, end, "cursor", 1, length(page.items)))
|
||||
}
|
||||
|
||||
countItem(value: model.Item, total: Int) (next: Int) {
|
||||
next = total + 1
|
||||
}
|
||||
|
||||
streamOnce(value: Int) () {
|
||||
begin = dever.time.monotonicNanos()
|
||||
rows = model.stream({
|
||||
order = id.asc
|
||||
size = 16
|
||||
})
|
||||
total = reduce(countItem, rows, 0)
|
||||
end = dever.time.monotonicNanos()
|
||||
blocking(bench.report(begin, end, "stream", 1, total))
|
||||
}
|
||||
|
||||
poolOnce(value: Int) () {
|
||||
begin = dever.time.monotonicNanos()
|
||||
total = model.count()
|
||||
end = dever.time.monotonicNanos()
|
||||
blocking(bench.report(begin, end, "pool", 1, total))
|
||||
}
|
||||
|
||||
transaction fillCancelledTransaction() () {
|
||||
created = model.create({
|
||||
value = 1
|
||||
label = "cancelled"
|
||||
})
|
||||
delayed = model.delay(config.ormCancelWork())
|
||||
}
|
||||
|
||||
ormIdle() (result: Bool) {
|
||||
total = model.count()
|
||||
blocking(bench.ready(total))
|
||||
bench.pause(config.lifetime())
|
||||
result = true
|
||||
}
|
||||
|
||||
ormCrud() (result: Bool) {
|
||||
ready()
|
||||
each(crudOnce, config.ormInput())
|
||||
result = true
|
||||
}
|
||||
|
||||
ormList() (result: Bool) {
|
||||
populate()
|
||||
ready()
|
||||
each(listOnce, config.ormInput())
|
||||
result = true
|
||||
}
|
||||
|
||||
ormCursor() (result: Bool) {
|
||||
populate()
|
||||
ready()
|
||||
each(cursorOnce, config.ormInput())
|
||||
result = true
|
||||
}
|
||||
|
||||
ormStream() (result: Bool) {
|
||||
ready()
|
||||
each(streamOnce, config.ormInput())
|
||||
result = true
|
||||
}
|
||||
|
||||
ormPool() (result: Bool) {
|
||||
ready()
|
||||
parallelEach(poolOnce, config.ormInput(), config.ormConcurrency())
|
||||
result = true
|
||||
}
|
||||
|
||||
ormSeed() (result: Bool) {
|
||||
populate()
|
||||
result = true
|
||||
}
|
||||
|
||||
count() (total: Int) {
|
||||
total = model.count()
|
||||
}
|
||||
|
||||
cancel() (result: Bool) {
|
||||
fillCancelledTransaction()
|
||||
result = true
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
public get count = app.count
|
||||
public post cancel = app.cancel
|
||||
cmd idle = app.ormIdle
|
||||
cmd crud = app.ormCrud
|
||||
cmd list = app.ormList
|
||||
cmd cursor = app.ormCursor
|
||||
cmd stream = app.ormStream
|
||||
cmd pool = app.ormPool
|
||||
cmd seed = app.ormSeed
|
||||
@@ -0,0 +1,13 @@
|
||||
type DelayResult {
|
||||
total: Int
|
||||
}
|
||||
|
||||
global type Item {
|
||||
value: Int index
|
||||
label: Text(1, 32)
|
||||
}
|
||||
|
||||
sql delay(work: Int) (result: DelayResult) {
|
||||
sqlite = "WITH RECURSIVE counter(value) AS (SELECT 1 UNION ALL SELECT value + 1 FROM counter WHERE value < ?1) SELECT SUM(value) AS total FROM counter"
|
||||
postgres = "SELECT SUM(value)::bigint AS total FROM generate_series(1::bigint, $1::bigint) AS value"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
cmd run = app.run
|
||||
@@ -0,0 +1,4 @@
|
||||
run() (result: Bool) {
|
||||
dever.io.println("profile")
|
||||
result = true
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
port.blockingPause(millis: Int) () {
|
||||
dever.time.sleep(millis)
|
||||
}
|
||||
|
||||
port.reportError(message: Text) () {
|
||||
dever.io.println("ERROR|" + message)
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
cmd sync_idle = app.syncIdle
|
||||
|
||||
cmd async_idle = app.asyncIdle
|
||||
|
||||
cmd tasks = app.tasks
|
||||
|
||||
cmd tasks_on_worker = app.tasksOnWorker
|
||||
|
||||
cmd channels = app.channels
|
||||
|
||||
cmd cancel = app.cancel
|
||||
@@ -0,0 +1,132 @@
|
||||
type Parking {
|
||||
ready: Channel<Int>
|
||||
gate: Channel<Int>
|
||||
}
|
||||
|
||||
syncIdle() (result: Bool) {
|
||||
bench.ready(0)
|
||||
port.blockingPause(config.lifetime())
|
||||
result = true
|
||||
}
|
||||
|
||||
idle() () {
|
||||
blocking(bench.ready(0))
|
||||
bench.pause(config.lifetime())
|
||||
}
|
||||
|
||||
asyncIdle() (result: Bool) {
|
||||
idle()
|
||||
result = true
|
||||
}
|
||||
|
||||
identity(value: Int) (result: Int) {
|
||||
result = value
|
||||
}
|
||||
|
||||
taskSum(value: Int, state: Int) (result: Int) {
|
||||
job = run(identity(value))
|
||||
result = state + wait(job)
|
||||
}
|
||||
|
||||
sum(value: Int, state: Int) (result: Int) {
|
||||
result = state + value
|
||||
}
|
||||
|
||||
taskSample(index: Int, input: Bytes) () {
|
||||
begin = dever.time.monotonicNanos()
|
||||
checksum = reduce(taskSum, input, 0)
|
||||
end = dever.time.monotonicNanos()
|
||||
blocking(bench.report(begin, end, "tasks", length(input), checksum))
|
||||
}
|
||||
|
||||
sendByte(value: Int, output: Channel<Int>) () {
|
||||
send(output, value)
|
||||
}
|
||||
|
||||
produce(input: Bytes, output: Channel<Int>) () {
|
||||
each(sendByte, input, output)
|
||||
close(output)
|
||||
}
|
||||
|
||||
channelSample(index: Int, input: Bytes) () {
|
||||
output = channel(Int, config.channelCapacity())
|
||||
begin = dever.time.monotonicNanos()
|
||||
producer = run(produce(input, output))
|
||||
checksum = reduce(sum, stream(output), 0)
|
||||
wait(producer)
|
||||
end = dever.time.monotonicNanos()
|
||||
blocking(bench.report(begin, end, "channels", length(input), checksum))
|
||||
}
|
||||
|
||||
waitOne(value: Int, parking: Parking) () {
|
||||
send(parking.ready, value)
|
||||
signaled(receive(parking.gate))
|
||||
}
|
||||
|
||||
signaled(value: Int) () {}
|
||||
|
||||
signaled(value: null) () {
|
||||
blocking(port.reportError("channel closed before expected signal"))
|
||||
}
|
||||
|
||||
hold(parking: Parking) () {
|
||||
parallelEach(waitOne, config.pendingInput(), config.pending(), parking)
|
||||
}
|
||||
|
||||
started(value: Int, ready: Channel<Int>) () {
|
||||
signaled(receive(ready))
|
||||
}
|
||||
|
||||
cancelWorker() () {
|
||||
parking = Parking {
|
||||
ready = channel(Int, config.pending())
|
||||
gate = channel(Int, 1)
|
||||
}
|
||||
job = run(hold(parking))
|
||||
each(started, config.pendingInput(), parking.ready)
|
||||
blocking(bench.ready(config.pending()))
|
||||
bench.pause(config.idleSampleMs())
|
||||
begin = dever.time.monotonicNanos()
|
||||
stop(job)
|
||||
end = dever.time.monotonicNanos()
|
||||
close(parking.ready)
|
||||
close(parking.gate)
|
||||
blocking(bench.report(begin, end, "cancel", config.pending(), config.pending()))
|
||||
}
|
||||
|
||||
taskWorker() () {
|
||||
input = config.input()
|
||||
blocking(bench.ready(0))
|
||||
each(taskSample, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], input)
|
||||
}
|
||||
|
||||
channelWorker() () {
|
||||
input = config.input()
|
||||
blocking(bench.ready(0))
|
||||
each(channelSample, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], input)
|
||||
}
|
||||
|
||||
tasks() (result: Bool) {
|
||||
taskWorker()
|
||||
result = true
|
||||
}
|
||||
|
||||
nestedTaskWorker() () {
|
||||
job = run(taskWorker())
|
||||
wait(job)
|
||||
}
|
||||
|
||||
tasksOnWorker() (result: Bool) {
|
||||
nestedTaskWorker()
|
||||
result = true
|
||||
}
|
||||
|
||||
channels() (result: Bool) {
|
||||
channelWorker()
|
||||
result = true
|
||||
}
|
||||
|
||||
cancel() (result: Bool) {
|
||||
cancelWorker()
|
||||
result = true
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
blockingPause(millis: Int) () fails dever.time.SleepResult
|
||||
|
||||
reportError(message: Text) () fails dever.io.WriteResult
|
||||
Reference in new issue
Block a user