pub fn build_rule_pipeline(
patterns: &[&str],
input_len: u32,
) -> std::result::Result<vyre_libs::scan::RulePipeline, vyre_libs::scan::RegexCompileError> {
vyre_libs::scan::build_rule_pipeline_from_regex(patterns, "input", "hits", input_len)
}
const PIPELINE_CACHE_VERSION: u32 = 1;
fn pipeline_cache_key(patterns: &[&str], input_len: u32) -> String {
use sha2::{Digest, Sha256};
let mut h = Sha256::new();
h.update(PIPELINE_CACHE_VERSION.to_le_bytes());
h.update(input_len.to_le_bytes());
h.update((patterns.len() as u32).to_le_bytes());
for p in patterns {
h.update((p.len() as u32).to_le_bytes());
h.update(p.as_bytes());
}
let digest = h.finalize();
let mut hex = String::with_capacity(64);
for byte in digest {
use std::fmt::Write as _;
let _ = write!(hex, "{:02x}", byte);
}
hex
}
pub fn rule_pipeline_cached(
patterns: &[&str],
input_len: u32,
) -> std::result::Result<vyre_libs::scan::RulePipeline, vyre_libs::scan::RegexCompileError> {
let started = std::time::Instant::now();
let Some(cache_dir) = super::gpu_cache::gpu_matcher_cache_dir() else {
return build_rule_pipeline(patterns, input_len);
};
let cache_key = format!("pipe-{}", pipeline_cache_key(patterns, input_len));
if let Some(path) = vyre_libs::scan::engine_cache_path(&cache_dir, &cache_key) {
if let Ok(bytes) = std::fs::read(&path) {
match vyre_libs::scan::RulePipeline::from_bytes(&bytes) {
Ok(pipeline) => {
tracing::debug!(
target: "keyhog::routing",
patterns = patterns.len(),
input_len,
elapsed_ms = started.elapsed().as_millis() as u64,
"RulePipeline cache hit: skipped compile"
);
return Ok(pipeline);
}
Err(error) => {
tracing::debug!(
target: "keyhog::routing",
cache = %path.display(),
%error,
"corrupt rule pipeline cache entry removed"
);
let _ = std::fs::remove_file(&path);
}
}
}
}
let pipeline = build_rule_pipeline(patterns, input_len)?;
if let Some(path) = vyre_libs::scan::engine_cache_path(&cache_dir, &cache_key) {
if let Ok(bytes) = pipeline.to_bytes() {
let tmp = path.with_extension(format!("tmp.{}", std::process::id()));
if let Some(parent) = path.parent() {
if let Err(error) = std::fs::create_dir_all(parent) {
tracing::debug!(
target: "keyhog::routing",
dir = %parent.display(),
%error,
"rule pipeline cache dir create failed; cache write will be skipped"
);
}
}
if std::fs::write(&tmp, &bytes).is_ok() {
if let Err(error) = std::fs::rename(&tmp, &path) {
tracing::debug!(
target: "keyhog::routing",
error = %error,
path = %path.display(),
"rule pipeline cache rename failed"
);
let _ = std::fs::remove_file(&tmp);
}
}
}
}
tracing::debug!(
target: "keyhog::routing",
patterns = patterns.len(),
input_len,
elapsed_ms = started.elapsed().as_millis() as u64,
"RulePipeline cache miss: compiled and saved"
);
Ok(pipeline)
}
pub const MEGASCAN_INPUT_LEN_DEFAULT: usize = 256 * 1024 * 1024;
pub const MEGASCAN_INPUT_LEN: usize = MEGASCAN_INPUT_LEN_DEFAULT;
pub fn megascan_input_len() -> usize {
use std::sync::OnceLock;
static CACHED: OnceLock<usize> = OnceLock::new();
*CACHED.get_or_init(|| {
let caps = crate::hw_probe::probe_hardware();
let len = match caps.gpu_vram_mb {
Some(mb) if mb >= 24 * 1024 => 1024 * 1024 * 1024,
Some(mb) if mb >= 12 * 1024 => 512 * 1024 * 1024,
Some(mb) if mb >= 8 * 1024 => 256 * 1024 * 1024,
Some(_) => 128 * 1024 * 1024,
None => MEGASCAN_INPUT_LEN_DEFAULT,
};
tracing::debug!(
target: "keyhog::routing",
gpu_vram_mb = ?caps.gpu_vram_mb,
megascan_input_len = len,
"MegaScan input length sized for VRAM"
);
len
})
}
pub const AC_GPU_MAX_MATCHES_PER_DISPATCH: u32 = 32_768;