package collector import ( "os/exec" "regexp" "strconv" "strings" ) type PFStates struct { CurrentEntries int `json:"current_entries"` HardLimit int `json:"hard_limit"` SearchRate float64 `json:"search_rate"` InsertRate float64 `json:"insert_rate"` RemovalRate float64 `json:"removal_rate"` MatchRate float64 `json:"match_rate"` } func CollectPFStates() *PFStates { result := &PFStates{} // pfctl -si for state table info if out, err := exec.Command("/sbin/pfctl", "-si").Output(); err == nil { lines := strings.Split(string(out), "\n") rateRe := regexp.MustCompile(`([\d.]+)/s`) for _, line := range lines { lower := strings.TrimSpace(line) if strings.HasPrefix(lower, "current entries") { result.CurrentEntries = parseLastInt(lower) } else if strings.HasPrefix(lower, "searches") { if m := rateRe.FindStringSubmatch(line); len(m) > 1 { result.SearchRate, _ = strconv.ParseFloat(m[1], 64) } } else if strings.HasPrefix(lower, "inserts") { if m := rateRe.FindStringSubmatch(line); len(m) > 1 { result.InsertRate, _ = strconv.ParseFloat(m[1], 64) } } else if strings.HasPrefix(lower, "removals") { if m := rateRe.FindStringSubmatch(line); len(m) > 1 { result.RemovalRate, _ = strconv.ParseFloat(m[1], 64) } } else if strings.HasPrefix(lower, "match") { if m := rateRe.FindStringSubmatch(line); len(m) > 1 { result.MatchRate, _ = strconv.ParseFloat(m[1], 64) } } } } // pfctl -sm for hard limit if out, err := exec.Command("/sbin/pfctl", "-sm").Output(); err == nil { for _, line := range strings.Split(string(out), "\n") { if strings.Contains(line, "states") && strings.Contains(line, "hard limit") { result.HardLimit = parseLastInt(line) break } } } return result } func parseLastInt(line string) int { fields := strings.Fields(line) for i := len(fields) - 1; i >= 0; i-- { if v, err := strconv.Atoi(fields[i]); err == nil { return v } } return 0 }