1use crate::ecosystem::Ecosystem as E;
10use crate::text::strip_suffix_ignore_ascii_case;
11use crate::{StackKind, StackLabel, labels};
12
13pub type ProcessRule = (&'static [&'static str], StackLabel, E);
16
17pub const PROCESS_RULES: &[ProcessRule] = &[
22 (&["node", "nodejs"], labels::NODE, E::Node),
23 (&["python", "python3", "pythonw"], labels::PYTHON, E::Python),
24 (&["ruby"], labels::RUBY, E::Ruby),
25 (&["java", "javaw"], labels::JAVA, E::Jvm),
26 (&["go"], labels::GO, E::Go),
27 (&["deno"], labels::DENO, E::Deno),
28 (&["bun"], labels::BUN, E::Node),
29 (&["dotnet"], labels::DOTNET, E::DotNet),
30 (&["php", "php-fpm"], labels::PHP, E::Php),
31 (&["perl"], labels::PERL, E::Other),
32 (&["cargo", "rustc"], labels::RUST, E::Rust),
33 (
35 &["erlang", "beam.smp", "erl", "werl"],
36 labels::ERLANG,
37 E::Beam,
38 ),
39 (&["elixir"], labels::ELIXIR, E::Beam),
40 (&["dart"], labels::DART, E::Other),
41 (&["swift"], labels::SWIFT, E::Other),
42 (&["postgres", "postgresql"], labels::POSTGRESQL, E::Other),
43 (&["mysqld", "mysql"], labels::MYSQL, E::Other),
44 (&["mariadbd", "mariadb"], labels::MARIADB, E::Other),
45 (&["mongod", "mongos"], labels::MONGODB, E::Other),
46 (&["redis-server", "redis"], labels::REDIS, E::Other),
47 (&["valkey-server", "valkey"], labels::VALKEY, E::Other),
48 (&["memcached"], labels::MEMCACHED, E::Other),
49 (&["clickhouse-server"], labels::CLICKHOUSE, E::Other),
50 (&["cockroach"], labels::COCKROACHDB, E::Other),
51 (&["sqlservr"], labels::SQL_SERVER, E::Other),
52 (&["nginx"], labels::NGINX, E::Other),
53 (&["apache2", "httpd"], labels::APACHE, E::Other),
54 (&["caddy"], labels::CADDY, E::Other),
55 (&["traefik"], labels::TRAEFIK, E::Other),
56 (&["envoy"], labels::ENVOY, E::Other),
57 (&["haproxy"], labels::HAPROXY, E::Other),
58 (&["w3wp"], labels::IIS, E::Other),
59 (&["gunicorn"], labels::GUNICORN, E::Python),
60 (&["uvicorn"], labels::UVICORN, E::Python),
61 (&["puma"], labels::PUMA, E::Ruby),
62 (&["elasticsearch"], labels::ELASTICSEARCH, E::Other),
63 (&["opensearch"], labels::OPENSEARCH, E::Other),
64 (&["rabbitmq-server"], labels::RABBITMQ, E::Other),
65 (&["kafka"], labels::KAFKA, E::Other),
66 (&["webpack"], labels::WEBPACK, E::Node),
67 (&["vite"], labels::VITE, E::Node),
68 (&["next-server"], labels::NEXT_JS, E::Node),
69 (&["nuxt"], labels::NUXT, E::Node),
70 (&["hugo"], labels::HUGO, E::Other),
71 (&["jekyll"], labels::JEKYLL, E::Ruby),
72 (&["flask"], labels::FLASK, E::Python),
73 (&["rails"], labels::RAILS, E::Ruby),
74 (&["gradle"], labels::JAVA_GRADLE, E::Jvm),
75 (&["mvn"], labels::JAVA_MAVEN, E::Jvm),
76];
77
78#[must_use]
115pub fn detect_from_process(process_name: &str) -> Option<StackLabel> {
116 find_process_rule(process_name).map(|(_, label, _)| label.clone())
117}
118
119#[must_use]
138pub fn detect_from_process_names(process_name: &str, exe_name: Option<&str>) -> Option<StackLabel> {
139 find_process_rule_by_names(process_name, exe_name).map(|(_, label, _)| label.clone())
140}
141
142const TITLED_PROCESSES: &[&str] = &["next-server", "puma", "gunicorn"];
147
148pub fn find_process_rule_by_names(
150 process_name: &str,
151 exe_name: Option<&str>,
152) -> Option<&'static ProcessRule> {
153 find_process_rule(process_name).or_else(|| exe_name.and_then(find_process_rule))
154}
155
156fn find_process_rule(process_name: &str) -> Option<&'static ProcessRule> {
157 let name = strip_windows_exe_suffix(process_name);
158
159 find_exact(name)
160 .or_else(|| find_titled(name))
161 .or_else(|| find_versioned_runtime(name))
162}
163
164fn find_exact(name: &str) -> Option<&'static ProcessRule> {
165 PROCESS_RULES
166 .iter()
167 .find(|(names, _, _)| names.iter().any(|key| name.eq_ignore_ascii_case(key)))
168}
169
170fn find_titled(name: &str) -> Option<&'static ProcessRule> {
171 let (head, _) = name.split_once([' ', ':'])?;
172 TITLED_PROCESSES
173 .iter()
174 .any(|titled| head.eq_ignore_ascii_case(titled))
175 .then(|| find_exact(head))
176 .flatten()
177}
178
179fn find_versioned_runtime(name: &str) -> Option<&'static ProcessRule> {
182 let base = name.trim_end_matches(|c: char| c.is_ascii_digit() || c == '.');
183 let version = name.get(base.len()..)?;
184
185 if base.is_empty() || !version.starts_with(|c: char| c.is_ascii_digit()) {
186 return None;
187 }
188
189 find_exact(base).filter(|(_, label, _)| label.kind() == StackKind::Runtime)
190}
191
192fn strip_windows_exe_suffix(process_name: &str) -> &str {
193 strip_suffix_ignore_ascii_case(process_name, ".exe").unwrap_or(process_name)
194}