Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
908 changes: 440 additions & 468 deletions Cargo.lock

Large diffs are not rendered by default.

22 changes: 11 additions & 11 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "sirun"
version = "0.1.11"
version = "0.1.12"
authors = ["Bryan English <bryan.english@datadoghq.com>"]
edition = "2018"
license = "Apache-2.0 OR MIT"
Expand All @@ -13,21 +13,21 @@ lto = true
[dependencies]
async-std = { version = "1.9.0", features = ["unstable", "attributes"] }
serde_json = { version = "1.0.64", features = ["preserve_order"] }
shlex = "1.0.0"
nix = "0.20.0"
assert_cmd = "1.0.3"
serde_yaml = "0.8.17"
lazy_static = "1.4.0"
serde = { version = "1.0.124", features = ["derive"] }
anyhow = "<=1.0.48"
which = "4.0.2"
indexmap = { version = "2.9.0", features = ["serde"] }
shlex = "2.0.1"
nix = { version = "0.31.3", features = ["fs"] }
assert_cmd = "2.0.17"
# yaml_serde is the official YAML organization's maintained fork of the
# now-unmaintained serde_yaml; the package rename keeps `use serde_yaml::` working.
serde_yaml = { package = "yaml_serde", version = "0.10" }
serde = { version = "1.0.228", features = ["derive"] }
anyhow = "1.0.102"
indexmap = { version = "2.14.0", features = ["serde"] }

[target.'cfg(target_os = "linux")'.dependencies]
perfcnt = "0.8.0"

[dev-dependencies]
predicates = "1.0.7"
predicates = "3.1.4"
serial_test = "0.5.1"

[target.'cfg(target_os = "linux")'.dev-dependencies]
Expand Down
154 changes: 93 additions & 61 deletions LICENSE-3rdparty.csv

Large diffs are not rendered by default.

58 changes: 21 additions & 37 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
//
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc.

use anyhow::*;
use lazy_static::lazy_static;
use anyhow::{anyhow, bail, ensure, Result};
use serde::{Deserialize, Serialize};
use serde_yaml::{from_str, to_string, Mapping, Value};
use std::fmt;
Expand All @@ -31,19 +30,15 @@ impl fmt::Display for Config {
}
}

