Skip to content

Commit

Permalink
Initial commit of vtebench
Browse files Browse the repository at this point in the history
  • Loading branch information
jwilm committed Dec 25, 2017
0 parents commit 444126e
Show file tree
Hide file tree
Showing 9 changed files with 691 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/target/
**/*.rs.bk
416 changes: 416 additions & 0 deletions Cargo.lock

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "vtebench"
version = "0.1.0"
authors = ["Joe Wilm <joe@jwilm.com>"]

[dependencies]
structopt = "0.1"
structopt-derive = "0.1"
terminfo = "0.4"
failure = "0.1"
rand = "0.4"
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
vtebench
========

A tool for generating terminal benchmarks

## Usage

The general usage pattern is

```
vtebench -w $(tput cols) -h $(tput lines) [-c|-b=BYTES|-t=TERM] <benchmark>
```

Terminal protocol will be output to `stdout`. Output **must be** directed into a
file rather than used directly to benchmark. `vtebench` is written for ease of
understanding, **not** performance. To benchmark the currently running terminal
then, something like this would work:

```sh
vtebench -w $(tput cols) -h $(tput lines) alt-screen-random-write \
> /tmp/100mb.vte

time cat /tmp/100mb.vte
```

In the future, it would be nice to have a script to automate generating all of
the tests, running them several times and generate statistics, and print all the
results in a machine+human friendly format.

## Contributing

If you wish to add a new test, do the following:

1. Add a new function in _bench.rs_ with the same pattern as an existing
function.
2. Add a subcommand to run it in the `Benchmark` enum within _cli.rs_.
3. Handle the subcommand in _main.rs_.

If there are escape codes that are not yet supported on `Context` it is quite
helpful to reference the `terminfo` man page and cross reference with the
`terminfo` **crate**'s `capability` submodule documentation. Each capability
name has a corresponding type in that submodule.
35 changes: 35 additions & 0 deletions src/bench.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use std::io::Write;

use rand::{self, Rng};

use context::Context;
use cli::Options;
use result::Result;

pub fn alt_screen_random_write<W: Write>(ctx: &mut Context<W>, options: &Options) -> Result<usize> {
let mut written = 0;
let mut rng = rand::thread_rng();
let h = options.height;
let w = options.width;
let mut buf = Vec::<u8>::with_capacity(w as usize);

ctx.smcup()?;

while written < options.bytes {
buf.clear();
let (l, c) = (rng.gen_range(0, h), rng.gen_range(0, w - 2));
let space = w - c;
let to_write = rng.gen_range(0, space);

written += ctx.cup(l, c)?;
if options.colorize {
written += ctx.setaf(rng.gen_range(0, 8))?;
}
written += ctx.write_ascii(to_write as _)?;
}

ctx.sgr0()?;
ctx.rmcup()?;

Ok(written)
}
37 changes: 37 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/// Command line options
#[derive(StructOpt, Debug)]
#[structopt(name = "vtebench", about = "Benchmark Terminal Emulators")]
pub struct Options {
#[structopt(short = "w", help = "width of terminal", default_value = "80")]
pub width: u16,

#[structopt(short = "h", help = "height of terminal", default_value = "24")]
pub height: u16,

#[structopt(
short = "b",
long = "bytes",
help = "minimum bytes to output; actual value may be slightly higher",
default_value = "1048576"
)]
pub bytes: usize,

#[structopt(short = "c", help = "colorized output (not all tests support)")]
pub colorize: bool,

#[structopt(long = "term", help = "height of terminal", default_value = "xterm-256color")]
pub term: String,

#[structopt(subcommand)]
pub benchmark: Benchmark,
}

#[derive(StructOpt, Debug)]
#[structopt(name = "vtebench", about = "Benchmark Terminal Emulators")]
pub enum Benchmark {
#[structopt(
name = "alt-screen-random-write",
help = "Set alt screen; repeatedly: pick random location, write ascii"
)]
AltScreenRandomWrite,
}
69 changes: 69 additions & 0 deletions src/context.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
use std::io::{self, Write};
use terminfo::{Database, capability as cap};

use rand::{self, Rng};

use result::Result;

pub struct Context<'a, W: Write + 'a> {
pub out: &'a mut W,
pub db: &'a Database,
pub buf: Vec<u8>,
pub rng: rand::ThreadRng,
}

impl<'a, W: Write + 'a> Context<'a, W> {
pub fn smcup(&mut self) -> Result<usize> {
let smcup = expand!(self.db.get::<cap::EnterCaMode>().unwrap().as_ref())?;
self.write_all(&smcup)?;
Ok(smcup.len())
}

pub fn rmcup(&mut self) -> Result<usize> {
let rmcup = expand!(self.db.get::<cap::ExitCaMode>().unwrap().as_ref())?;
self.write_all(&rmcup)?;
Ok(rmcup.len())
}

pub fn cup(&mut self, line: u16, col: u16) -> Result<usize> {
let cup = expand!(self.db.get::<cap::CursorAddress>().unwrap().as_ref(); line, col)?;
self.write_all(&cup)?;
Ok(cup.len())
}

pub fn write_ascii(&mut self, count: usize) -> Result<usize> {
self.buf.clear();
for _ in 0..count {
self.buf.push(self.rng.gen_range(32, 127));
}

self.out.write_all(&self.buf)?;
Ok(count)
}

pub fn setaf(&mut self, v: u16) -> Result<usize> {
let setaf = expand!(
self.db.get::<cap::SetAForeground>().unwrap().as_ref(); v
)?;
self.write_all(&setaf)?;
Ok(setaf.len())
}

pub fn sgr0(&mut self) -> Result<usize> {
let sgr0 = expand!(self.db.get::<cap::ExitAttributeMode>().unwrap().as_ref())?;
self.write_all(&sgr0)?;
Ok(sgr0.len())
}
}

impl<'a, W: Write + 'a> Write for Context<'a, W> {
#[inline]
fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
self.out.write(bytes)
}

#[inline]
fn flush(&mut self) -> io::Result<()> {
self.out.flush()
}
}
54 changes: 54 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//! A tool for generating benchmark scripts for terminal emulators
//!
//~ This program is intended to be run and its output piped into a file, and the
//~ file can simply be `cat`ed from the terminal emulator under test. This
//~ ensures that the terminal is being benchmarked and not this vtebench
//~ application.
extern crate rand;
extern crate structopt;

#[macro_use] extern crate failure;
#[macro_use] extern crate structopt_derive;
#[macro_use] extern crate terminfo;

use std::io::{self, BufWriter};

use structopt::StructOpt;
use terminfo::Database;

mod bench;
mod cli;
mod context;
mod result;

use cli::{Options, Benchmark};
use context::Context;
use result::Result;

fn main() {
run().unwrap();
}

fn run() -> Result<()> {
// Load command line options
let options = Options::from_args();

// Load terminfo database
let db = Database::from_name(&options.term)?;

// Get I/O handle
let stdout = io::stdout();
let mut out = stdout.lock();
let mut out = BufWriter::new(&mut out);

// Create the output context
let mut ctx = Context { out: &mut out, db: &db, buf: Vec::new(), rng: rand::thread_rng() };

// Run the requested benchmark
match options.benchmark {
Benchmark::AltScreenRandomWrite => bench::alt_screen_random_write(&mut ctx, &options)?,
};

// Success!
Ok(())
}
25 changes: 25 additions & 0 deletions src/result.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use std::io;
use terminfo;

pub type Result<T> = ::std::result::Result<T, Error>;

#[derive(Debug, Fail)]
pub enum Error {
#[fail(display = "{}", _0)]
Io(#[cause] io::Error),

#[fail(display = "{}", _0)]
Terminfo(#[cause] terminfo::Error),
}

impl From<io::Error> for Error {
fn from(val: io::Error) -> Error {
Error::Io(val)
}
}

impl From<terminfo::Error> for Error {
fn from(val: terminfo::Error) -> Error {
Error::Terminfo(val)
}
}

0 comments on commit 444126e

Please sign in to comment.