Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Export crashes through telemetry #285

Merged
merged 19 commits into from
Mar 1, 2024
Merged
Show file tree
Hide file tree
Changes from 18 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
25 changes: 25 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ members = [
"spawn_worker",
"tests/spawn_from_lib",
"serverless",
"bin_tests",
]
# https://doc.rust-lang.org/cargo/reference/resolver.html#feature-resolver-version-2
resolver = "2"
Expand Down
242 changes: 235 additions & 7 deletions LICENSE-3rdparty.yml

Large diffs are not rendered by default.

26 changes: 26 additions & 0 deletions bin_tests/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
# This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021-Present Datadog, Inc.

[package]
name = "bin_tests"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
once_cell = "1.18"
anyhow = "1.0"
current_platform = "0.2.0"
datadog-profiling = { path = "../profiling" }
datadog-crashtracker = { path = "../crashtracker" }
ddcommon = { path = "../ddcommon" }
tempfile = "3.3"
serde_json = { version = "1.0" }
hyper = { version = "0.14", default-features = false }

[[bin]]
name = "crashtracker_bin_test"

[[bin]]
name = "test_the_tests"
65 changes: 65 additions & 0 deletions bin_tests/src/bin/crashtracker_bin_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2023-Present Datadog, Inc.

#[cfg(not(unix))]
fn main() {}

#[cfg(unix)]
fn main() -> anyhow::Result<()> {
unix::main()
}

#[cfg(unix)]
mod unix {
use anyhow::Context;
use std::env;

use datadog_crashtracker::{
self as crashtracker, CrashtrackerConfiguration, CrashtrackerMetadata,
};
use datadog_profiling::exporter::Tag;

#[inline(never)]
unsafe fn deref_ptr(p: *mut u8) {
*std::hint::black_box(p) = std::hint::black_box(1);
}

pub fn main() -> anyhow::Result<()> {
let mut args = env::args().skip(1);
let output_filename = args.next().context("Unexpected number of arguments")?;
let receiver_binary = args.next().context("Unexpected number of arguments")?;
let stderr_filename = args.next().context("Unexpected number of arguments")?;
let stdout_filename = args.next().context("Unexpected number of arguments")?;
crashtracker::init(
CrashtrackerConfiguration {
create_alt_stack: true,
endpoint: Some(datadog_profiling::exporter::Endpoint {
url: ddcommon::parse_uri(&format!("file://{}", output_filename))?,
api_key: None,
}),
path_to_receiver_binary: receiver_binary,
resolve_frames: crashtracker::CrashtrackerResolveFrames::Never,
stderr_filename: Some(stderr_filename),
stdout_filename: Some(stdout_filename),
collect_stacktrace: true,
},
CrashtrackerMetadata {
profiling_library_name: "libdatadog".to_owned(),
profiling_library_version: "1.0.0".to_owned(),
family: "native".to_owned(),
tags: vec![
Tag::new("service", "foo").unwrap(),
Tag::new("service_version", "bar").unwrap(),
Tag::new("runtime-id", "xyz").unwrap(),
Tag::new("language", "native").unwrap(),
],
},
)?;
crashtracker::begin_profiling_op(crashtracker::ProfilingOpTypes::CollectingSample)?;
unsafe {
deref_ptr(std::ptr::null_mut::<u8>());
}
crashtracker::end_profiling_op(crashtracker::ProfilingOpTypes::CollectingSample)?;
Ok(())
}
}
10 changes: 10 additions & 0 deletions bin_tests/src/bin/crashtracker_receiver.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2023-Present Datadog, Inc.

#[cfg(not(unix))]
fn main() {}

#[cfg(unix)]
fn main() -> anyhow::Result<()> {
datadog_crashtracker::receiver_entry_point()
}
7 changes: 7 additions & 0 deletions bin_tests/src/bin/test_the_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2023-Present Datadog, Inc.

/// This is an empty binary used to test the machinery of building bins
/// in cargo test works

fn main() {}
164 changes: 164 additions & 0 deletions bin_tests/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this machinery necessary?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a comment at the top of the module describing what it does

/// This module implements an abstraction over compilation with cargo with the purpose
/// of testing full binaries or dynamic libraries, instead if just rust static libraries.
///
/// The main entrypoint is fn build_artifacts which takes a list of artifacts to build,
/// either executable crates, cdylib, or extra binaries, invokes cargo and return the path
/// of the built artifact.
///
/// Builds are cached between invocations so that multiple tests can use the same artifact
/// without doing expensive work twice.
///
/// It is assumed that functions in this module are invoked in the context of a cargo #[test]
/// item, or a cargo run command to be able to locate artifacts built by cargo from the position
/// of the current binary.

// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2023-Present Datadog, Inc.

use std::{collections::HashMap, env, ops::DerefMut, path::PathBuf, process, sync::Mutex};

use anyhow::Ok;
use once_cell::sync::OnceCell;

