Skip to content

Commit

Permalink
Basic version with example
Browse files Browse the repository at this point in the history
This is not a complete solution but has the base form for how the
project is laid out and has an example showing how it can be used in a
project.

To test with telegraf you'll need to use the changes in this PR:
influxdata/telegraf#6024
  • Loading branch information
Timidger committed Jun 20, 2019
1 parent d23c530 commit 1796f57
Show file tree
Hide file tree
Showing 11 changed files with 314 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ Cargo.lock
example/target/
*.so
*.a
src/gen.rs
6 changes: 6 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[submodule "example/telegraf-plugin-go-glue"]
path = example/telegraf-plugin-go-glue
url = https://github.com/StarryInternet/telegraf-plugin-go-glue
[submodule "telegraf-plugin-go-glue"]
path = telegraf-plugin-go-glue
url = https://github.com/StarryInternet/telegraf-plugin-go-glue
20 changes: 20 additions & 0 deletions .rustfmt.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# line length
error_on_line_overflow = true
max_width = 100

# comments
comment_width = 100
wrap_comments = false # rustfmt is broken currently and doesn't softwrap
normalize_comments = true

# commas
trailing_comma = "Never"
match_block_trailing_comma = true

# code
use_try_shorthand = true
binop_separator = "Back"
format_strings = false # rustfmt string handling is horribly busted

# imports
reorder_imports = true
19 changes: 19 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "telegraf-plugin"
version = "0.1.0"
authors = ["Timidger <pcarpenter@starry.com>"]
edition = "2018"
build = "build.rs"

[lib]
proc-macro = true

[build-dependencies]
bindgen = "0.49.*"

[dependencies]
chrono = "0.4"
libc = "0.2"
syn = { version = "0.15", features = ["full", "fold"] }
quote = "0.6"
proc-macro2 = "0.4"
15 changes: 15 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright 2019. Starry, Inc. All Rights Reserved.
fn main() {
let builder = bindgen::builder()
.derive_debug(true)
.derive_default(true)
.derive_copy(true)
.generate_comments(true)
.blacklist_function("sample_config")
.blacklist_function("description")
.blacklist_function("gather")
.header("telegraf-plugin-go-glue/c_api.h")
.ctypes_prefix("libc");
let generated = builder.generate().unwrap();
generated.write_to_file("src/gen.rs").unwrap();
}
12 changes: 12 additions & 0 deletions example/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "telegraf-plugin-example"
version = "0.1.0"
authors = ["Timidger <pcarpenter@starry.com>"]
edition = "2018"

[lib]
crate_type = ["staticlib"]

[dependencies]
libc = "0.2"
telegraf-plugin = { path = "../" }
41 changes: 41 additions & 0 deletions example/makefile-template.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
[tasks.rust_build]
command = "cargo"
args = ["build"]

[tasks.copy_go_files]
script = [
'''
cp target/debug/libtelegraf_plugin_example.a ./plugin.a
cp telegraf-plugin-go-glue/lib.go ./
cp telegraf-plugin-go-glue/c_api.h ./
'''
]

[tasks.go_build]
command = "go"
args = ["build", "-buildmode=plugin", "-o=plugin.so"]
dependencies = ["rust_build", "copy_go_files"]

[tasks.cleanup]
script = [
'''
rm plugin.a
rm lib.go
rm c_api.h
if [ -f plugin.so ]; then
chmod a+x plugin.so
fi
'''
]

# This must run after the Rust build,
# so that it can link the Rust artifact to it.
[tasks.build]
dependencies = [
"rust_build",
"go_build",
"cleanup"
]

[config]
on_error_task = "cleanup"
25 changes: 25 additions & 0 deletions example/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright 2019. Starry, Inc. All Rights Reserved.
use telegraf_plugin::link_to_go;

macro_rules! map(
{ $($key:expr => $value:expr),* } => {
{
#[allow(unused_mut)]
let mut m = ::std::collections::HashMap::new();
$(
m.insert($key, $value);
)*
m
}
};
);

#[link_to_go("Description of the plugin", "<sample_config>")]
fn collect_metric() {
AddField(
"rust-metric".into(),
map! {"rust_key".into() => "rust_value".into()},
map! {"rust_field".into() => 100000000.into()},
None
)
}
1 change: 1 addition & 0 deletions example/telegraf-plugin-go-glue
173 changes: 173 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
// Copyright 2019. Starry, Inc. All Rights Reserved.
#![recursion_limit = "264"]

extern crate proc_macro;

use std::ffi::CString;

