use crate::ecosystem::Ecosystem as E;
use crate::{StackKind, StackLabel};
pub type ProcessRule = (&'static str, StackLabel, E);
pub const PROCESS_MAP: &[ProcessRule] = &[
("node", StackLabel::runtime("Node.js"), E::Node),
("nodejs", StackLabel::runtime("Node.js"), E::Node),
("python", StackLabel::runtime("Python"), E::Python),
("python3", StackLabel::runtime("Python"), E::Python),
("pythonw", StackLabel::runtime("Python"), E::Python),
("ruby", StackLabel::runtime("Ruby"), E::Ruby),
("java", StackLabel::runtime("Java"), E::Jvm),
("javaw", StackLabel::runtime("Java"), E::Jvm),
("go", StackLabel::runtime("Go"), E::Go),
("deno", StackLabel::runtime("Deno"), E::Deno),
("bun", StackLabel::runtime("Bun"), E::Node),
("dotnet", StackLabel::runtime(".NET"), E::DotNet),
("php", StackLabel::runtime("PHP"), E::Php),
("php-fpm", StackLabel::runtime("PHP"), E::Php),
("perl", StackLabel::runtime("Perl"), E::Other),
("cargo", StackLabel::runtime("Rust"), E::Rust),
("rustc", StackLabel::runtime("Rust"), E::Rust),
("erlang", StackLabel::runtime("Erlang"), E::Beam),
("beam.smp", StackLabel::runtime("Erlang"), E::Beam),
("elixir", StackLabel::runtime("Elixir"), E::Beam),
("dart", StackLabel::runtime("Dart"), E::Other),
("swift", StackLabel::runtime("Swift"), E::Other),
("postgres", StackLabel::database("PostgreSQL"), E::Other),
("postgresql", StackLabel::database("PostgreSQL"), E::Other),
("mysqld", StackLabel::database("MySQL"), E::Other),
("mysql", StackLabel::database("MySQL"), E::Other),
("mariadbd", StackLabel::database("MariaDB"), E::Other),
("mariadb", StackLabel::database("MariaDB"), E::Other),
("mongod", StackLabel::database("MongoDB"), E::Other),
("mongos", StackLabel::database("MongoDB"), E::Other),
("redis-server", StackLabel::database("Redis"), E::Other),
("redis", StackLabel::database("Redis"), E::Other),
("valkey-server", StackLabel::database("Valkey"), E::Other),
("valkey", StackLabel::database("Valkey"), E::Other),
("memcached", StackLabel::database("Memcached"), E::Other),
(
"clickhouse-server",
StackLabel::database("ClickHouse"),
E::Other,
),
("cockroach", StackLabel::database("CockroachDB"), E::Other),
("sqlservr", StackLabel::database("SQL Server"), E::Other),
("nginx", StackLabel::service("Nginx"), E::Other),
("apache2", StackLabel::service("Apache"), E::Other),
("httpd", StackLabel::service("Apache"), E::Other),
("caddy", StackLabel::service("Caddy"), E::Other),
("traefik", StackLabel::service("Traefik"), E::Other),
("envoy", StackLabel::service("Envoy"), E::Other),
("haproxy", StackLabel::service("HAProxy"), E::Other),
("w3wp", StackLabel::service("IIS"), E::Other),
("gunicorn", StackLabel::runtime("Gunicorn"), E::Python),
("uvicorn", StackLabel::runtime("Uvicorn"), E::Python),
("puma", StackLabel::runtime("Puma"), E::Ruby),
(
"elasticsearch",
StackLabel::database("Elasticsearch"),
E::Other,
),
("opensearch", StackLabel::database("OpenSearch"), E::Other),
("rabbitmq-server", StackLabel::service("RabbitMQ"), E::Other),
("kafka", StackLabel::service("Kafka"), E::Other),
("webpack", StackLabel::tool("Webpack"), E::Node),
("vite", StackLabel::tool("Vite"), E::Node),
("next-server", StackLabel::framework("Next.js"), E::Node),
("nuxt", StackLabel::framework("Nuxt"), E::Node),
("hugo", StackLabel::framework("Hugo"), E::Other),
("jekyll", StackLabel::framework("Jekyll"), E::Ruby),
("flask", StackLabel::framework("Flask"), E::Python),
("rails", StackLabel::framework("Rails"), E::Ruby),
("gradle", StackLabel::tool("Java (Gradle)"), E::Jvm),
("mvn", StackLabel::tool("Java (Maven)"), E::Jvm),
];
#[must_use]
pub fn detect_from_process(process_name: &str) -> Option<StackLabel> {
find_process_rule(process_name).map(|(_, label, _)| label.clone())
}
#[must_use]
pub fn detect_from_process_names(process_name: &str, exe_name: Option<&str>) -> Option<StackLabel> {
find_process_rule_by_names(process_name, exe_name).map(|(_, label, _)| label.clone())
}
const TITLED_PROCESSES: &[&str] = &["next-server", "puma", "gunicorn"];
pub fn find_process_rule_by_names(
process_name: &str,
exe_name: Option<&str>,
) -> Option<&'static ProcessRule> {
find_process_rule(process_name).or_else(|| exe_name.and_then(find_process_rule))
}
fn find_process_rule(process_name: &str) -> Option<&'static ProcessRule> {
let name = strip_windows_exe_suffix(process_name);
find_exact(name)
.or_else(|| find_titled(name))
.or_else(|| find_versioned_runtime(name))
}
fn find_exact(name: &str) -> Option<&'static ProcessRule> {
PROCESS_MAP
.iter()
.find(|(key, _, _)| name.eq_ignore_ascii_case(key))
}
fn find_titled(name: &str) -> Option<&'static ProcessRule> {
let (head, _) = name.split_once([' ', ':'])?;
TITLED_PROCESSES
.iter()
.any(|titled| head.eq_ignore_ascii_case(titled))
.then(|| find_exact(head))
.flatten()
}
fn find_versioned_runtime(name: &str) -> Option<&'static ProcessRule> {
let base = name.trim_end_matches(|c: char| c.is_ascii_digit() || c == '.');
let version = name.get(base.len()..)?;
if base.is_empty() || !version.starts_with(|c: char| c.is_ascii_digit()) {
return None;
}
find_exact(base).filter(|(_, label, _)| label.kind() == StackKind::Runtime)
}
fn strip_windows_exe_suffix(process_name: &str) -> &str {
let Some(prefix_len) = process_name.len().checked_sub(4) else {
return process_name;
};
match process_name.get(prefix_len..) {
Some(suffix) if suffix.eq_ignore_ascii_case(".exe") => {
process_name.get(..prefix_len).unwrap_or(process_name)
}
_ => process_name,
}
}