/// This crate implements an abstraction over compilation with cargo with the purpose
/// of testing full binaries or dynamic libraries, instead if just rust static libraries.
///
/// The main entrypoint is `fn build_artifacts` which takes a list of artifacts to build,
/// either executable crates, cdylib, or extra binaries, invokes cargo and return the path
/// of the built artifact.
///
/// Builds are cached between invocations so that multiple tests can use the same artifact
/// without doing expensive work twice.
///
/// It is assumed that functions in this crate are invoked in the context of a cargo #[test]
/// item, or a `cargo run` command to be able to locate artifacts built by cargo from the position
/// of the current binary.

#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub enum ArtifactType {
ExecutablePackage,
CDylib,
Bin,
}

#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub enum BuildProfile {
Debug,
Release,
}

#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct ArtifactsBuild {
pub name: String,
pub artifact_type: ArtifactType,
pub build_profile: BuildProfile,
pub triple_target: Option<String>,
}

fn inner_build_artifact(c: &ArtifactsBuild) -> anyhow::Result<PathBuf> {
let mut build_cmd = process::Command::new(env!("CARGO"));
build_cmd.arg("build");
if let BuildProfile::Release = c.build_profile {
build_cmd.arg("--release");
}
match c.artifact_type {
ArtifactType::ExecutablePackage | ArtifactType::CDylib => build_cmd.arg("-p"),
ArtifactType::Bin => build_cmd.arg("--bin"),
};
build_cmd.arg(&c.name);

let output = build_cmd.output().unwrap();
if !output.status.success() {
anyhow::bail!(
"Cargo build failed: status code {:?}\nstderr:\n {}",
output.status.code(),
String::from_utf8_lossy(&output.stderr)
);
}

/// This static variable contains the path in which cargo puts it's build artifacts
/// This relies on the assumption that the current binary is assumed to not have been moved from it's directory
static ARTIFACT_DIR: OnceCell<PathBuf> = OnceCell::new();
let artifact_dir = ARTIFACT_DIR.get_or_init(|| {
let test_bin_location = PathBuf::from(env::args().next().unwrap());
let mut location_components = test_bin_location.components().rev().peekable();
loop {
let Some(c) = location_components.peek() else {
break;
};
if c.as_os_str() == "target" {
break;
}
location_components.next();
}
location_components.rev().collect::<PathBuf>()
});

let mut artifact_path = artifact_dir.clone();
artifact_path.push(match c.build_profile {
BuildProfile::Debug => "debug",
BuildProfile::Release => "release",
});

match c.artifact_type {
ArtifactType::ExecutablePackage | ArtifactType::Bin => artifact_path.push(&c.name),
ArtifactType::CDylib => {
let name = "lib".to_owned()
+ &c.name.replace('-', "_")
+ "."
+ shared_lib_extension(
c.triple_target
.as_deref()
.unwrap_or(current_platform::CURRENT_PLATFORM),
)?;
artifact_path.push(name);
}
};
Ok(artifact_path)
}

/// Caches and returns the path of the artifacts built by cargo
/// This function should only be called from cargo tests
pub fn build_artifacts<'b>(
crates: &[&'b ArtifactsBuild],
) -> anyhow::Result<HashMap<&'b ArtifactsBuild, PathBuf>> {
static ARTIFACTS: OnceCell<Mutex<HashMap<ArtifactsBuild, PathBuf>>> = OnceCell::new();

let mut res = HashMap::new();

let artifacts = ARTIFACTS.get_or_init(|| Mutex::new(HashMap::new()));
for &c in crates {
let mut artifacts = artifacts.lock().unwrap();
let artifacts = artifacts.deref_mut();

if artifacts.contains_key(c) {
res.insert(c, artifacts.get(c).unwrap().clone());
} else {
let p = inner_build_artifact(c)?;
res.insert(c, p.clone());
artifacts.insert(c.clone(), p);
}
}

Ok(res)
}

fn shared_lib_extension(triple_target: &str) -> anyhow::Result<&'static str> {
let (_arch, rest) = triple_target
.split_once('-')
.ok_or_else(|| anyhow::anyhow!("malformed triple target {}", triple_target))?;
Ok(
if rest.starts_with("unknown-linux") || rest.starts_with("alpine-linux") {
"so"
} else if rest.starts_with("pc-windows") {
"dll"
} else if rest.starts_with("apple-darwin") {
"dylib"
} else {
return Err(anyhow::anyhow!(
"unrecognized triple-target {}",
triple_target
));
},
)
}

#[macro_export]
macro_rules! timeit {
($op_name:literal, $op:block) => {{
let start = std::time::Instant::now();
let res = $op;
let delta = std::time::Instant::now().duration_since(start);
println!(
concat!($op_name, " took {} ms"),
delta.as_secs_f64() * 1000.0
);
res
}};
}
Loading
Loading