use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::quote;
use syn::{
parse::{self, Parse, ParseStream},
parse_macro_input, parse_quote,
punctuated::Punctuated,
Expr, ExprBlock, ItemFn, LitByteStr, LitStr, Token
};

#[allow(
non_camel_case_types,
non_snake_case,
non_upper_case_globals,
unused_variables,
unused_imports,
dead_code
)]

struct Args {
description: CString,
sample_config: CString
}

impl Parse for Args {
fn parse(input: ParseStream) -> parse::Result<Self> {
let mut vars = Punctuated::<LitStr, Token![,]>::parse_terminated(input)?.into_iter();
let description = vars
.next()
.map(|v| CString::new(v.value()).expect("Invalid CString"))
.expect("Need two arguments: description and sample_config");
let sample_config = vars
.next()
.as_ref()
.map(|v| CString::new(v.value()).expect("Invalid CString"))
.expect("Need two arguments: description and sample_config");
Ok(Args {
description,
sample_config
})
}
}

#[proc_macro_attribute]
pub fn link_to_go(args: TokenStream, input: TokenStream) -> TokenStream {
let fn_gather_user = parse_macro_input!(input as ItemFn);
let args = parse_macro_input!(args as Args);

let description_string =
LitByteStr::new(args.description.as_bytes_with_nul(), Span::call_site());
let fn_description: ItemFn = parse_quote! {
#[no_mangle]
unsafe extern "C" fn description() -> *const libc::c_char {
#description_string as *const _ as *const libc::c_char
}
};

let sample_config_string =
LitByteStr::new(args.sample_config.as_bytes_with_nul(), Span::call_site());
let fn_sample_config: ItemFn = parse_quote! {
#[no_mangle]
unsafe extern "C" fn sample_config() -> *const libc::c_char {
#sample_config_string as *const _ as *const libc::c_char
}
};

let call_user_code = Expr::Block(ExprBlock {
attrs: vec![],
label: None,
block: (*fn_gather_user.block).clone()
});
let fn_gather: ItemFn = parse_quote! {
#[no_mangle]
extern "C" fn gather() {
#call_user_code
}
};

// TODO(timidger): This should all be generated automatically
let telegraf_api_decl: proc_macro2::TokenStream = parse_quote! {
#[repr(C)]
enum Type {
TYPE_INT = 0
}

#[repr(C)]
union Value {
int_: libc::c_int
}

impl From<i32> for go_value {
fn from(num: i32) -> Self {
go_value {
type_: go_value_TYPE_INT,
value: go_value__bindgen_ty_2 {
int_: i32::from(num)
}
}
}
}

fn AddField(
management: String,
tags: std::collections::HashMap<String, String>,
fields: std::collections::HashMap<String, go_value>,
timestamp: Option<std::time::SystemTime>
) {
let management = std::ffi::CString::new(management).unwrap();

let tags = tags
.into_iter()
.map(|(k, v)| {
(
std::ffi::CString::new(k).expect("Not a C string"),
std::ffi::CString::new(v).expect("Not a C string")
)
})
.collect::<std::collections::HashMap<std::ffi::CString, std::ffi::CString>>();
let mut tags_list = Vec::with_capacity(tags.len());
for (key, value) in tags.iter() {
tags_list.push(tag {
key: key.as_ptr() as *mut _,
value: value.as_ptr() as *mut _
})
}

let fields = fields
.into_iter()
.map(|(k, v)| (std::ffi::CString::new(k).expect("Not a C string"), v))
.collect::<std::collections::HashMap<std::ffi::CString, go_value>>();
let mut fields_list = Vec::with_capacity(fields.len());
for (key, &value) in fields.iter() {
fields_list.push(field {
key: key.as_ptr() as *mut _,
value
})
}

let unix_time = timestamp
.map(|timestamp| timestamp.duration_since(std::time::UNIX_EPOCH)
.expect("Time went backwards"))
.unwrap_or_else(|| std::time::Duration::new(0, 0));

unsafe {
add_field(
management.as_ptr() as *mut _,
tags_list.as_mut_ptr(),
tags_list.len() as i32,
fields_list.as_mut_ptr(),
fields_list.len() as i32,
unix_time.as_secs() as i64,
unix_time.as_nanos() as i64
)
}
}
};
let c_prelude: proc_macro2::TokenStream = syn::parse_str(include_str!("gen.rs")).unwrap();

TokenStream::from(quote! {
#c_prelude
#fn_gather
#telegraf_api_decl
#fn_description
#fn_sample_config
})
}
1 change: 1 addition & 0 deletions telegraf-plugin-go-glue

0 comments on commit 1796f57

Please sign in to comment.