Skip to content
This repository was archived by the owner on Oct 31, 2023. It is now read-only.

Commit 07df63b

Browse files
gagboactualwitch
andauthored
Release 0.2.6 (#37)
* Add Python support (#36) * Update CHANGELOG * chore: Release am_list version 0.2.6 --------- Co-authored-by: あで <[email protected]>
1 parent 1f14f5d commit 07df63b

File tree

12 files changed

+497
-23
lines changed

12 files changed

+497
-23
lines changed

CHANGELOG.md

+8-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
## [Unreleased] - ReleaseDate
1111

12+
## [Version 0.2.6] - 2023-07-27
13+
14+
### Added
15+
16+
- [Python] Support for python language
17+
1218
## [Version 0.2.5] - 2023-07-19
1319

1420
### Added
@@ -81,7 +87,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
8187
### Security
8288

8389
<!-- next-url -->
84-
[Unreleased]: https://github.com/autometrics-dev/am_list/compare/v0.2.5...HEAD
90+
[Unreleased]: https://github.com/autometrics-dev/am_list/compare/v0.2.6...HEAD
91+
[Version 0.2.6]: https://github.com/autometrics-dev/am_list/compare/v0.2.5...v0.2.6
8592
[Version 0.2.5]: https://github.com/autometrics-dev/am_list/compare/v0.2.4...v0.2.5
8693
[Version 0.2.4]: https://github.com/autometrics-dev/am_list/compare/v0.2.3...v0.2.4
8794
[Version 0.2.3]: https://github.com/autometrics-dev/am_list/compare/v0.2.2...v0.2.3

Cargo.lock

+12-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "am_list"
3-
version = "0.2.5"
3+
version = "0.2.6"
44
edition = "2021"
55
repository = "https://github.com/autometrics-dev/am_list"
66
authors = ["Fiberplane <[email protected]>", "Gerry Agbobada <[email protected]>"]
@@ -22,6 +22,7 @@ serde_json = "1.0.96"
2222
thiserror = "1.0.40"
2323
tree-sitter = "0.20.10"
2424
tree-sitter-go = "0.19.1"
25+
tree-sitter-python = "0.20.2"
2526
tree-sitter-rust = "0.20.3"
2627
tree-sitter-typescript = "0.20.2"
2728
walkdir = "2.3.3"

README.md

+25-20
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ VERSION=0.2.0 curl --proto '=https' --tlsv1.2 -LsSf https://github.com/autometri
2020
```
2121

2222
And run the binary
23+
2324
```bash
2425
# Make sure that `~/.cargo/bin` is in your `PATH`
2526
am_list list -l rs /path/to/project/root
@@ -34,20 +35,22 @@ returns the exact same labels as the ones you would need to use in PromQL to
3435
look at the metrics. In a nutshell,
3536
"[Autometrics](https://github.com/autometrics-dev) compliance".
3637

37-
Language | Function name detection | Module detection
38-
:---:|:---:|:---:
39-
[Rust](https://github.com/autometrics-dev/autometrics-rs) | ✅ | ✅
40-
[Typescript](https://github.com/autometrics-dev/autometrics-ts) | ✅ | ⚠️[^wrapper]
41-
[Go](https://github.com/autometrics-dev/autometrics-go) | ⚠️[^all-functions] | ✅
42-
[Python](https://github.com/autometrics-dev/autometrics-py) | ❌ | ❌
43-
[C#](https://github.com/autometrics-dev/autometrics-cs) | ❌ | ❌
38+
| Language | Function name detection | Module detection |
39+
| :-------------------------------------------------------------: | :---------------------: | :--------------: |
40+
| [Rust](https://github.com/autometrics-dev/autometrics-rs) |||
41+
| [Typescript](https://github.com/autometrics-dev/autometrics-ts) | | ⚠️[^wrapper] |
42+
| [Go](https://github.com/autometrics-dev/autometrics-go) | ⚠️[^all-functions] ||
43+
| [Python](https://github.com/autometrics-dev/autometrics-py) |||
44+
| [C#](https://github.com/autometrics-dev/autometrics-cs) |||
4445

45-
[^wrapper]: For Typescript (and all languages where autometrics is a wrapper
46+
[^wrapper]:
47+
For Typescript (and all languages where autometrics is a wrapper
4648
function), static analysis makes it hard to traverse imports to find the
4749
module where an instrumented function is _defined_, so the reported module
4850
is the module where the function has been _instrumented_
4951

50-
[^all-functions]: Support list all autometricized functions, but not all
52+
[^all-functions]:
53+
Support list all autometricized functions, but not all
5154
functions without restriction
5255

5356
### Typescript
@@ -65,42 +68,44 @@ attempt to be useful.
6568
The other difficulty encountered when using a static analysis tool with autometrics-ts is that the
6669
instrumentation can happen anywhere, as the wrapper function call can use an imported symbol as its argument:
6770

68-
``` typescript
69-
import { exec } from 'child_process'
70-
import { autometrics } from '@autometrics/autometrics'
71+
```typescript
72+
import { exec } from "child_process";
73+
import { autometrics } from "@autometrics/autometrics";
7174

72-
const instrumentedExec = autometrics(exec)
75+
const instrumentedExec = autometrics(exec);
7376

7477
// use instrumentedExec everywhere instead of exec
7578
```
7679

7780
In order to report the locus of _function definition_ as the module, we would
7881
need to include both:
82+
7983
- a complete import resolution step, to figure out the origin module of the
8084
instrumented function (`child_process` in the example), and
8185
- a dependency inspection step, to figure out the path to the instrumented
8286
function definition _within_ the dependency (`lib/child_process.js` in the
8387
[node source code](https://github.com/nodejs/node/blob/main/lib/child_process.js))
84-
88+
8589
This is impractical and error-prone to implement these steps accurately, so
8690
instead we only try to detect imports when they are explicitely imported in the
8791
same file, and we will only report the function module as the imported module
8892
(not the path to the file it is defined in). Practically that means that for
8993
this example:
9094

91-
``` typescript
95+
```typescript
9296
// in src/router/index.ts
93-
import { exec } from 'child_process'
94-
import { origRoute as myRoute } from '../handlers'
95-
import { autometrics } from '@autometrics/autometrics'
97+
import { exec } from "child_process";
98+
import { origRoute as myRoute } from "../handlers";
99+
import { autometrics } from "@autometrics/autometrics";
96100

97-
const instrumentedExec = autometrics(exec)
98-
const instrumentedRoute = autometrics(myRoute)
101+
const instrumentedExec = autometrics(exec);
102+
const instrumentedRoute = autometrics(myRoute);
99103

100104
// use instrumentedExec everywhere instead of exec
101105
```
102106

103107
`am_list` will report 2 functions:
108+
104109
- `{"function": "exec", "module": "ext://child_process"}`: using `ext://`
105110
protocol to say the module is non-local
106111
- `{"function": "origRoute", "module": "handlers"}`: even if `myRoute` is
+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
(function_definition
2+
name: (identifier) @func.name)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
(decorated_definition
2+
(decorator
3+
[(identifier) @decorator.name
4+
(call (identifier) @decorator.name)])
5+
(#eq? @decorator.name "{0}")
6+
definition: (function_definition
7+
name: (identifier) @func.name))

runtime/queries/python/import.scm

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
(import_from_statement
2+
module_name: (dotted_name (identifier) @import.module)
3+
(#eq? @import.module "autometrics")
4+
name:[
5+
(dotted_name (identifier) @import.name)
6+
(aliased_import
7+
name: (dotted_name (identifier) @import.name)
8+
alias: (identifier) @import.alias)]
9+
(#eq? @import.name "autometrics"))

src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pub mod go;
2+
pub mod python;
23
pub mod rust;
34
pub mod typescript;
45

src/main.rs

+6
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ enum Language {
4242
Rust,
4343
Go,
4444
Typescript,
45+
Python,
4546
}
4647

4748
impl FromStr for Language {
@@ -61,6 +62,10 @@ impl FromStr for Language {
6162
return Ok(Self::Typescript);
6263
}
6364

65+
if ["python", "py"].contains(&discriminant.as_str()) {
66+
return Ok(Self::Python);
67+
}
68+
6469
Err(format!("Unknown language: {s}"))
6570
}
6671
}
@@ -80,6 +85,7 @@ fn main() -> anyhow::Result<()> {
8085
Language::Rust => Box::new(am_list::rust::Impl {}),
8186
Language::Go => Box::new(am_list::go::Impl {}),
8287
Language::Typescript => Box::new(am_list::typescript::Impl {}),
88+
Language::Python => Box::new(am_list::python::Impl {}),
8389
};
8490

8591
let mut res = if args.all_functions {

src/python.rs

+127
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
mod queries;
2+
3+
use crate::{ExpectedAmLabel, ListAmFunctions, Result};
4+
use queries::{AllFunctionsQuery, AmImportQuery, AmQuery};
5+
use rayon::prelude::*;
6+
use std::{
7+
collections::HashSet,
8+
fs::read_to_string,
9+
path::{Path, MAIN_SEPARATOR},
10+
};
11+
use walkdir::{DirEntry, WalkDir};
12+
13+
/// Implementation of the Python support for listing autometricized functions.
14+
#[derive(Clone, Copy, Debug, Default)]
15+
pub struct Impl {}
16+
17+
impl Impl {
18+
fn is_hidden(entry: &DirEntry) -> bool {
19+
entry
20+
.file_name()
21+
.to_str()
22+
.map(|s| s.starts_with('.'))
23+
.unwrap_or(false)
24+
}
25+
26+
fn is_valid(entry: &DirEntry) -> bool {
27+
if Impl::is_hidden(entry) {
28+
return false;
29+
}
30+
entry.file_type().is_dir()
31+
|| entry
32+
.path()
33+
.extension()
34+
.map_or(false, |ext| ext == "py" || ext == "py3")
35+
}
36+
}
37+
38+
impl ListAmFunctions for Impl {
39+
fn list_autometrics_functions(&mut self, project_root: &Path) -> Result<Vec<ExpectedAmLabel>> {
40+
const PREALLOCATED_ELEMS: usize = 100;
41+
let mut list = HashSet::with_capacity(PREALLOCATED_ELEMS);
42+
let root_name = project_root
43+
.file_name()
44+
.map(|s| s.to_str().unwrap_or_default())
45+
.unwrap_or("");
46+
47+
let walker = WalkDir::new(project_root).into_iter();
48+
let mut source_mod_pairs = Vec::with_capacity(PREALLOCATED_ELEMS);
49+
source_mod_pairs.extend(walker.filter_entry(Self::is_valid).filter_map(|entry| {
50+
let entry = entry.ok()?;
51+
Some(
52+
entry
53+
.path()
54+
.to_str()
55+
.map(ToString::to_string)
56+
.unwrap_or_default(),
57+
)
58+
}));
59+
60+
list.par_extend(source_mod_pairs.par_iter().filter_map(move |path| {
61+
let relative_module_name = Path::new(path)
62+
.strip_prefix(project_root)
63+
.ok()?
64+
.with_extension("")
65+
.to_str()?
66+
.replace(MAIN_SEPARATOR, ".");
67+
let module_name = format!("{}.{}", root_name, relative_module_name);
68+
let source = read_to_string(path).ok()?;
69+
let import_query = AmImportQuery::try_new().ok()?;
70+
let decorator_name = import_query.get_decorator_name(source.as_str()).ok()?;
71+
let query = AmQuery::try_new(decorator_name.as_str()).ok()?;
72+
let names = query
73+
.list_function_names(&source, module_name.as_str())
74+
.unwrap_or_default();
75+
Some(names)
76+
}));
77+
78+
let mut result = Vec::with_capacity(PREALLOCATED_ELEMS);
79+
result.extend(list.into_iter().flatten());
80+
Ok(result)
81+
}
82+
83+
fn list_all_functions(&mut self, project_root: &Path) -> Result<Vec<ExpectedAmLabel>> {
84+
const PREALLOCATED_ELEMS: usize = 100;
85+
let mut list = HashSet::with_capacity(PREALLOCATED_ELEMS);
86+
let root_name = project_root
87+
.file_name()
88+
.map(|s| s.to_str().unwrap_or_default())
89+
.unwrap_or("");
90+
91+
let walker = WalkDir::new(project_root).into_iter();
92+
let mut source_mod_pairs = Vec::with_capacity(PREALLOCATED_ELEMS);
93+
source_mod_pairs.extend(walker.filter_entry(Self::is_valid).filter_map(|entry| {
94+
let entry = entry.ok()?;
95+
Some(
96+
entry
97+
.path()
98+
.to_str()
99+
.map(ToString::to_string)
100+
.unwrap_or_default(),
101+
)
102+
}));
103+
104+
list.par_extend(source_mod_pairs.par_iter().filter_map(move |path| {
105+
let relative_module_name = Path::new(path)
106+
.strip_prefix(project_root)
107+
.ok()?
108+
.with_extension("")
109+
.to_str()?
110+
.replace(MAIN_SEPARATOR, ".");
111+
let module_name = format!("{}.{}", root_name, relative_module_name);
112+
let source = read_to_string(path).ok()?;
113+
let query = AllFunctionsQuery::try_new().ok()?;
114+
let names = query
115+
.list_function_names(&source, module_name.as_str())
116+
.unwrap_or_default();
117+
Some(names)
118+
}));
119+
120+
let mut result = Vec::with_capacity(PREALLOCATED_ELEMS);
121+
result.extend(list.into_iter().flatten());
122+
Ok(result)
123+
}
124+
}
125+
126+
#[cfg(test)]
127+
mod tests;

0 commit comments

Comments
 (0)