diff --git a/appmap.yml b/appmap.yml index bad68b4..d10c3ac 100644 --- a/appmap.yml +++ b/appmap.yml @@ -1,3 +1,3 @@ -appmap_dir: maps/tmp/appmap +appmap_dir: maps/tmp/ language: rust name: appmap_tracing_test diff --git a/src/appmap_definition.rs b/src/appmap_definition.rs index 9fd7bd0..bc41345 100644 --- a/src/appmap_definition.rs +++ b/src/appmap_definition.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use std::path::PathBuf; use serde::{Deserialize, Serialize}; +use tracing::{info, instrument}; pub use event_id::EventId; @@ -29,12 +30,17 @@ pub struct MessageCallObject {} #[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] pub struct AppMapObject { pub version: String, - pub metadata: MetadataObject, + + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + pub metadata: Option, // pub class_map: Vec, + #[serde(rename = "classMap")] pub class_map: Vec, pub events: Vec, #[serde(skip_serializing_if = "Option::is_none")] #[serde(default)] + #[serde(rename = "eventUpdates")] pub event_updates: Option>, } //region events @@ -114,10 +120,11 @@ pub struct CallObject { #[serde(default)] pub parameters: Option>, ///Required flag if the method is class-scoped (static) or instance-scoped. Must be true or false. Example: true. + #[serde(rename = "static")] pub is_static: bool, #[serde(flatten)] - pub data: CallObjectType, + pub type_: CallObjectType, } #[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] @@ -125,7 +132,7 @@ pub struct CallObject { #[serde(rename_all = "camelCase")] pub enum CallObjectType { Normal, - Function(FunctionCallObject), + Function, HttpServerRequest(HttpServerRequestCallObject), HttpServerResponse(HttpServerResponseCallObject), HttpClientRequest(HttpClientRequestCallObject), @@ -133,32 +140,6 @@ pub enum CallObjectType { SqlQuery(SqlQueryCallObject), Message(MessageCallObject), } -#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] -/// Note: -/// -/// In order to correlate function call events with function objects defined in the class map, the -/// path and lineno attributes of each "call" event should exactly match the location attribute of -/// the corresponding function in the classMap. -pub struct FunctionCallObject { - /// Recommended path name of the file which triggered the event. - /// Example: "/src/architecture/lib/appland/local/client.rb". - #[serde(skip_serializing_if = "Option::is_none")] - #[serde(default)] - pub path: Option, - /// Recommended line number which triggered the event. Example: 5. - #[serde(skip_serializing_if = "Option::is_none")] - #[serde(default)] - pub lineno: Option, - /// Optional parameter object describing the object on which the function is called. Corresponds - /// to the receiver, self and this concept found in various programming languages. - #[serde(skip_serializing_if = "Option::is_none")] - #[serde(default)] - pub receiver: Option, - /// Recommended array of parameter objects describing the function call parameters. - #[serde(skip_serializing_if = "Option::is_none")] - #[serde(default)] - pub parameters: Option>, -} //endregion #[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] @@ -236,6 +217,7 @@ pub struct FunctionCodeObject { #[serde(default)] pub location: Option, ///Required flag if the method is class-scoped (static) or instance-scoped. Must be true or false. Example: true. + #[serde(rename = "static")] pub is_static: bool, ///Optional list of arbitrary labels describing the function. #[serde(skip_serializing_if = "Option::is_none")] @@ -252,3 +234,8 @@ pub struct FunctionCodeObject { } //endregion + +#[instrument] +pub fn test_sub_mod() { + info!("test message from test_sub_mod"); +} diff --git a/src/appmap_definition/event_id.rs b/src/appmap_definition/event_id.rs index ffd314c..32a0c4a 100644 --- a/src/appmap_definition/event_id.rs +++ b/src/appmap_definition/event_id.rs @@ -3,9 +3,9 @@ use std::ops::{Deref, DerefMut}; use serde::{Deserialize, Serialize}; #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Serialize, Deserialize)] -pub struct EventId(u32); +pub struct EventId(u64); impl Deref for EventId { - type Target = u32; + type Target = u64; fn deref(&self) -> &Self::Target { &self.0 @@ -13,12 +13,12 @@ impl Deref for EventId { } impl DerefMut for EventId { fn deref_mut(&mut self) -> &mut Self::Target { - self + &mut self.0 } } -impl From for EventId { - fn from(value: u32) -> Self { +impl From for EventId { + fn from(value: u64) -> Self { Self(value) } } @@ -34,6 +34,6 @@ impl Deref for ObjectId { } impl DerefMut for ObjectId { fn deref_mut(&mut self) -> &mut Self::Target { - self + &mut self.0 } } diff --git a/src/extensions.rs b/src/extensions.rs new file mode 100644 index 0000000..6c30d5c --- /dev/null +++ b/src/extensions.rs @@ -0,0 +1,25 @@ +pub trait OptionVecExtensions { + fn push_or_create(&mut self, value: T); + fn push_or_create_and_get_mut(&mut self, value: T) -> &mut T; + fn push_or_create_and_get(&mut self, value: T) -> &T; +} +impl OptionVecExtensions for Option> { + fn push_or_create(&mut self, value: T) { + if let Some(children) = self { + children.push(value); + } else { + *self = Some(vec![value]); + } + } + fn push_or_create_and_get_mut(&mut self, value: T) -> &mut T { + self.push_or_create(value); + let vec = self.as_mut().expect("We just created it"); + let i = vec.len(); + vec.get_mut(i).expect("This can not be empty") + } + fn push_or_create_and_get(&mut self, value: T) -> &T { + self.push_or_create(value); + let vec = self.as_ref().expect("We just created it"); + vec.get(vec.len()).expect("This can not be empty") + } +} diff --git a/src/lib.rs b/src/lib.rs index a72b09f..c6de87a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,16 +1,254 @@ -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::fmt; +use std::error::Error; use std::fmt::Debug; -use std::time::Instant; -use tracing::field::{Field, Visit}; -use tracing::span::{Attributes, Record}; -use tracing::{Id, Subscriber}; +use std::fs::File; +use std::io::Write; +use std::path::{Path, PathBuf}; +use std::sync::Mutex; + +use serde::{Deserialize, Serialize}; +use tracing::{Event, Id, Subscriber}; use tracing_subscriber::layer::Context; use tracing_subscriber::Layer; + +use crate::appmap_definition::*; +use crate::extensions::OptionVecExtensions; +use crate::node_functions::*; + pub mod appmap_definition; #[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] pub struct AppMap { #[serde(flatten)] - pub data: crate::appmap_definition::AppMapObject, + pub data: AppMapObject, + #[serde(skip)] + next_event_id: u64, } + +#[derive(Debug)] +pub struct AppMapLayer { + pub test: Mutex, +} + +impl AppMapLayer { + pub fn new() -> Self { + Self { + test: Mutex::new(AppMap::new()), + } + } +} +impl AppMap { + pub fn new() -> Self { + Self { + data: AppMapObject { + version: "1.12".to_string(), + metadata: None, + class_map: vec![], + events: vec![], + event_updates: None, + }, + next_event_id: 1, + } + } + pub fn get_next_event_id(&mut self) -> u64 { + let x = self.next_event_id; + self.next_event_id += 1; + x + } + + pub fn add_function_call_event( + &mut self, + thread_id: u32, + class: String, + method: String, + path: Option, + lineno: Option, + is_static: bool, + ) { + let id = self.get_next_event_id(); + // let class_name = class.clone(); + // let class_name = class_name.rsplit_once("::").unwrap_or(("", &class)).1; + self.data.events.push(EventObject { + id: EventId::from(id), + thread_id, + event: EventObjectType::Call(CallObject { + defined_class: class.to_string(), + method_id: method.clone(), + path: path.clone(), + lineno, + receiver: None, + parameters: None, + is_static, + type_: CallObjectType::Function, + }), + }); + let existing_node = self.find_in_class_map(&class, &method); + if existing_node.is_none() { + println!("node not found: {} ; {}", class, method); + self.add_func_to_hierarchy( + class, + method, + path.map(|x| x.to_str().map(|x| format!("{}:{}", x, lineno.unwrap_or(0)))) + .flatten(), + ); + } else { + println!( + "node already existing: {} ; {} => {:?}", + class, method, existing_node + ); + } + } + fn add_func_to_hierarchy(&mut self, class: String, method: String, path: Option) { + let func = FunctionCodeObject { + name: method, + location: path, + is_static: true, + labels: None, + comment: None, + source: None, + }; + let func = CodeObjectType::Function(func); + + let class_node = self.find_class_in_class_map_mut(&class); + if let Some(class_node) = class_node { + class_node.children.push_or_create(func); + } else { + self.add_class_to_hierarchy(&class); + let class_node = self + .find_class_in_class_map_mut(&class) + .expect("We just created this node. It can not be None"); + class_node.children.push_or_create(func); + } + } + + fn add_class_to_hierarchy(&mut self, class: &str) { + println!("class_map: {:?}", self.data.class_map); + let class_parts = class.split_once("::"); + if let Some((base, name)) = class_parts { + //class is a subclass. Check if the parent of the class exists already + let mut parent_class = self.find_class_in_class_map_mut(base); + if parent_class.is_none() { + //parent did not exist. Create it! + self.add_class_to_hierarchy(base); + parent_class = self.find_class_in_class_map_mut(base); + } + let class_node = CodeObjectType::Class(ClassCodeObject { + name: name.to_string(), + children: None, + }); + parent_class + .expect("Could not find or create the parent class") + .children + .push_or_create(class_node); + println!( + "added sub class: {} under {} => {:?}", + name, base, self.data.class_map + ); + return; + } + //could not split so the class should be a top level class + println!("got add request for top level class: {}", class); + + let top_level_class = self.find_class_in_class_map_mut(class); + if top_level_class.is_some() { + return; + } else { + let class_node = CodeObjectType::Class(ClassCodeObject { + name: class.to_string(), + children: None, + }); + + let classes: &mut Vec<_> = &mut self.data.class_map; + classes.push(class_node); + println!("Added top level class: {} => {:?}", class, classes); + } + } + + fn find_class_in_class_map(&self, class: &str) -> Option<&ClassCodeObject> { + for node in self.data.class_map.iter() { + let class_node = find_class_in_tree(node, class); + if class_node.is_some() { + return class_node; + } + } + None + } + fn find_in_class_map(&self, class: &str, method: &str) -> Option<&ClassCodeObject> { + for node in self.data.class_map.iter() { + let class_node = find_class_in_tree(node, class); + if let Some(class_node) = class_node { + if class_node.name == method { + return Some(class_node); + } + } + } + None + } + fn find_class_in_class_map_mut(&mut self, class: &str) -> Option<&mut ClassCodeObject> { + for node in self.data.class_map.iter_mut() { + let class_node = find_class_in_tree_mut(node, class); + if class_node.is_some() { + return class_node; + } + } + None + } + fn find_in_class_map_mut(&mut self, class: &str, method: &str) -> Option<&mut CodeObjectType> { + for node in self.data.class_map.iter_mut() { + let class_node = find_class_in_tree_mut(node, class); + if let Some(class_node) = class_node { + if let Some(children) = class_node.children.as_mut() { + for child in children.iter_mut() { + let result = is_node_the_searched_function_mut(child, method); + if result.is_some() { + return result; + } + } + } + } + } + None + } + pub fn write_to_file(&self) -> Result<(), Box> { + let s = serde_json::to_string_pretty(self)?; + let mut file = File::options() + .create(true) + .write(true) + .truncate(true) + .open(Path::new("maps/tmp/test_1.appmap.json"))?; + file.write_all(s.as_bytes())?; + + Ok(()) + } +} + +impl tracing_subscriber::registry::LookupSpan<'lookup>> + Layer for AppMapLayer +{ + fn on_event(&self, event: &Event<'_>, ctx: Context<'_, S>) { + println!("event: {:?}; ctx: {:?}", event, ctx); + } + fn on_enter(&self, id: &Id, ctx: Context<'_, S>) { + println!("on_enter=> id: {:?}; ctx: {:?}", id, ctx); + let metadata = ctx.metadata(id); + if let Some(metadata) = metadata { + let parameters = metadata.fields(); + println!("parameters: {:?}", parameters); + let x = metadata.module_path().unwrap() == metadata.target(); + println!("some test data: {:?}", x); + + self.test.lock().unwrap().add_function_call_event( + 9999, + metadata.target().to_string(), + metadata.name().to_string(), + metadata.file().map(|f| PathBuf::from(f)), + metadata.line().map(|x| x as usize), + true, + ); + self.test.lock().unwrap().write_to_file().unwrap(); + } + } + fn on_close(&self, id: Id, ctx: Context<'_, S>) { + println!("on_close=> id: {:?}; ctx: {:?}", id, ctx); + } +} +mod extensions; +mod node_functions; diff --git a/src/main.rs b/src/main.rs index ff7530c..52e3222 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,78 +1,94 @@ -use std::collections::HashMap; use std::error::Error; use std::fmt::Error as FmtError; use std::path::PathBuf; +use tracing::{info, instrument}; -use appmap_tracing_test::appmap_definition::{ - AppMapObject, CallObject, CallObjectType, ClassCodeObject, CodeObject, CodeObjectType, EventId, - EventObject, EventObjectType, FunctionCallObject, FunctionCodeObject, MetadataObject, - PackageCodeObject, -}; -use appmap_tracing_test::AppMap; +use tracing_subscriber::layer::SubscriberExt; +use tracing_subscriber::Registry; + +use appmap_tracing_test::appmap_definition::*; +use appmap_tracing_test::*; pub fn main() -> Result<(), Box> { + init_tracing(); + sample_json()?; + test_sub_mod(); Ok(()) } + +fn init_tracing() { + // let stdout_layer = tracing_subscriber::fmt::layer().pretty(); + let app_layer = AppMapLayer::new(); + + let subscriber = Registry::default() + // + // .with(stdout_layer) + // + .with(app_layer); + + tracing::subscriber::set_global_default(subscriber).expect("Unable to set global subscriber"); +} + +//region AppMapObject +#[instrument] fn sample_json() -> Result<(), Box> { - let data = AppMap { - data: AppMapObject { - metadata: MetadataObject {}, - class_map: vec![CodeObjectType::Package(PackageCodeObject { - name: "main pkg".to_string(), - children: Some(vec![CodeObjectType::Class(ClassCodeObject { - name: "main cls".to_string(), - children: Some(vec![CodeObjectType::Function(FunctionCodeObject { - name: "sample_json".to_string(), - location: Some(format!( - "{}:{}", - PathBuf::from("src/main.rs").to_str().ok_or(FmtError)?, - 14 - )), - is_static: true, - labels: Some(vec!["security".to_string()]), - comment: None, - source: None, - })]), - })]), - })], - events: vec![EventObject { - id: EventId::from(1), - thread_id: 9999, - event: EventObjectType::Call(CallObject { - defined_class: "main".to_string(), - method_id: "sample_json".to_string(), - path: Some(PathBuf::from("src/main.rs")), - lineno: Some(14), - receiver: None, - parameters: None, + info!("creating sample object"); + let data = AppMapObject { + metadata: None, + class_map: vec![CodeObjectType::Package(PackageCodeObject { + name: "main pkg".to_string(), + children: Some(vec![CodeObjectType::Class(ClassCodeObject { + name: "main cls".to_string(), + children: Some(vec![CodeObjectType::Function(FunctionCodeObject { + name: "sample_json".to_string(), + location: Some(format!( + "{}:{}", + PathBuf::from("src/main.rs").to_str().ok_or(FmtError)?, + 14 + )), is_static: true, - data: CallObjectType::Function(FunctionCallObject { - path: None, - lineno: None, - receiver: None, - parameters: None, - }), - }), - }], - version: String::from("1.12"), - event_updates: None, - }, + labels: Some(vec!["security".to_string()]), + comment: None, + source: None, + })]), + })]), + })], + events: vec![EventObject { + id: EventId::from(1), + thread_id: 9999, + event: EventObjectType::Call(CallObject { + defined_class: "main".to_string(), + method_id: "sample_json".to_string(), + path: Some(PathBuf::from("src/main.rs")), + lineno: Some(14), + receiver: None, + parameters: None, + is_static: true, + type_: CallObjectType::Function, + }), + }], + version: String::from("1.12"), + event_updates: None, }; - println!("data debug: {:?}", data); + info!("data debug: {:?}", data); let data_string = serde_json::to_string_pretty(&data)?; - println!("data to string: {}", data_string); + info!("data to string: {}", data_string); + // println!("data to string: {}", data_string); - let data_reversed: AppMap = sample_from_str(&data_string)?; - println!("data_reversed debug: {:?}", data_reversed); + let data_reversed: AppMapObject = sample_from_str(&data_string)?; + info!("data_reversed debug: {:?}", data_reversed); assert_eq!(data, data_reversed); - println!("it works!"); + info!("it works!"); + // println!("it works!"); Ok(()) } -fn sample_from_str(s: &str) -> Result> { - let data_reversed: AppMap = serde_json::from_str(s)?; - println!("sample_from_str(s): result debug: {:?}", data_reversed); +#[instrument] +fn sample_from_str(s: &str) -> Result> { + let data_reversed: AppMapObject = serde_json::from_str(s)?; + info!("sample_from_str(s): result debug: {:?}", data_reversed); Ok(data_reversed) } +//endregion diff --git a/src/node_functions/mod.rs b/src/node_functions/mod.rs new file mode 100644 index 0000000..87d54c2 --- /dev/null +++ b/src/node_functions/mod.rs @@ -0,0 +1,85 @@ +use crate::appmap_definition::*; + +pub fn find_class_in_tree<'a>( + node: &'a CodeObjectType, + class: &str, +) -> Option<&'a ClassCodeObject> { + let children = match node { + CodeObjectType::Class(c) => { + if class.ends_with(&c.name) { + return Some(c); + } + if class.starts_with(&c.name) { + c.children.as_ref() + } else { + None + } + } + _ => None, + }; + if let Some(children) = children { + for x in children.iter() { + let child_found = find_class_in_tree(x, class); + if child_found.is_some() { + return child_found; + } + } + } + return None; +} +pub fn is_node_the_searched_function<'a>( + node: &'a CodeObjectType, + method: &str, +) -> Option<&'a CodeObjectType> { + match node { + CodeObjectType::Function(f) => { + if f.name == method { + return Some(node); + } + None + } + _ => None, + } +} +pub fn find_class_in_tree_mut<'a>( + node: &'a mut CodeObjectType, + class: &str, +) -> Option<&'a mut ClassCodeObject> { + println!("trying to find class: {} in node: {:?}", class, node); + let children = match node { + CodeObjectType::Class(c) => { + if class.ends_with(&c.name) { + return Some(c); + } + if class.starts_with(&c.name) { + c.children.as_mut() + } else { + None + } + } + _ => None, + }; + if let Some(children) = children { + for x in children.iter_mut() { + let child_found = find_class_in_tree_mut(x, class); + if child_found.is_some() { + return child_found; + } + } + } + return None; +} +pub fn is_node_the_searched_function_mut<'a>( + node: &'a mut CodeObjectType, + method: &str, +) -> Option<&'a mut CodeObjectType> { + match node { + CodeObjectType::Function(f) => { + if f.name == method { + return Some(node); + } + None + } + _ => None, + } +}