1use crate::ecosystem::Ecosystem as E;
10use crate::{StackKind, StackLabel};
11
12pub type ProcessRule = (&'static str, StackLabel, E);
15
16pub const PROCESS_MAP: &[ProcessRule] = &[
22 ("node", StackLabel::runtime("Node.js"), E::Node),
23 ("nodejs", StackLabel::runtime("Node.js"), E::Node),
24 ("python", StackLabel::runtime("Python"), E::Python),
25 ("python3", StackLabel::runtime("Python"), E::Python),
26 ("pythonw", StackLabel::runtime("Python"), E::Python),
27 ("ruby", StackLabel::runtime("Ruby"), E::Ruby),
28 ("java", StackLabel::runtime("Java"), E::Jvm),
29 ("javaw", StackLabel::runtime("Java"), E::Jvm),
30 ("go", StackLabel::runtime("Go"), E::Go),
31 ("deno", StackLabel::runtime("Deno"), E::Deno),
32 ("bun", StackLabel::runtime("Bun"), E::Node),
33 ("dotnet", StackLabel::runtime(".NET"), E::DotNet),
34 ("php", StackLabel::runtime("PHP"), E::Php),
35 ("php-fpm", StackLabel::runtime("PHP"), E::Php),
36 ("perl", StackLabel::runtime("Perl"), E::Other),
37 ("cargo", StackLabel::runtime("Rust"), E::Rust),
38 ("rustc", StackLabel::runtime("Rust"), E::Rust),
39 ("erlang", StackLabel::runtime("Erlang"), E::Beam),
40 ("beam.smp", StackLabel::runtime("Erlang"), E::Beam),
41 ("elixir", StackLabel::runtime("Elixir"), E::Beam),
42 ("dart", StackLabel::runtime("Dart"), E::Other),
43 ("swift", StackLabel::runtime("Swift"), E::Other),
44 ("postgres", StackLabel::database("PostgreSQL"), E::Other),
45 ("postgresql", StackLabel::database("PostgreSQL"), E::Other),
46 ("mysqld", StackLabel::database("MySQL"), E::Other),
47 ("mysql", StackLabel::database("MySQL"), E::Other),
48 ("mariadbd", StackLabel::database("MariaDB"), E::Other),
49 ("mariadb", StackLabel::database("MariaDB"), E::Other),
50 ("mongod", StackLabel::database("MongoDB"), E::Other),
51 ("mongos", StackLabel::database("MongoDB"), E::Other),
52 ("redis-server", StackLabel::database("Redis"), E::Other),
53 ("redis", StackLabel::database("Redis"), E::Other),
54 ("valkey-server", StackLabel::database("Valkey"), E::Other),
55 ("valkey", StackLabel::database("Valkey"), E::Other),
56 ("memcached", StackLabel::database("Memcached"), E::Other),
57 (
58 "clickhouse-server",
59 StackLabel::database("ClickHouse"),
60 E::Other,
61 ),
62 ("cockroach", StackLabel::database("CockroachDB"), E::Other),
63 ("sqlservr", StackLabel::database("SQL Server"), E::Other),
64 ("nginx", StackLabel::service("Nginx"), E::Other),
65 ("apache2", StackLabel::service("Apache"), E::Other),
66 ("httpd", StackLabel::service("Apache"), E::Other),
67 ("caddy", StackLabel::service("Caddy"), E::Other),
68 ("traefik", StackLabel::service("Traefik"), E::Other),
69 ("envoy", StackLabel::service("Envoy"), E::Other),
70 ("haproxy", StackLabel::service("HAProxy"), E::Other),
71 ("w3wp", StackLabel::service("IIS"), E::Other),
72 ("gunicorn", StackLabel::runtime("Gunicorn"), E::Python),
73 ("uvicorn", StackLabel::runtime("Uvicorn"), E::Python),
74 ("puma", StackLabel::runtime("Puma"), E::Ruby),
75 (
76 "elasticsearch",
77 StackLabel::database("Elasticsearch"),
78 E::Other,
79 ),
80 ("opensearch", StackLabel::database("OpenSearch"), E::Other),
81 ("rabbitmq-server", StackLabel::service("RabbitMQ"), E::Other),
82 ("kafka", StackLabel::service("Kafka"), E::Other),
83 ("webpack", StackLabel::tool("Webpack"), E::Node),
84 ("vite", StackLabel::tool("Vite"), E::Node),
85 ("next-server", StackLabel::framework("Next.js"), E::Node),
86 ("nuxt", StackLabel::framework("Nuxt"), E::Node),
87 ("hugo", StackLabel::framework("Hugo"), E::Other),
88 ("jekyll", StackLabel::framework("Jekyll"), E::Ruby),
89 ("flask", StackLabel::framework("Flask"), E::Python),
90 ("rails", StackLabel::framework("Rails"), E::Ruby),
91 ("gradle", StackLabel::tool("Java (Gradle)"), E::Jvm),
92 ("mvn", StackLabel::tool("Java (Maven)"), E::Jvm),
93];
94
95#[must_use]
132pub fn detect_from_process(process_name: &str) -> Option<StackLabel> {
133 find_process_rule(process_name).map(|(_, label, _)| label.clone())
134}
135
136#[must_use]
155pub fn detect_from_process_names(process_name: &str, exe_name: Option<&str>) -> Option<StackLabel> {
156 find_process_rule_by_names(process_name, exe_name).map(|(_, label, _)| label.clone())
157}
158
159const TITLED_PROCESSES: &[&str] = &["next-server", "puma", "gunicorn"];
164
165pub fn find_process_rule_by_names(
167 process_name: &str,
168 exe_name: Option<&str>,
169) -> Option<&'static ProcessRule> {
170 find_process_rule(process_name).or_else(|| exe_name.and_then(find_process_rule))
171}
172
173fn find_process_rule(process_name: &str) -> Option<&'static ProcessRule> {
174 let name = strip_windows_exe_suffix(process_name);
175
176 find_exact(name)
177 .or_else(|| find_titled(name))
178 .or_else(|| find_versioned_runtime(name))
179}
180
181fn find_exact(name: &str) -> Option<&'static ProcessRule> {
182 PROCESS_MAP
183 .iter()
184 .find(|(key, _, _)| name.eq_ignore_ascii_case(key))
185}
186
187fn find_titled(name: &str) -> Option<&'static ProcessRule> {
188 let (head, _) = name.split_once([' ', ':'])?;
189 TITLED_PROCESSES
190 .iter()
191 .any(|titled| head.eq_ignore_ascii_case(titled))
192 .then(|| find_exact(head))
193 .flatten()
194}
195
196fn find_versioned_runtime(name: &str) -> Option<&'static ProcessRule> {
199 let base = name.trim_end_matches(|c: char| c.is_ascii_digit() || c == '.');
200 let version = name.get(base.len()..)?;
201
202 if base.is_empty() || !version.starts_with(|c: char| c.is_ascii_digit()) {
203 return None;
204 }
205
206 find_exact(base).filter(|(_, label, _)| label.kind() == StackKind::Runtime)
207}
208
209fn strip_windows_exe_suffix(process_name: &str) -> &str {
210 let Some(prefix_len) = process_name.len().checked_sub(4) else {
211 return process_name;
212 };
213
214 match process_name.get(prefix_len..) {
215 Some(suffix) if suffix.eq_ignore_ascii_case(".exe") => {
216 process_name.get(..prefix_len).unwrap_or(process_name)
217 }
218 _ => process_name,
219 }
220}