fn get_shell_command(obj: &Mapping, name: &Value) -> Result<Vec<String>> {
fn get_shell_command(obj: &Mapping, name: &str) -> Result<Vec<String>> {
let run = obj
.get(name)
.unwrap()
.as_str()
.ok_or_else(|| anyhow!("'{}' must be a string", name.as_str().unwrap()))?;

shlex::split(run).ok_or_else(|| {
anyhow!(
"'{}' must be a properly formed shell command",
name.as_str().unwrap()
)
})
.ok_or_else(|| anyhow!("'{}' must be a string", name))?;

shlex::split(run)
.ok_or_else(|| anyhow!("'{}' must be a properly formed shell command", name))
}

fn get_env(env: &mut HashMap<String, String>, config_env: &Value) -> Result<()> {
Expand All @@ -62,25 +57,14 @@ fn get_env(env: &mut HashMap<String, String>, config_env: &Value) -> Result<()>
Ok(())
}

lazy_static! {
static ref NAME_KEY: Value = "name".into();
static ref RUN_KEY: Value = "run".into();
static ref SERVICE_KEY: Value = "service".into();
static ref SETUP_KEY: Value = "setup".into();
static ref TEARDOWN_KEY: Value = "teardown".into();
static ref TIMEOUT_KEY: Value = "timeout".into();
static ref ITERATIONS_KEY: Value = "iterations".into();
static ref INSTRUCTIONS_KEY: Value = "instructions".into();
}

fn apply_config(config: &mut Config, config_val: &Value) -> Result<()> {
let config_val = config_val
.as_mapping()
.ok_or_else(|| anyhow!("invalid json"))?;

if let Ok(name) = env::var("SIRUN_NAME") {
config.name = Some(name)
} else if let Some(name_val) = config_val.get(&NAME_KEY) {
} else if let Some(name_val) = config_val.get("name") {
config.name = Some(
name_val
.as_str()
Expand All @@ -89,45 +73,45 @@ fn apply_config(config: &mut Config, config_val: &Value) -> Result<()> {
);
}

if config_val.contains_key(&SERVICE_KEY) {
config.service = Some(get_shell_command(config_val, &SERVICE_KEY)?);
if config_val.contains_key("service") {
config.service = Some(get_shell_command(config_val, "service")?);
}

if config_val.contains_key(&RUN_KEY) {
config.run = get_shell_command(config_val, &RUN_KEY)?;
if config_val.contains_key("run") {
config.run = get_shell_command(config_val, "run")?;
}

if config_val.contains_key(&SETUP_KEY) {
config.setup = Some(get_shell_command(config_val, &SETUP_KEY)?);
if config_val.contains_key("setup") {
config.setup = Some(get_shell_command(config_val, "setup")?);
}

if config_val.contains_key(&TEARDOWN_KEY) {
config.teardown = Some(get_shell_command(config_val, &TEARDOWN_KEY)?);
if config_val.contains_key("teardown") {
config.teardown = Some(get_shell_command(config_val, "teardown")?);
}

if let Some(timeout_val) = config_val.get(&TIMEOUT_KEY) {
if let Some(timeout_val) = config_val.get("timeout") {
config.timeout = Some(
timeout_val
.as_u64()
.ok_or_else(|| anyhow!("'timeout' must be a positive integer"))?,
);
}

if let Some(iterations_val) = config_val.get(&ITERATIONS_KEY) {
if let Some(iterations_val) = config_val.get("iterations") {
config.iterations = iterations_val
.as_u64()
.ok_or_else(|| anyhow!("iterations must be an integer >=1"))?;
ensure!(config.iterations > 0, "iterations must be an integer >=1");
}

if let Some(instructions_val) = config_val.get(&INSTRUCTIONS_KEY) {
if let Some(instructions_val) = config_val.get("instructions") {
config.instructions = instructions_val
.as_bool()
.ok_or_else(|| anyhow!("'instructions' must be a boolean"))?;
}

if let Some(env) = config_val.get(&"env".to_owned().into()) {
get_env(&mut config.env, &env)?;
if let Some(env) = config_val.get("env") {
get_env(&mut config.env, env)?;
}
Ok(())
}
Expand Down Expand Up @@ -183,7 +167,7 @@ pub(crate) fn get_config(filename: &str) -> Result<Config> {
);
&variants[id]
} else if let Some(variants) = variants.as_mapping() {
match variants.get(&variant_key.clone().into()) {
match variants.get(variant_key.as_str()) {
Some(val) => val,
None => bail!("variant key {} does not exist in object", variant_key),
}
Expand Down
10 changes: 5 additions & 5 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc.

use anyhow::*;
use anyhow::Result;
use async_std::{
net::UdpSocket,
process::{Command, Stdio, Child, ExitStatus},
Expand All @@ -14,10 +14,9 @@ use serde_json::json;
use std::{
collections::HashMap,
env,
os::unix::{io::{FromRawFd, RawFd}, process::ExitStatusExt},
os::unix::{io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}, process::ExitStatusExt},
process::exit,
};
use which::which;
use indexmap::IndexMap;

mod config;
Expand Down Expand Up @@ -139,16 +138,17 @@ async fn run_test(config: &Config, mut metrics: &mut HashMap<String, MetricValue
let (read_fd, write_fd) = nix::unistd::pipe()?;
// Set CLOEXEC on read_fd so the child does not inherit the read end.
nix::fcntl::fcntl(
read_fd,
&read_fd,
Comment thread
bengl marked this conversation as resolved.
nix::fcntl::FcntlArg::F_SETFD(nix::fcntl::FdFlag::FD_CLOEXEC),
)?;
let read_fd = read_fd.into_raw_fd();

let mut start_time = std::time::Instant::now();
// RUSAGE_CHILDREN cannot be snapshotted mid-run (it only updates after a child
// exits), so we use read_child_cpu_us to snapshot the live process CPU at
// signal time and subtract it from the final RUSAGE_CHILDREN delta.
let rusage_start = Rusage::new();
let mut child = run_cmd(&config.run, &config.env, Some(write_fd))?;
let mut child = run_cmd(&config.run, &config.env, Some(write_fd.as_raw_fd()))?;
// Close parent's write end so the child's exit causes EOF on the read end.
nix::unistd::close(write_fd)?;

Expand Down
2 changes: 1 addition & 1 deletion src/statsd.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::metric_value::*;
use anyhow::*;
use anyhow::Result;
use async_std::{
net::UdpSocket,
sync::{Arc, Barrier, RwLock},
Expand Down
2 changes: 1 addition & 1 deletion src/subproc.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use anyhow::*;
use anyhow::{bail, Result};
use async_std::{
process::{Command, Child, Stdio},
task::sleep,
Expand Down
2 changes: 1 addition & 1 deletion src/summarize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc.

use anyhow::*;
use anyhow::Result;
use async_std::io;
use indexmap::IndexMap;

Expand Down
32 changes: 16 additions & 16 deletions tests/examples.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,16 @@ fn simple_json() {
fn wall_and_cpu_pct() {
json_has!("examples/simple.json", move |map: &serde_yaml::Mapping| {
let map = map
.get(&"iterations".into())
.get("iterations")
.unwrap()
.as_sequence()
.unwrap();
let map = map.get(0).unwrap().as_mapping().unwrap();
let wall_time = map.get(&"wall.time".into()).unwrap().as_f64().unwrap();
let stime = map.get(&"system.time".into()).unwrap().as_f64().unwrap();
let utime = map.get(&"user.time".into()).unwrap().as_f64().unwrap();
let wall_time = map.get("wall.time").unwrap().as_f64().unwrap();
let stime = map.get("system.time").unwrap().as_f64().unwrap();
let utime = map.get("user.time").unwrap().as_f64().unwrap();
let pct = map
.get(&"cpu.pct.wall.time".into())
.get("cpu.pct.wall.time")
.unwrap()
.as_f64()
.unwrap();
Expand Down Expand Up @@ -188,7 +188,7 @@ fn iterations() {
"you should see this\nyou should see this\nyou should see this",
));
json_has!("./examples/iterations.json", |map: &serde_yaml::Mapping| {
map.get(&"iterations".into())
map.get("iterations")
.unwrap()
.as_sequence()
.unwrap()
Expand All @@ -208,7 +208,7 @@ fn iterations_nohup() {
fn iterations_not_cumulative() {
json_has!("./examples/iterations.json", |map: &serde_yaml::Mapping| {
let iter = map
.get(&"iterations".into())
.get("iterations")
.unwrap()
.as_sequence()
.unwrap()
Expand All @@ -218,7 +218,7 @@ fn iterations_not_cumulative() {
let val = iteration
.as_mapping()
.unwrap()
.get(&"user.time".into())
.get("user.time")
.unwrap()
.as_f64()
.unwrap();
Expand All @@ -236,13 +236,13 @@ fn iterations_not_cumulative() {
#[serial]
fn long() {
json_has!("./examples/long.json", |map: &serde_yaml::Mapping| {
map.get(&"iterations".into())
map.get("iterations")
.unwrap()
.get(0)
.unwrap()
.as_mapping()
.unwrap()
.get(&"user.time".into())
.get("user.time")
.unwrap()
.as_f64()
.unwrap()
Expand Down Expand Up @@ -283,7 +283,7 @@ fn assigned_port() {
fn insctrution_counts() {
if caps::has_cap(None, caps::CapSet::Permitted, caps::Capability::CAP_SYS_PTRACE).unwrap() {
json_has!("./examples/instructions.json", move |map: &serde_yaml::Mapping| {
let count = map.get(&"instructions".into())
let count = map.get("instructions")
.unwrap()
.as_f64()
.unwrap();
Expand All @@ -305,13 +305,13 @@ fn ready_signal_resets_wall_time() {
"./examples/ready-signal.json",
|map: &serde_yaml::Mapping| {
let wall_time = map
.get(&"iterations".into())
.get("iterations")
.unwrap()
.as_sequence()
.unwrap()[0]
.as_mapping()
.unwrap()
.get(&"wall.time".into())
.get("wall.time")
.unwrap()
.as_f64()
.unwrap();
Expand All @@ -329,7 +329,7 @@ fn ready_signal_fallback_when_no_signal() {
// App exits without writing to SIRUN_READY_FD — full timing used.
json_has!("./examples/simple.json", |map: &serde_yaml::Mapping| {
// simple.json has no ready signal; we just verify it still runs normally.
map.get(&"iterations".into())
map.get("iterations")
.unwrap()
.as_sequence()
.unwrap()
Expand All @@ -345,14 +345,14 @@ fn ready_signal_cpu_pct_bounded() {
"./examples/ready-signal-cpu.json",
|map: &serde_yaml::Mapping| {
let iter = map
.get(&"iterations".into())
.get("iterations")
.unwrap()
.as_sequence()
.unwrap()[0]
.as_mapping()
.unwrap();
let cpu_pct = iter
.get(&"cpu.pct.wall.time".into())
.get("cpu.pct.wall.time")
.unwrap()
.as_f64()
.unwrap();
Expand Down
Loading