Skip to content

Commit f58291b

Browse files
authored
Add support for class constructor instrumentation (#3)
1 parent b461e7a commit f58291b

File tree

12 files changed

+180
-9
lines changed

12 files changed

+180
-9
lines changed

src/function_query.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ impl FunctionKind {
6565

6666
pub struct FunctionQuery {
6767
pub name: String,
68+
pub class: Option<String>,
6869
pub typ: FunctionType,
6970
pub kind: FunctionKind,
7071
pub index: usize,
@@ -113,10 +114,14 @@ impl TryFrom<&Yaml> for FunctionQuery {
113114
let typ = get_str!(query, "type");
114115
let kind = get_str!(query, "kind");
115116
let name = get_str!(query, "name");
117+
let class = query["class"]
118+
.as_str()
119+
.map(std::string::ToString::to_string);
116120
let index: usize = query["index"].as_i64().unwrap_or(0).try_into().unwrap_or(0);
117121

118122
Ok(FunctionQuery {
119123
name: name.to_string(),
124+
class,
120125
typ: FunctionType::from_str(typ).ok_or(format!(
121126
"Invalid config: 'type' must be one of 'decl', 'expr', or 'method', got '{typ}'"
122127
))?,

src/instrumentation.rs

Lines changed: 83 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ use std::path::PathBuf;
33
use swc_core::common::{Span, SyntaxContext};
44
use swc_core::ecma::{
55
ast::{
6-
ArrowExpr, AssignExpr, AssignTarget, BlockStmt, ClassMethod, Expr, FnDecl, FnExpr, Ident,
7-
Lit, MemberProp, MethodProp, Module, ModuleItem, Pat, PropName, Script, SimpleAssignTarget,
8-
Stmt, Str, VarDecl,
6+
ArrowExpr, AssignExpr, AssignTarget, BlockStmt, ClassDecl, ClassMethod, Constructor, Expr,
7+
FnDecl, FnExpr, Ident, Lit, MemberProp, MethodProp, Module, ModuleItem, Pat, PropName,
8+
Script, SimpleAssignTarget, Stmt, Str, VarDecl,
99
},
1010
atoms::Atom,
1111
};
@@ -26,11 +26,16 @@ macro_rules! ident {
2626
pub struct Instrumentation {
2727
config: InstrumentationConfig,
2828
count: usize,
29+
is_correct_class: bool,
2930
}
3031

3132
impl Instrumentation {
3233
pub(crate) fn new(config: InstrumentationConfig) -> Self {
33-
Self { config, count: 0 }
34+
Self {
35+
config,
36+
count: 0,
37+
is_correct_class: false,
38+
}
3439
}
3540

3641
fn new_fn(&self, body: BlockStmt) -> ArrowExpr {
@@ -97,6 +102,49 @@ impl Instrumentation {
97102
];
98103
}
99104

105+
fn insert_constructor_tracing(&self, body: &mut BlockStmt) {
106+
let original_stmts = std::mem::take(&mut body.stmts);
107+
108+
let ch_ident = ident!(format!("tr_ch_apm${}", &self.config.channel_name));
109+
let ctx_ident = ident!(format!("tr_ch_apm_ctx${}", &self.config.channel_name));
110+
let mut try_catch = quote!(
111+
"try {
112+
if ($ch.hasSubscribers) {
113+
$ch.start.publish($ctx);
114+
}
115+
} catch (tr_ch_err) {
116+
if ($ch.hasSubscribers) {
117+
$ctx.error = tr_ch_err;
118+
try {
119+
$ctx.self = this;
120+
} catch (refErr) {
121+
// This can only error out if super hasn't been called yet.
122+
// Safe to ignore, but note that self/this won't get into the context.
123+
}
124+
$ch.error.publish($ctx);
125+
}
126+
throw tr_ch_err;
127+
} finally {
128+
if ($ch.hasSubscribers) {
129+
$ctx.self = this;
130+
$ch.end.publish($ctx);
131+
}
132+
}" as Stmt,
133+
ch = ch_ident,
134+
ctx = ctx_ident.clone(),
135+
);
136+
if let Some(try_catch_stmt) = try_catch.as_mut_try_stmt() {
137+
for stmt in &original_stmts {
138+
try_catch_stmt.block.stmts.push(stmt.clone());
139+
}
140+
}
141+
142+
body.stmts = vec![
143+
quote!("const $ctx = { arguments };" as Stmt, ctx = ctx_ident,),
144+
try_catch,
145+
];
146+
}
147+
100148
fn trace_expr_or_count(&mut self, func_expr: &mut FnExpr, name: &Atom) -> bool {
101149
if self
102150
.config
@@ -163,11 +211,27 @@ impl Instrumentation {
163211
!traced
164212
}
165213

214+
pub fn visit_mut_class_decl(&mut self, node: &mut ClassDecl) -> bool {
215+
self.is_correct_class = self
216+
.config
217+
.function_query
218+
.class
219+
.as_ref()
220+
.map_or(true, |class| node.ident.sym.as_ref() == class);
221+
true
222+
}
223+
166224
pub fn visit_mut_class_method(&mut self, node: &mut ClassMethod) -> bool {
167225
let name = match &node.key {
168226
PropName::Ident(ident) => ident.sym.clone(),
169227
_ => return false,
170228
};
229+
230+
// Only increment count when class matches
231+
if !self.is_correct_class {
232+
return true;
233+
}
234+
171235
if self
172236
.config
173237
.function_query
@@ -178,6 +242,21 @@ impl Instrumentation {
178242
self.insert_tracing(body);
179243
}
180244
}
245+
true
246+
}
247+
248+
pub fn visit_mut_constructor(&mut self, node: &mut Constructor) -> bool {
249+
if !self.is_correct_class || self.config.function_query.name != "constructor" {
250+
return false;
251+
}
252+
253+
if self.count == self.config.function_query.index && node.body.is_some() {
254+
if let Some(body) = node.body.as_mut() {
255+
self.insert_constructor_tracing(body);
256+
}
257+
} else {
258+
self.count += 1;
259+
}
181260
false
182261
}
183262

src/lib.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@ use std::path::PathBuf;
1818
use std::str::FromStr;
1919

2020
use swc_core::ecma::{
21-
ast::{AssignExpr, ClassMethod, FnDecl, MethodProp, Module, Script, Str, VarDecl},
21+
ast::{
22+
AssignExpr, ClassDecl, ClassMethod, Constructor, FnDecl, MethodProp, Module, Script, Str,
23+
VarDecl,
24+
},
2225
visit::{VisitMut, VisitMutWith},
2326
};
2427
use swc_core::quote;
@@ -142,7 +145,9 @@ impl VisitMut for InstrumentationVisitor<'_> {
142145

143146
visit_with_all_fn!(visit_mut_fn_decl, FnDecl);
144147
visit_with_all_fn!(visit_mut_var_decl, VarDecl);
145-
visit_with_all_fn!(visit_mut_class_method, ClassMethod);
146148
visit_with_all_fn!(visit_mut_method_prop, MethodProp);
147149
visit_with_all_fn!(visit_mut_assign_expr, AssignExpr);
150+
visit_with_all_fn!(visit_mut_class_decl, ClassDecl);
151+
visit_with_all_fn!(visit_mut_class_method, ClassMethod);
152+
visit_with_all_fn!(visit_mut_constructor, Constructor);
148153
}

tests/class_method_cjs/instrumentations.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ instrumentations:
44
version_range: ">=0.0.1"
55
file_path: index.mjs
66
function_query:
7+
class: Undici
78
name: fetch
89
type: method
910
kind: async
10-
index: 0
1111
operator: tracePromise
1212
channel_name: Undici_fetch

tests/class_method_cjs/mod.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1-
class Undici {
2-
async fetch (lmao) {
1+
class UndiciBase {
2+
async fetch (url) {
33
return 42;
44
}
55
}
6+
class Undici extends UndiciBase {
7+
async fetch (url) {
8+
return super.fetch(url);
9+
}
10+
}
611

712
module.exports = Undici;
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
version: 1
2+
instrumentations:
3+
- module_name: undici
4+
version_range: ">=0.0.1"
5+
file_path: index.mjs
6+
function_query:
7+
name: constructor
8+
type: method
9+
kind: sync
10+
class: Undici
11+
operator: traceSync
12+
channel_name: Undici_constructor

tests/constructor_cjs/mod.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
class UndiciBase {
2+
constructor () {
3+
}
4+
}
5+
6+
class Undici extends UndiciBase {
7+
constructor (val) {
8+
super();
9+
this.val = val;
10+
}
11+
}
12+
13+
module.exports = Undici;

tests/constructor_cjs/test.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
const Undici = require('./instrumented.js');
2+
const { assert, getContext } = require('../common/preamble.js');
3+
const context = getContext('orchestrion:undici:Undici_constructor');
4+
(() => {
5+
const undici = new Undici(42);
6+
assert.deepEqual(undici.val, 42);
7+
assert.deepStrictEqual(context, {
8+
start: true,
9+
end: true
10+
});
11+
})();
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
version: 1
2+
instrumentations:
3+
- module_name: undici
4+
version_range: ">=0.0.1"
5+
file_path: index.mjs
6+
function_query:
7+
name: constructor
8+
type: method
9+
kind: sync
10+
class: Undici
11+
operator: traceSync
12+
channel_name: Undici_constructor

tests/constructor_mjs/mod.mjs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
class UndiciBase {
2+
constructor() {
3+
}
4+
}
5+
6+
class Undici extends UndiciBase {
7+
constructor(val) {
8+
super();
9+
this.val = val;
10+
}
11+
}
12+
13+
export { Undici };

0 commit comments

Comments
 (0)