-
Notifications
You must be signed in to change notification settings - Fork 11
feat: Add rate limiting, configurable workers and network optimizations #24
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,4 @@ | ||
| .env | ||
| dist/ | ||
| output/ | ||
| prometheus-net-discovery |
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -13,11 +13,41 @@ go get -u github.com/fortnoxab/prometheus-net-discovery | |||||||||||||||
| prometheus-net-discovery -networks "192.168.1.0/24" --filesdpath /tmp/ | ||||||||||||||||
| ``` | ||||||||||||||||
|
|
||||||||||||||||
| ### Configuration options | ||||||||||||||||
|
|
||||||||||||||||
| | Option | Default | Description | | ||||||||||||||||
| |--------|---------|-------------| | ||||||||||||||||
| | `-workers` | 16 | Number of concurrent workers | | ||||||||||||||||
| | `-scanratelimit` | 50 | Maximum hosts to probe per second | | ||||||||||||||||
| | `-skipnetworkbroadcast` | true | Skip network and broadcast addresses | | ||||||||||||||||
| | `-exporterexporterport` | 9999 | Port where exporter_exporter is listening | | ||||||||||||||||
| | `-port` | 8080 | Port for the internal webserver | | ||||||||||||||||
| | `-interval` | 60m | How often to scan | | ||||||||||||||||
|
|
||||||||||||||||
|
Comment on lines
+22
to
+26
|
||||||||||||||||
| | `-skipnetworkbroadcast` | true | Skip network and broadcast addresses | | |
| | `-exporterexporterport` | 9999 | Port where exporter_exporter is listening | | |
| | `-port` | 8080 | Port for the internal webserver | | |
| | `-dnsserver` | (empty) | DNS server for PTR lookups (e.g., "8.8.8.8:53"), empty uses OS default | | |
| | `-exporterexporterport` | 9999 | Port where exporter_exporter is listening | | |
| | `-port` | 8080 | Port for the internal webserver | | |
| | `-dnsserver` | (empty) | DNS server for PTR lookups (e.g., "8.8.8.8:53"), empty uses OS default | |
Copilot
AI
Jan 21, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This documentation describes the skipnetworkbroadcast flag, but the corresponding SkipNetworkBroadcast configuration field is not used in the code. Network and broadcast addresses are always skipped unconditionally. Either remove this documentation or implement the conditional behavior in the code.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,6 +20,7 @@ import ( | |
| "github.com/fortnoxab/fnxlogrus" | ||
| "github.com/koding/multiconfig" | ||
| "github.com/sirupsen/logrus" | ||
| "golang.org/x/time/rate" | ||
| ) | ||
|
|
||
| // ExporterConfig configures ports to scan to what filename to save it to. | ||
|
|
@@ -43,7 +44,7 @@ var mutex sync.Mutex | |
| func main() { | ||
| config := &Config{} | ||
| multiconfig.MustLoad(&config) | ||
| exporterConfig[0].port = config.ExpoterExporterPort | ||
| exporterConfig[0].port = config.ExporterExporterPort | ||
|
|
||
| fnxlogrus.Init(config.Log, logrus.StandardLogger()) | ||
|
|
||
|
|
@@ -85,8 +86,13 @@ func runDiscovery(parentCtx context.Context, config *Config, networks []string) | |
| exporter := make(chan *Address) | ||
| ctx, cancel := context.WithCancel(parentCtx) | ||
| defer cancel() | ||
|
|
||
| // Rate limiter for HTTP probes (still useful to spread load) | ||
| limiter := rate.NewLimiter(rate.Limit(config.ScanRateLimit), 1) | ||
|
|
||
| var wg sync.WaitGroup | ||
| for i := 0; i < 128; i++ { | ||
| workerCount := config.Workers | ||
| for i := 0; i < workerCount; i++ { | ||
| wg.Add(1) | ||
| i := i | ||
| go func() { | ||
|
|
@@ -98,6 +104,10 @@ func runDiscovery(parentCtx context.Context, config *Config, networks []string) | |
| logrus.Debugf("worker %d finished", i) | ||
| return | ||
| } | ||
| // Wait for rate limiter before processing | ||
| if err := limiter.Wait(ctx); err != nil { | ||
| return | ||
| } | ||
| fn(ctx) | ||
| case <-ctx.Done(): | ||
| return | ||
|
|
@@ -115,7 +125,7 @@ func runDiscovery(parentCtx context.Context, config *Config, networks []string) | |
| if network == "" { | ||
| continue | ||
| } | ||
| discoverNetwork(network, job, exporter) | ||
| discoverNetwork(network, job, exporter, config) | ||
| } | ||
| close(job) | ||
| }() | ||
|
|
@@ -139,6 +149,7 @@ func runDiscovery(parentCtx context.Context, config *Config, networks []string) | |
| wg.Wait() | ||
|
|
||
| saveConfigs(ctx, config, exporters) | ||
|
|
||
| logrus.Info("discovery done") | ||
| } | ||
|
|
||
|
|
@@ -163,12 +174,46 @@ func isVip(name string) bool { | |
| return vipRegexp.MatchString(name) | ||
| } | ||
|
|
||
| func discoverNetwork(network string, queue chan func(context.Context), exporter chan *Address) { | ||
| // isNetworkOrBroadcast checks if the IP is the network address or broadcast address of the given subnet. | ||
| // Note: IPv6 addresses always return false as IPv6 doesn't have broadcast addresses. | ||
| // For IPv6, this function only filters the all-zeros network address. | ||
| func isNetworkOrBroadcast(ip net.IP, ipnet *net.IPNet) bool { | ||
| // Try IPv4 first | ||
| ipv4 := ip.To4() | ||
| if ipv4 != nil { | ||
| // Get network address | ||
| networkIP := ipv4.Mask(ipnet.Mask) | ||
|
|
||
| // Calculate broadcast address | ||
| broadcast := make(net.IP, len(networkIP)) | ||
| for i := range networkIP { | ||
| broadcast[i] = networkIP[i] | ^ipnet.Mask[i] | ||
| } | ||
|
|
||
| return ipv4.Equal(networkIP) || ipv4.Equal(broadcast) | ||
| } | ||
|
|
||
| // For IPv6, check if it's the network address (IPv6 doesn't have broadcast) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Använder vi ens IPv6?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nej bara ipv4 |
||
| networkIP := ip.Mask(ipnet.Mask) | ||
| return ip.Equal(networkIP) | ||
| } | ||
|
|
||
| func discoverNetwork(network string, queue chan func(context.Context), exporter chan *Address, config *Config) { | ||
| networkip, ipnet, err := net.ParseCIDR(network) | ||
| if err != nil { | ||
| log.Fatal("network CIDR could not be parsed:", err) | ||
| } | ||
|
|
||
| skippedCount := 0 | ||
| for ip := networkip.Mask(ipnet.Mask); ipnet.Contains(ip); inc(ip) { | ||
| // Skip network and broadcast addresses if configured | ||
| currentIP := make(net.IP, len(ip)) | ||
| copy(currentIP, ip) | ||
| if config.SkipNetworkBroadcast && isNetworkOrBroadcast(currentIP, ipnet) { | ||
| skippedCount++ | ||
| continue | ||
| } | ||
|
|
||
| network := network | ||
| ip := ip.String() | ||
| queue <- func(ctx context.Context) { | ||
|
|
@@ -189,10 +234,10 @@ func discoverNetwork(network string, queue chan func(context.Context), exporter | |
| } | ||
|
|
||
| logrus.Info(net.JoinHostPort(ip, port), " is alive") | ||
| addr, _ := net.LookupAddr(ip) // #nosec | ||
| addr, _ := net.DefaultResolver.LookupAddr(ctx, ip) // #nosec | ||
| hostname := strings.TrimRight(getFirst(addr), ".") | ||
| if hostname == "" { | ||
| logrus.Error("missing reverse record for ", ip) | ||
| logrus.Debugf("skipping %s: missing reverse record", ip) | ||
| continue | ||
| } | ||
| if isVip(hostname) && !strings.HasPrefix(hostname, "k8s-") { | ||
|
|
@@ -215,6 +260,9 @@ func discoverNetwork(network string, queue chan func(context.Context), exporter | |
| } | ||
| } | ||
| } | ||
| if skippedCount > 0 { | ||
| logrus.Debugf("Skipped %d network/broadcast addresses in %s", skippedCount, network) | ||
| } | ||
| } | ||
|
|
||
| func getOldGroups(path string) ([]Group, error) { | ||
|
|
@@ -254,7 +302,7 @@ func writeFileSDConfig(config *Config, exporterName string, addresses []Address) | |
| "host": v.Hostname, | ||
| }, | ||
| } | ||
| if v.Port == config.ExpoterExporterPort { | ||
| if v.Port == config.ExporterExporterPort { | ||
| group.Labels["__metrics_path__"] = "/proxy" | ||
| group.Labels["__param_module"] = exporterName | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,6 +1,8 @@ | ||||||
| package main | ||||||
|
|
||||||
| import "github.com/fortnoxab/fnxlogrus" | ||||||
| import ( | ||||||
| "github.com/fortnoxab/fnxlogrus" | ||||||
| ) | ||||||
|
|
||||||
| // Config is main application configuration. | ||||||
| type Config struct { | ||||||
|
|
@@ -12,7 +14,13 @@ type Config struct { | |||||
| FileSdPath string | ||||||
| Log fnxlogrus.Config | ||||||
| Port string `default:"8080"` | ||||||
| ExpoterExporterPort string `default:"9999"` | ||||||
| ExporterExporterPort string `default:"9999"` | ||||||
| // Workers is the number of concurrent workers for scanning. Default 16 | ||||||
| Workers int `default:"16"` | ||||||
| // ScanRateLimit is the maximum number of hosts to scan per second. Default 50 to reduce network load | ||||||
| ScanRateLimit float64 `default:"50"` | ||||||
| // SkipNetworkBroadcast skips network and broadcast addresses. Default true | ||||||
| SkipNetworkBroadcast bool `default:"true"` | ||||||
|
Comment on lines
+22
to
+23
|
||||||
| // SkipNetworkBroadcast skips network and broadcast addresses. Default true | |
| SkipNetworkBroadcast bool `default:"true"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The lint and test-coverage targets were removed from the Makefile. While these may have been unused, removing lint checks could reduce code quality enforcement. Consider keeping the lint target or documenting why it was removed in the PR description.