feat: use tracing crate to measure cpu time
This commit is contained in:
parent
80fe2c6d83
commit
0a5e25e4f0
8 changed files with 289 additions and 104 deletions
|
|
@ -19,7 +19,11 @@ serde = { version = "1.0", features = [ "derive" ] }
|
||||||
serde_json = { version ="1.0", features = [] }
|
serde_json = { version ="1.0", features = [] }
|
||||||
structopt = { version = "0.3" }
|
structopt = { version = "0.3" }
|
||||||
elm-project-utils = { path = "../../../infra/rust-elm-project-utils" }
|
elm-project-utils = { path = "../../../infra/rust-elm-project-utils" }
|
||||||
tracing = "0.1"
|
tracing = { version = "0.1", features = [] }
|
||||||
|
rustc-hash = "1.1"
|
||||||
|
|
||||||
|
# Required to transpile view functions to Rust
|
||||||
|
genco = "0.15"
|
||||||
|
|
||||||
# All of these are required for deno's javascript runtime. We need to keep the
|
# All of these are required for deno's javascript runtime. We need to keep the
|
||||||
# same versions as other projects in our cargo workspace. Multiple different
|
# same versions as other projects in our cargo workspace. Multiple different
|
||||||
|
|
|
||||||
4
examples/single-page/script/generate_test_file
Executable file
4
examples/single-page/script/generate_test_file
Executable file
|
|
@ -0,0 +1,4 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# generate a 10MB test file
|
||||||
|
< /dev/urandom tr -dc "[:alnum:]" | head -c10000000 > file.txt
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
module Main exposing (view, view2, view3)
|
module Main exposing (view, view2, view3, badReturnType)
|
||||||
|
|
||||||
import Html exposing (Html, div, text)
|
import Html exposing (Html, div, text)
|
||||||
import Svg exposing (Svg, svg)
|
import Svg exposing (Svg, svg)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
|
use crate::{InputType, OutputType};
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use crate::{OutputType, InputType};
|
|
||||||
|
|
||||||
pub(crate) fn generate<S: AsRef<str>>(
|
pub(crate) fn generate<S: AsRef<str>>(
|
||||||
source_checksum: u64,
|
source_checksum: u64,
|
||||||
|
|
|
||||||
164
src/main.rs
164
src/main.rs
|
|
@ -1,5 +1,7 @@
|
||||||
extern crate naive_wadler_prettier as pretty;
|
extern crate naive_wadler_prettier as pretty;
|
||||||
use crate::reporting::{CompilerError, InterpreterError, Problem, SetupError, TypeError};
|
use crate::reporting::{CompilerError, InterpreterError, Problem, SetupError, TypeError};
|
||||||
|
use crate::timings::Timings;
|
||||||
|
use elm_project_utils::ChecksumConstraint;
|
||||||
use elmi::DataBinary;
|
use elmi::DataBinary;
|
||||||
use os_pipe::dup_stderr;
|
use os_pipe::dup_stderr;
|
||||||
use pretty::pretty;
|
use pretty::pretty;
|
||||||
|
|
@ -14,19 +16,18 @@ use std::process::{Command, Stdio};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
use elm_project_utils::ChecksumConstraint;
|
|
||||||
use tokio;
|
use tokio;
|
||||||
use tracing::{span, info_span};
|
use tracing::{info_span, span, Instrument};
|
||||||
use crate::timings::Timings;
|
|
||||||
|
|
||||||
mod reporting;
|
|
||||||
mod fixture;
|
mod fixture;
|
||||||
|
mod reporting;
|
||||||
mod timings;
|
mod timings;
|
||||||
|
mod transpile;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let args = Arguments::from_args();
|
let args = Arguments::from_args();
|
||||||
|
|
||||||
let span_subscriber = Timings::new();
|
let (span_subscriber, timing_report) = Timings::new();
|
||||||
tracing::subscriber::set_global_default(span_subscriber).unwrap();
|
tracing::subscriber::set_global_default(span_subscriber).unwrap();
|
||||||
|
|
||||||
match args {
|
match args {
|
||||||
|
|
@ -39,26 +40,43 @@ fn main() {
|
||||||
output,
|
output,
|
||||||
verbosity,
|
verbosity,
|
||||||
} => {
|
} => {
|
||||||
let run_program = info_span!("run_program_span");
|
|
||||||
run_program.enter();
|
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
if let Err(problem) = exec(file, debug, function, input, output, verbosity) {
|
let span = info_span!("exec");
|
||||||
eprintln!(
|
let timing_guard = span.enter();
|
||||||
"\t\x1b[1;92mFinished\x1b[0m in {:?}",
|
let result = exec(file, debug, function, input, output, verbosity);
|
||||||
Instant::now() - start
|
drop(timing_guard);
|
||||||
);
|
if let Err(problem) = result {
|
||||||
|
let span = info_span!("pretty print problem");
|
||||||
|
let timing_guard = span.enter();
|
||||||
eprintln!("{}", pretty(80, problem.to_doc()));
|
eprintln!("{}", pretty(80, problem.to_doc()));
|
||||||
} else {
|
drop(timing_guard);
|
||||||
|
}
|
||||||
{
|
{
|
||||||
let stdout = io::stdout();
|
let stdout = io::stdout();
|
||||||
let mut handle = stdout.lock();
|
let mut handle = stdout.lock();
|
||||||
handle.flush().unwrap();
|
handle.flush().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if verbosity > 1 {
|
||||||
|
let mut report = timing_report.dump().unwrap();
|
||||||
|
report.sort_by(|(_, a), (_, b)| b.partial_cmp(a).unwrap());
|
||||||
|
|
||||||
|
for (step, duration) in report.iter() {
|
||||||
|
eprintln!("[{:>10.3?}] {}", duration, step);
|
||||||
|
}
|
||||||
|
}
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"\n\t\x1b[1;92mFinished\x1b[0m in {:?}",
|
"\t\x1b[1;92mFinished\x1b[0m [{}] in {:?}",
|
||||||
|
"unoptimized",
|
||||||
Instant::now() - start
|
Instant::now() - start
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Arguments::Transpile {
|
||||||
|
file,
|
||||||
|
function,
|
||||||
|
verbosity,
|
||||||
|
} => {
|
||||||
|
println!("todo transpile the code")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -71,14 +89,10 @@ fn exec(
|
||||||
output: Option<PathBuf>,
|
output: Option<PathBuf>,
|
||||||
verbosity: u64,
|
verbosity: u64,
|
||||||
) -> Result<(), Problem> {
|
) -> Result<(), Problem> {
|
||||||
info_span!("another_span").enter();
|
|
||||||
// Our first elm make call is where we build the users program. There is a pretty good chance
|
// Our first elm make call is where we build the users program. There is a pretty good chance
|
||||||
// this won't work.
|
// this won't work.
|
||||||
let mut command = Command::new("elm");
|
let mut command = Command::new("elm");
|
||||||
command
|
command.arg("make").arg("--output").arg("/dev/null");
|
||||||
.arg("make")
|
|
||||||
.arg("--output")
|
|
||||||
.arg("/dev/null");
|
|
||||||
//.stdin(Stdio::null());
|
//.stdin(Stdio::null());
|
||||||
//.stdout(Stdio::null());
|
//.stdout(Stdio::null());
|
||||||
// I am using the default inherit stderr behavior here.
|
// I am using the default inherit stderr behavior here.
|
||||||
|
|
@ -89,11 +103,13 @@ fn exec(
|
||||||
}
|
}
|
||||||
command.arg(&file);
|
command.arg(&file);
|
||||||
|
|
||||||
let start = Instant::now();
|
let span = info_span!("elm make target file");
|
||||||
|
let timing_guard = span.enter();
|
||||||
match command.output() {
|
match command.output() {
|
||||||
Ok(output) => {
|
Ok(output) => {
|
||||||
if !output.status.success() {
|
if !output.status.success() {
|
||||||
io::stderr().write_all(&output.stderr).unwrap();
|
io::stderr().write_all(&output.stderr).unwrap();
|
||||||
|
io::stderr().write_all(&output.stdout).unwrap();
|
||||||
return Err(CompilerError::FailedBuildingFixture.into());
|
return Err(CompilerError::FailedBuildingFixture.into());
|
||||||
}
|
}
|
||||||
if verbosity > 0 {
|
if verbosity > 0 {
|
||||||
|
|
@ -105,7 +121,7 @@ fn exec(
|
||||||
return Err(Problem::Wildcard("elm failed".into()));
|
return Err(Problem::Wildcard("elm failed".into()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
eprintln!("[{:?}] compiled {:?}", Instant::now() - start, file);
|
drop(timing_guard);
|
||||||
|
|
||||||
// Step 2, find the elm.json and elm-stuff directory
|
// Step 2, find the elm.json and elm-stuff directory
|
||||||
let elm_project_dir =
|
let elm_project_dir =
|
||||||
|
|
@ -155,7 +171,8 @@ fn exec(
|
||||||
let interfaces = load_interfaces(&elm_cache_dir)?;
|
let interfaces = load_interfaces(&elm_cache_dir)?;
|
||||||
|
|
||||||
// Step 5, check for the desired function
|
// Step 5, check for the desired function
|
||||||
let start = Instant::now();
|
let span = info_span!("resolved target function");
|
||||||
|
let timing_guard = span.enter();
|
||||||
let (input_type, output_type) = match interfaces.get(&target_module) {
|
let (input_type, output_type) = match interfaces.get(&target_module) {
|
||||||
Some(interface) => match interface.values.get(&elmi::Name::from(&function)) {
|
Some(interface) => match interface.values.get(&elmi::Name::from(&function)) {
|
||||||
Some(annotation) => {
|
Some(annotation) => {
|
||||||
|
|
@ -163,17 +180,18 @@ fn exec(
|
||||||
|
|
||||||
resolve_function_type(tipe)?
|
resolve_function_type(tipe)?
|
||||||
}
|
}
|
||||||
None => return Err(CompilerError::BadImport(function).into()),
|
None => return Err(CompilerError::BadImport(target_module, function).into()),
|
||||||
},
|
},
|
||||||
None => return Err(CompilerError::MissingModuleTypeInformation(target_module).into()),
|
None => return Err(CompilerError::MissingModuleTypeInformation(target_module).into()),
|
||||||
};
|
};
|
||||||
eprintln!("[{:?}] resolved target function", Instant::now() - start);
|
drop(timing_guard);
|
||||||
|
|
||||||
// step 6 create our private project
|
// step 6 create our private project
|
||||||
let generator_dir = setup_generator_project(verbosity, elm_project_dir.clone())?;
|
let generator_dir = setup_generator_project(verbosity, elm_project_dir.clone())?;
|
||||||
|
|
||||||
// step 7 create an Elm fixture file to run our function
|
// step 7 create an Elm fixture file to run our function
|
||||||
let start = Instant::now();
|
let span = info_span!("create Elm fixture files");
|
||||||
|
let timing_guard = span.enter();
|
||||||
let (gen_module_name, source) = fixture::generate(
|
let (gen_module_name, source) = fixture::generate(
|
||||||
source_checksum,
|
source_checksum,
|
||||||
target_module,
|
target_module,
|
||||||
|
|
@ -189,11 +207,7 @@ fn exec(
|
||||||
.map_err(|io_err| CompilerError::WriteOutputFailed(io_err, source_filename.clone()))?
|
.map_err(|io_err| CompilerError::WriteOutputFailed(io_err, source_filename.clone()))?
|
||||||
.write_all(source.as_bytes())
|
.write_all(source.as_bytes())
|
||||||
.map_err(|io_err| CompilerError::WriteOutputFailed(io_err, source_filename.clone()))?;
|
.map_err(|io_err| CompilerError::WriteOutputFailed(io_err, source_filename.clone()))?;
|
||||||
eprintln!(
|
drop(timing_guard);
|
||||||
"[{:?}] created generator Elm program",
|
|
||||||
Instant::now() - start
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
// step 8 compile the fixture
|
// step 8 compile the fixture
|
||||||
let mut intermediate_file = generator_dir.join("obj").join(&gen_module_name);
|
let mut intermediate_file = generator_dir.join("obj").join(&gen_module_name);
|
||||||
|
|
@ -221,7 +235,8 @@ fn exec(
|
||||||
}
|
}
|
||||||
command.arg(&source_filename);
|
command.arg(&source_filename);
|
||||||
|
|
||||||
let start = Instant::now();
|
let span = info_span!("elm make fixture file");
|
||||||
|
let timing_guard = span.enter();
|
||||||
match command.output() {
|
match command.output() {
|
||||||
Ok(output) => {
|
Ok(output) => {
|
||||||
if !output.status.success() {
|
if !output.status.success() {
|
||||||
|
|
@ -232,13 +247,7 @@ fn exec(
|
||||||
return Err(CompilerError::FailedBuildingFixture.into());
|
return Err(CompilerError::FailedBuildingFixture.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if verbosity > 1 {
|
drop(timing_guard);
|
||||||
eprintln!(
|
|
||||||
"compiled {:?} in {:?}",
|
|
||||||
intermediate_file,
|
|
||||||
Instant::now() - start,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 9 fixup the compiled script to run in Deno
|
// Step 9 fixup the compiled script to run in Deno
|
||||||
|
|
||||||
|
|
@ -251,7 +260,7 @@ fn exec(
|
||||||
// `"REPLACE_ME" + "abc" * 2000` we would have over 6k bytes to write out the new code. We
|
// `"REPLACE_ME" + "abc" * 2000` we would have over 6k bytes to write out the new code. We
|
||||||
// will have to do some extra book keeping to make sure the buffer space is big enough
|
// will have to do some extra book keeping to make sure the buffer space is big enough
|
||||||
// for the replacement code.
|
// for the replacement code.
|
||||||
let mut final_script = ( || {
|
let mut final_script = (|| {
|
||||||
let mut final_script = data
|
let mut final_script = data
|
||||||
.replace("'REPLACE_ME_WITH_JSON_STRINGIFY'", "JSON.stringify(x)")
|
.replace("'REPLACE_ME_WITH_JSON_STRINGIFY'", "JSON.stringify(x)")
|
||||||
//.replace("setTimeout", "(function(x, y) { globalThis.__bootstrap.timers.setTimeout(x, y)})")
|
//.replace("setTimeout", "(function(x, y) { globalThis.__bootstrap.timers.setTimeout(x, y)})")
|
||||||
|
|
@ -273,7 +282,9 @@ fn exec(
|
||||||
// scope. Figure out if I need to include all of them. For example, starmelon does not need
|
// scope. Figure out if I need to include all of them. For example, starmelon does not need
|
||||||
// to perform http calls right now, but I eventually want to.
|
// to perform http calls right now, but I eventually want to.
|
||||||
final_script.push_str("const { setTimeout } = globalThis.__bootstrap.timers;");
|
final_script.push_str("const { setTimeout } = globalThis.__bootstrap.timers;");
|
||||||
final_script.push_str("Deno.core.setMacrotaskCallback(globalThis.__bootstrap.timers.handleTimerMacrotask);\n");
|
final_script.push_str(
|
||||||
|
"Deno.core.setMacrotaskCallback(globalThis.__bootstrap.timers.handleTimerMacrotask);\n",
|
||||||
|
);
|
||||||
final_script.push_str("globalThis.setTimeout = setTimeout;");
|
final_script.push_str("globalThis.setTimeout = setTimeout;");
|
||||||
|
|
||||||
final_script.push_str(&format!("var worker = Elm.{}.init();\n", &gen_module_name));
|
final_script.push_str(&format!("var worker = Elm.{}.init();\n", &gen_module_name));
|
||||||
|
|
@ -445,12 +456,18 @@ fn exec(
|
||||||
};
|
};
|
||||||
|
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
tokio::runtime::Builder::new_current_thread()
|
let span = info_span!("create tokio runtime");
|
||||||
|
let timing_guard = span.enter();
|
||||||
|
let sys = tokio::runtime::Builder::new_current_thread()
|
||||||
.enable_all()
|
.enable_all()
|
||||||
.build()
|
.build()
|
||||||
.unwrap()
|
.unwrap();
|
||||||
.block_on(async move { runtime::xyz(worker, main_module, input).await })?;
|
drop(timing_guard);
|
||||||
eprintln!("eval javascript in {:?}", Instant::now() - start);
|
|
||||||
|
let span = info_span!("eval javascript");
|
||||||
|
let timing_guard = span.enter();
|
||||||
|
sys.block_on(async move { runtime::xyz(worker, main_module, input).await })?;
|
||||||
|
drop(timing_guard);
|
||||||
|
|
||||||
// step 13 receive the callback
|
// step 13 receive the callback
|
||||||
// If I understood which combination of run_event_loop was required to execute
|
// If I understood which combination of run_event_loop was required to execute
|
||||||
|
|
@ -458,13 +475,12 @@ fn exec(
|
||||||
// now. Another way to do this would be with an Arc. This will panic if the
|
// now. Another way to do this would be with an Arc. This will panic if the
|
||||||
// mailbox is currently borrowed
|
// mailbox is currently borrowed
|
||||||
match mailbox.replace(None) {
|
match mailbox.replace(None) {
|
||||||
Some(Ok(buffer)) => {
|
Some(Ok(buffer)) => match output {
|
||||||
match output {
|
|
||||||
None => {
|
None => {
|
||||||
io::stdout()
|
io::stdout()
|
||||||
.write_all(&buffer)
|
.write_all(&buffer)
|
||||||
.map_err(|io_err| CompilerError::WriteOutputFailed(io_err, "stdout".into()))?;
|
.map_err(|io_err| CompilerError::WriteOutputFailed(io_err, "stdout".into()))?;
|
||||||
},
|
}
|
||||||
Some(filename) => {
|
Some(filename) => {
|
||||||
let mut f = fs::File::create(&filename)
|
let mut f = fs::File::create(&filename)
|
||||||
.map_err(|io_err| CompilerError::WriteOutputFailed(io_err, filename))?;
|
.map_err(|io_err| CompilerError::WriteOutputFailed(io_err, filename))?;
|
||||||
|
|
@ -472,8 +488,7 @@ fn exec(
|
||||||
f.write_all(&buffer)
|
f.write_all(&buffer)
|
||||||
.map_err(|io_err| CompilerError::WriteOutputFailed(io_err, "stdout".into()))?;
|
.map_err(|io_err| CompilerError::WriteOutputFailed(io_err, "stdout".into()))?;
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
|
||||||
Some(Err(problem)) => {
|
Some(Err(problem)) => {
|
||||||
println!("had a problem {}", problem);
|
println!("had a problem {}", problem);
|
||||||
}
|
}
|
||||||
|
|
@ -499,9 +514,14 @@ fn load_interfaces(elm_cache_dir: &Path) -> Result<HashMap<String, elmi::Interfa
|
||||||
Some(ext) if ext == "elmi" => {
|
Some(ext) if ext == "elmi" => {
|
||||||
// step 4 load all the modules
|
// step 4 load all the modules
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
|
let parse_span = info_span!("parse elmi");
|
||||||
|
let parse_guard = parse_span.enter();
|
||||||
|
|
||||||
|
let load_span = info_span!("file reads");
|
||||||
|
let load_guard = load_span.enter();
|
||||||
let data = std::fs::read(&path)
|
let data = std::fs::read(&path)
|
||||||
.map_err(|io_err| CompilerError::ReadInputFailed(io_err, path.clone()))?;
|
.map_err(|io_err| CompilerError::ReadInputFailed(io_err, path.clone()))?;
|
||||||
eprintln!("[{:?}] loaded {:?}", Instant::now() - start, path);
|
drop(load_guard);
|
||||||
|
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
let (_remaining, i) = elmi::Interface::get(&data).map_err(|_err| {
|
let (_remaining, i) = elmi::Interface::get(&data).map_err(|_err| {
|
||||||
|
|
@ -516,7 +536,6 @@ fn load_interfaces(elm_cache_dir: &Path) -> Result<HashMap<String, elmi::Interfa
|
||||||
.split('-')
|
.split('-')
|
||||||
.collect::<Vec<&str>>()
|
.collect::<Vec<&str>>()
|
||||||
.join(".");
|
.join(".");
|
||||||
eprintln!("[{:?}] parsed {:?}", Instant::now() - start, module_name);
|
|
||||||
interfaces.insert(module_name, i);
|
interfaces.insert(module_name, i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -571,7 +590,6 @@ enum Arguments {
|
||||||
Exec {
|
Exec {
|
||||||
#[structopt(parse(from_os_str))]
|
#[structopt(parse(from_os_str))]
|
||||||
file: PathBuf,
|
file: PathBuf,
|
||||||
|
|
||||||
function: String,
|
function: String,
|
||||||
#[structopt(long)]
|
#[structopt(long)]
|
||||||
debug: bool,
|
debug: bool,
|
||||||
|
|
@ -582,6 +600,13 @@ enum Arguments {
|
||||||
#[structopt(short = "v", parse(from_occurrences))]
|
#[structopt(short = "v", parse(from_occurrences))]
|
||||||
verbosity: u64,
|
verbosity: u64,
|
||||||
},
|
},
|
||||||
|
Transpile {
|
||||||
|
#[structopt(parse(from_os_str))]
|
||||||
|
file: PathBuf,
|
||||||
|
function: String,
|
||||||
|
#[structopt(short = "v", parse(from_occurrences))]
|
||||||
|
verbosity: u64,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
|
@ -716,13 +741,11 @@ fn setup_generator_project(verbosity: u64, elm_project_dir: PathBuf) -> Result<P
|
||||||
|
|
||||||
let elm_json_path = elm_project_dir.join("elm.json");
|
let elm_json_path = elm_project_dir.join("elm.json");
|
||||||
|
|
||||||
let mut constraint = ChecksumConstraint::new(
|
let mut constraint =
|
||||||
&elm_json_path,
|
ChecksumConstraint::new(&elm_json_path, our_temp_dir.join("elm-json-checksum"))?;
|
||||||
our_temp_dir.join("elm-json-checksum"),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
if !constraint.dirty()? {
|
if !constraint.dirty()? {
|
||||||
return Ok(our_temp_dir)
|
return Ok(our_temp_dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut data = fs::read(&elm_json_path)
|
let mut data = fs::read(&elm_json_path)
|
||||||
|
|
@ -791,7 +814,8 @@ fn elm_install<P: AsRef<Path>, S: AsRef<str>>(
|
||||||
our_temp_dir: P,
|
our_temp_dir: P,
|
||||||
package: S,
|
package: S,
|
||||||
) -> Result<(), Problem> {
|
) -> Result<(), Problem> {
|
||||||
info_span!("elm_install").enter();
|
let span = info_span!("elm_install");
|
||||||
|
let _ = span.enter();
|
||||||
let mut command = Command::new("elm");
|
let mut command = Command::new("elm");
|
||||||
|
|
||||||
command
|
command
|
||||||
|
|
@ -830,7 +854,6 @@ fn elm_install<P: AsRef<Path>, S: AsRef<str>>(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
mod runtime {
|
mod runtime {
|
||||||
use crate::reporting::InterpreterError;
|
use crate::reporting::InterpreterError;
|
||||||
use crate::ValidatedInput;
|
use crate::ValidatedInput;
|
||||||
|
|
@ -850,6 +873,7 @@ mod runtime {
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
use tracing::{info_span, Instrument};
|
||||||
|
|
||||||
fn get_error_class_name(e: &AnyError) -> &'static str {
|
fn get_error_class_name(e: &AnyError) -> &'static str {
|
||||||
deno_runtime::errors::get_error_class_name(e).unwrap_or("Error")
|
deno_runtime::errors::get_error_class_name(e).unwrap_or("Error")
|
||||||
|
|
@ -862,7 +886,6 @@ mod runtime {
|
||||||
todo!("Web workers are not supported in the example");
|
todo!("Web workers are not supported in the example");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
let options = WorkerOptions {
|
let options = WorkerOptions {
|
||||||
bootstrap: BootstrapOptions {
|
bootstrap: BootstrapOptions {
|
||||||
args: vec![],
|
args: vec![],
|
||||||
|
|
@ -912,8 +935,14 @@ mod runtime {
|
||||||
) -> Result<(), InterpreterError> {
|
) -> Result<(), InterpreterError> {
|
||||||
let wait_for_inspector = false;
|
let wait_for_inspector = false;
|
||||||
// step 10 load the module into our v8 isolate
|
// step 10 load the module into our v8 isolate
|
||||||
worker.execute_main_module(&main_module).await?;
|
worker
|
||||||
worker.run_event_loop(wait_for_inspector).await?;
|
.execute_main_module(&main_module)
|
||||||
|
.instrument(info_span!("execute main module"))
|
||||||
|
.await?;
|
||||||
|
worker
|
||||||
|
.run_event_loop(wait_for_inspector)
|
||||||
|
.instrument(info_span!("run v8 event loop"))
|
||||||
|
.await?;
|
||||||
|
|
||||||
{
|
{
|
||||||
let scope = &mut worker.js_runtime.handle_scope();
|
let scope = &mut worker.js_runtime.handle_scope();
|
||||||
|
|
@ -935,6 +964,8 @@ mod runtime {
|
||||||
let this = v8::undefined(scope).into();
|
let this = v8::undefined(scope).into();
|
||||||
|
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
|
let span = info_span!("dispatch v8 call");
|
||||||
|
let timing_guard = span.enter();
|
||||||
match input {
|
match input {
|
||||||
None => {
|
None => {
|
||||||
function.call(scope, this, &[]);
|
function.call(scope, this, &[]);
|
||||||
|
|
@ -973,10 +1004,13 @@ mod runtime {
|
||||||
function.call(scope, this, &[arg2]);
|
function.call(scope, this, &[arg2]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
eprintln!("\tcall dispatched {:?}", Instant::now() - start);
|
drop(timing_guard)
|
||||||
}
|
}
|
||||||
worker.run_event_loop(wait_for_inspector).await?;
|
|
||||||
eprintln!("finished waiting on runtime");
|
worker
|
||||||
|
.run_event_loop(wait_for_inspector)
|
||||||
|
.instrument(info_span!("run v8 event loop"))
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use deno_core::error::AnyError;
|
use deno_core::error::AnyError;
|
||||||
|
use elm_project_utils::RedoScriptError;
|
||||||
use elmi;
|
use elmi;
|
||||||
use pretty::{self, cyan, vcat, Doc};
|
use pretty::{self, cyan, vcat, Doc};
|
||||||
use rusty_v8;
|
use rusty_v8;
|
||||||
|
|
@ -6,7 +7,6 @@ use serde_json;
|
||||||
use std::cmp::max;
|
use std::cmp::max;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use elm_project_utils::RedoScriptError;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Problem {
|
pub enum Problem {
|
||||||
|
|
@ -69,7 +69,6 @@ impl From<elm_project_utils::RedoScriptError> for Problem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum TypeError {
|
pub enum TypeError {
|
||||||
CantEvalRecord,
|
CantEvalRecord,
|
||||||
|
|
@ -91,7 +90,7 @@ pub enum CompilerError {
|
||||||
CantParseModule(String), // first line
|
CantParseModule(String), // first line
|
||||||
EmptyModule,
|
EmptyModule,
|
||||||
MissingModuleTypeInformation(String),
|
MissingModuleTypeInformation(String),
|
||||||
BadImport(String),
|
BadImport(String, String),
|
||||||
FailedBuildingFixture,
|
FailedBuildingFixture,
|
||||||
ReadInputFailed(io::Error, PathBuf),
|
ReadInputFailed(io::Error, PathBuf),
|
||||||
WriteOutputFailed(io::Error, PathBuf),
|
WriteOutputFailed(io::Error, PathBuf),
|
||||||
|
|
@ -148,9 +147,12 @@ impl CompilerError {
|
||||||
CantParseModule(_first_line_prefix) => Doc::text("todo could not parse module"),
|
CantParseModule(_first_line_prefix) => Doc::text("todo could not parse module"),
|
||||||
EmptyModule => Doc::text("I did not expect the module file to be empty"),
|
EmptyModule => Doc::text("I did not expect the module file to be empty"),
|
||||||
MissingModuleTypeInformation(_) => Doc::text("todo missing module type information"),
|
MissingModuleTypeInformation(_) => Doc::text("todo missing module type information"),
|
||||||
BadImport(_) => {
|
BadImport(module, symbol) => {
|
||||||
// TODO suggest alternatives using edit distance
|
// TODO suggest alternatives using edit distance
|
||||||
Doc::text("The `Html` module does not expose `view5`")
|
Doc::text(format!(
|
||||||
|
"The `{}` module does not expose `{}`",
|
||||||
|
module, symbol
|
||||||
|
))
|
||||||
// TODO
|
// TODO
|
||||||
// Doc::text("These names seem close though")
|
// Doc::text("These names seem close though")
|
||||||
//Doc::group(
|
//Doc::group(
|
||||||
|
|
@ -161,7 +163,7 @@ impl CompilerError {
|
||||||
ReadInputFailed(_io_err, _path) => {
|
ReadInputFailed(_io_err, _path) => {
|
||||||
title = "IO ERROR";
|
title = "IO ERROR";
|
||||||
Doc::text("TODO read file failed")
|
Doc::text("TODO read file failed")
|
||||||
},
|
}
|
||||||
WriteOutputFailed(io_err, path) => {
|
WriteOutputFailed(io_err, path) => {
|
||||||
Doc::text(format!("TODO write file failed {:?} {:?}", io_err, path))
|
Doc::text(format!("TODO write file failed {:?} {:?}", io_err, path))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
140
src/timings.rs
Normal file
140
src/timings.rs
Normal file
|
|
@ -0,0 +1,140 @@
|
||||||
|
//! This module provides a crude system for timing how long various steps of the program take to
|
||||||
|
//! execute using the tracing framework. I seek absolution for suboptimal code. For example I want
|
||||||
|
//! to throw a span around all clones and see exactly how long I spend copying data. If I see that
|
||||||
|
//! I only spend a few milliseconds overall then that could be a price worth paying for development
|
||||||
|
//! velocity.
|
||||||
|
//!
|
||||||
|
//! This is quite a different use case then flame graphs.
|
||||||
|
use rustc_hash::FxHashMap;
|
||||||
|
use std::collections::hash_map::Entry;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
use std::sync::{Arc, RwLock};
|
||||||
|
use std::thread::{self, ThreadId};
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
use tracing::info_span;
|
||||||
|
use tracing::span::{Attributes, Record};
|
||||||
|
use tracing::{Event, Id, Metadata, Subscriber};
|
||||||
|
|
||||||
|
//
|
||||||
|
// Each step will be described with a less than 100 char string. Steps will overlap.
|
||||||
|
//
|
||||||
|
// I am not tracking the relationship between
|
||||||
|
// the spans. I assume that in each thread that if enter is called then exit will be called before
|
||||||
|
// enter is called again.
|
||||||
|
pub(crate) struct Timings {
|
||||||
|
ids: AtomicUsize,
|
||||||
|
started: RwLock<FxHashMap<(ThreadId, u64), Instant>>,
|
||||||
|
// This setup of having separate locks creates a risk of deadlocks when generating a report.
|
||||||
|
// What if the reading thread takes a lock
|
||||||
|
summary: Arc<RwLock<FxHashMap<u64, Duration>>>,
|
||||||
|
span_names: Arc<RwLock<FxHashMap<u64, String>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct ReportHandle {
|
||||||
|
summary: Arc<RwLock<FxHashMap<u64, Duration>>>,
|
||||||
|
span_names: Arc<RwLock<FxHashMap<u64, String>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Timings {
|
||||||
|
pub fn new() -> (Timings, ReportHandle) {
|
||||||
|
let summary = Arc::new(RwLock::new(FxHashMap::default()));
|
||||||
|
let span_names = Arc::new(RwLock::new(FxHashMap::default()));
|
||||||
|
|
||||||
|
let report_handle = ReportHandle {
|
||||||
|
summary: Arc::clone(&summary),
|
||||||
|
span_names: Arc::clone(&span_names),
|
||||||
|
};
|
||||||
|
|
||||||
|
let subscriber = Timings {
|
||||||
|
ids: AtomicUsize::new(1),
|
||||||
|
started: RwLock::new(FxHashMap::default()),
|
||||||
|
summary,
|
||||||
|
span_names,
|
||||||
|
};
|
||||||
|
|
||||||
|
(subscriber, report_handle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReportHandle {
|
||||||
|
pub fn dump(&self) -> Option<Vec<(String, Duration)>> {
|
||||||
|
// Now it might take a long time (relatively speaking) to copy many megabytes of report
|
||||||
|
// data. I went with this design because my target use case is for batch programs that
|
||||||
|
// print out a report at the end of processing. Also my target use case won't have
|
||||||
|
// megabytes of timing information. It should only have a few kilobytes.
|
||||||
|
let summary = self.summary.try_read().ok()?.clone();
|
||||||
|
let span_names = self.span_names.try_read().ok()?.clone();
|
||||||
|
|
||||||
|
let mut accumulator: HashMap<String, Duration> = HashMap::new();
|
||||||
|
|
||||||
|
for (id, duration) in summary.into_iter() {
|
||||||
|
if let Some(name) = span_names.get(&id) {
|
||||||
|
let entry = accumulator
|
||||||
|
.entry(name.clone())
|
||||||
|
.or_insert_with(|| Default::default());
|
||||||
|
*entry += duration;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(accumulator.into_iter().collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl tracing::Subscriber for Timings {
|
||||||
|
fn enabled(&self, metadata: &Metadata<'_>) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_span(&self, span: &Attributes<'_>) -> Id {
|
||||||
|
//println!("got new span {:?}", span);
|
||||||
|
let x = self.ids.fetch_add(1, Ordering::SeqCst);
|
||||||
|
let id = Id::from_u64(x as u64);
|
||||||
|
let name = span.metadata().name().to_owned();
|
||||||
|
if let Ok(mut guard) = self.span_names.write() {
|
||||||
|
guard.insert(x as u64, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn record(&self, _span: &Id, _values: &Record<'_>) {}
|
||||||
|
|
||||||
|
fn record_follows_from(&self, _span: &Id, _follows: &Id) {}
|
||||||
|
|
||||||
|
fn event(&self, _event: &Event<'_>) {}
|
||||||
|
|
||||||
|
fn enter(&self, span: &Id) {
|
||||||
|
let mut lock = self.started.write().unwrap();
|
||||||
|
match lock.entry((thread::current().id(), span.into_u64())) {
|
||||||
|
Entry::Vacant(vacant) => {
|
||||||
|
vacant.insert(Instant::now());
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// We already started this span in the current thread so this is a logic error.
|
||||||
|
// This subscriber is only listening to how long each span is executing.
|
||||||
|
()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exit(&self, span: &Id) {
|
||||||
|
let stopped = Instant::now();
|
||||||
|
// I unwrap the read lock because the mutex should not be poisoned in the simple single
|
||||||
|
// threaded code of starmelon.
|
||||||
|
let key = (thread::current().id(), span.into_u64());
|
||||||
|
if let Some(started) = self.started.write().unwrap().remove(&key) {
|
||||||
|
let mut lock = self.summary.write().unwrap();
|
||||||
|
match lock.entry(span.into_u64()) {
|
||||||
|
Entry::Vacant(vacant) => {
|
||||||
|
vacant.insert(stopped - started);
|
||||||
|
}
|
||||||
|
Entry::Occupied(mut occupied) => {
|
||||||
|
*occupied.get_mut() += stopped - started;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println!("failed to find span when exiting");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1
src/transpile.rs
Normal file
1
src/transpile.rs
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue