From e05a8c67154a80dff1249133e9428a8ba82eca20 Mon Sep 17 00:00:00 2001 From: Jakub Date: Sat, 30 Sep 2023 13:07:28 +0200 Subject: [PATCH 01/36] Start preparing application for taking yaml config --- cmd/exporter/main.go | 23 ++++- config/exporter_config.go | 115 ++++++++++++++++++++++ config/exporter_config_test.go | 173 +++++++++++++++++++++++++++++++++ config/logstash_config.go | 8 -- config/server_config.go | 17 ---- fixtures/invalid_config.toml | 2 + fixtures/valid_config.yml | 8 ++ go.mod | 1 + go.sum | 5 + server/healthcheck.go | 50 +++++++--- server/healthcheck_test.go | 40 +++++--- server/server.go | 4 +- server/server_test.go | 52 ++++++++-- 13 files changed, 433 insertions(+), 65 deletions(-) create mode 100644 config/exporter_config.go create mode 100644 config/exporter_config_test.go delete mode 100644 config/logstash_config.go delete mode 100644 config/server_config.go create mode 100644 fixtures/invalid_config.toml create mode 100644 fixtures/valid_config.yml diff --git a/cmd/exporter/main.go b/cmd/exporter/main.go index 1a701b5c..9db5da1c 100644 --- a/cmd/exporter/main.go +++ b/cmd/exporter/main.go @@ -6,6 +6,7 @@ import ( "log" "log/slog" "os" + "strconv" "github.com/joho/godotenv" "github.com/kuskoman/logstash-exporter/collectors" @@ -34,16 +35,28 @@ func main() { } slog.SetDefault(logger) - port, host := config.Port, config.Host - logstashUrl := config.LogstashUrl + exporterConfig, err := config.GetConfig(config.ExporterConfigLocation) + if err != nil { + slog.Error("failed to get exporter config", "err", err) + os.Exit(1) + } + + host := exporterConfig.Server.Host + port := strconv.Itoa(exporterConfig.Server.Port) slog.Debug("application starting... ") versionInfo := config.GetVersionInfo() slog.Info(versionInfo.String()) - collectorManager := collectors.NewCollectorManager(logstashUrl) - appServer := server.NewAppServer(host, port) - prometheus.MustRegister(collectorManager) + for _, logstashServerConfig := range exporterConfig.Logstash.Servers { + logstashUrl := logstashServerConfig.URL + slog.Info("booting collector manager for", "logstashUrl", logstashUrl) + + collectorManager := collectors.NewCollectorManager(logstashUrl) + prometheus.MustRegister(collectorManager) + } + + appServer := server.NewAppServer(host, port, exporterConfig) slog.Info("starting server on", "host", host, "port", port) if err := appServer.ListenAndServe(); err != nil { diff --git a/config/exporter_config.go b/config/exporter_config.go new file mode 100644 index 00000000..3b9b0053 --- /dev/null +++ b/config/exporter_config.go @@ -0,0 +1,115 @@ +package config + +import ( + "log/slog" + "os" + + "gopkg.in/yaml.v2" +) + +const ( + defaultConfigLocation = "config.yml" + defaultPort = 9198 + defaultLogLevel = "info" + defaultLogstashURL = "http://localhost:9600" +) + +var ( + ExporterConfigLocation = getEnvWithDefault("EXPORTER_CONFIG_LOCATION", defaultConfigLocation) +) + +// LogstashServer represents individual Logstash server configuration +type LogstashServer struct { + URL string `yaml:"url"` +} + +// LogstashConfig holds the configuration for all Logstash servers +type LogstashConfig struct { + Servers []LogstashServer `yaml:"servers"` +} + +// ServerConfig represents the server configuration +type ServerConfig struct { + // Host is the host the exporter will listen on. + // Defaults to an empty string, which will listen on all interfaces + // Can be overridden by setting the HOST environment variable + // For windows, use "localhost", because an empty string will not work + // with the default windows firewall configuration. + // Alternatively you can change the firewall configuration to allow + // connections to the port from all interfaces. + Host string `yaml:"host"` + Port int `yaml:"port"` +} + +// LoggingConfig represents the logging configuration +type LoggingConfig struct { + Level string `yaml:"level"` +} + +// Config represents the overall configuration loaded from the YAML file +type Config struct { + Logstash LogstashConfig `yaml:"logstash"` + Server ServerConfig `yaml:"server"` + Logging LoggingConfig `yaml:"logging"` +} + +// loadConfig loads the configuration from the YAML file. +func loadConfig(location string) (*Config, error) { + data, err := os.ReadFile(location) + if err != nil { + return nil, err + } + + var config Config + err = yaml.Unmarshal(data, &config) + if err != nil { + return nil, err + } + + return &config, nil +} + +// mergeWithDefault merges the loaded configuration with the default configuration values. +func mergeWithDefault(config *Config) *Config { + if config == nil { + config = &Config{} + } + + if config.Server.Port == 0 { + slog.Debug("using default port", "port", defaultPort) + config.Server.Port = defaultPort + } + + if config.Logging.Level == "" { + slog.Debug("using default log level", "level", defaultLogLevel) + config.Logging.Level = defaultLogLevel + } + + if len(config.Logstash.Servers) == 0 { + slog.Debug("using default logstash server", "url", defaultLogstashURL) + config.Logstash.Servers = append(config.Logstash.Servers, LogstashServer{ + URL: defaultLogstashURL, + }) + } + + return config +} + +// GetConfig combines loadConfig and mergeWithDefault to get the final configuration. +func GetConfig(location string) (*Config, error) { + config, err := loadConfig(location) + if err != nil { + return nil, err + } + + mergedConfig := mergeWithDefault(config) + return mergedConfig, nil +} + +func (cfg *Config) GetLogstashUrls() []string { + urls := make([]string, len(cfg.Logstash.Servers)) + for i, server := range cfg.Logstash.Servers { + urls[i] = server.URL + } + return urls +} diff --git a/config/exporter_config_test.go b/config/exporter_config_test.go new file mode 100644 index 00000000..3632b43b --- /dev/null +++ b/config/exporter_config_test.go @@ -0,0 +1,173 @@ +package config + +import ( + "reflect" + "testing" +) + +func TestLoadConfig(t *testing.T) { + t.Parallel() + t.Run("loads valid config", func(t *testing.T) { + t.Parallel() + + location := "../fixtures/valid_config.yml" + config, err := loadConfig(location) + + if err != nil { + t.Fatalf("got an error %v", err) + } + if config == nil { + t.Fatal("expected config to be non-nil") + } + if config.Logstash.Servers[0].URL != "http://localhost:9601" { + t.Errorf("expected URL to be %v, got %v", "http://localhost:9601", config.Logstash.Servers[0].URL) + } + }) + + t.Run("returns error for non-existent file", func(t *testing.T) { + t.Parallel() + + location := "../fixtures/non_existent.yml" + config, err := loadConfig(location) + + if err == nil { + t.Fatal("expected error, got none") + } + if config != nil { + t.Fatal("expected config to be nil") + } + }) + + t.Run("returns error for invalid config", func(t *testing.T) { + t.Parallel() + + location := "../fixtures/invalid_config.toml" + config, err := loadConfig(location) + + if err == nil { + t.Fatal("expected error, got none") + } + + if config != nil { + t.Fatal("expected config to be nil") + } + }) +} + +func TestMergeWithDefault(t *testing.T) { + t.Parallel() + + t.Run("merge with empty config", func(t *testing.T) { + t.Parallel() + + config := &Config{} + mergedConfig := mergeWithDefault(config) + + if mergedConfig.Server.Port != defaultPort { + t.Errorf("expected port to be %v, got %v", defaultPort, mergedConfig.Server.Port) + } + if mergedConfig.Logging.Level != defaultLogLevel { + t.Errorf("expected level to be %v, got %v", defaultLogLevel, mergedConfig.Logging.Level) + } + if mergedConfig.Logstash.Servers[0].URL != defaultLogstashURL { + t.Errorf("expected URL to be %v, got %v", defaultLogstashURL, mergedConfig.Logstash.Servers[0].URL) + } + }) + + t.Run("merge with nil config", func(t *testing.T) { + t.Parallel() + + mergedConfig := mergeWithDefault(nil) + + if mergedConfig.Server.Port != defaultPort { + t.Errorf("expected port to be %v, got %v", defaultPort, mergedConfig.Server.Port) + } + if mergedConfig.Logging.Level != defaultLogLevel { + t.Errorf("expected level to be %v, got %v", defaultLogLevel, mergedConfig.Logging.Level) + } + if mergedConfig.Logstash.Servers[0].URL != defaultLogstashURL { + t.Errorf("expected URL to be %v, got %v", defaultLogstashURL, mergedConfig.Logstash.Servers[0].URL) + } + }) + + t.Run("merge with non-empty config", func(t *testing.T) { + t.Parallel() + + config := &Config{ + Server: ServerConfig{ + Port: 1234, + }, + Logging: LoggingConfig{ + Level: "debug", + }, + Logstash: LogstashConfig{ + Servers: []LogstashServer{ + {URL: "http://localhost:9601"}, + {URL: "http://localhost:9602"}, + }, + }, + } + + mergedConfig := mergeWithDefault(config) + + if mergedConfig.Server.Port != 1234 { + t.Errorf("expected port to be %v, got %v", 1234, mergedConfig.Server.Port) + } + + if mergedConfig.Logging.Level != "debug" { + t.Errorf("expected level to be %v, got %v", "debug", mergedConfig.Logging.Level) + } + + if mergedConfig.Logstash.Servers[0].URL != "http://localhost:9601" { + t.Errorf("expected URL to be %v, got %v", "http://localhost:9601", mergedConfig.Logstash.Servers[0].URL) + } + + if mergedConfig.Logstash.Servers[1].URL != "http://localhost:9602" { + t.Errorf("expected URL to be %v, got %v", "http://localhost:9602", mergedConfig.Logstash.Servers[1].URL) + } + }) +} + +func TestGetConfig(t *testing.T) { + t.Run("returns valid config", func(t *testing.T) { + + location := "../fixtures/valid_config.yml" + config, err := GetConfig(location) + + if err != nil { + t.Fatalf("got an error %v", err) + } + if config == nil { + t.Fatal("expected config to be non-nil") + } + }) + + t.Run("returns error for invalid config", func(t *testing.T) { + location := "../fixtures/invalid_config.yml" + config, err := GetConfig(location) + + if err == nil { + t.Fatal("expected error, got none") + } + if config != nil { + t.Fatal("expected config to be nil") + } + }) +} + +func TestGetLogstashUrls(t *testing.T) { + t.Run("gets Logstash URLs correctly", func(t *testing.T) { + config := &Config{ + Logstash: LogstashConfig{ + Servers: []LogstashServer{{URL: "http://localhost:9601"}}, + }, + } + + urls := config.GetLogstashUrls() + expectedUrls := []string{"http://localhost:9601"} + + if !reflect.DeepEqual(urls, expectedUrls) { + t.Errorf("expected urls to be %v, got %v", expectedUrls, urls) + } + }) +} diff --git a/config/logstash_config.go b/config/logstash_config.go deleted file mode 100644 index c684410b..00000000 --- a/config/logstash_config.go +++ /dev/null @@ -1,8 +0,0 @@ -package config - -var ( - // LogstashUrl is the URL of the Logstash instance to be monitored. - // Defaults to http://localhost:9600 - // Can be overridden by setting the LOGSTASH_URL environment variable - LogstashUrl = getEnvWithDefault("LOGSTASH_URL", "http://localhost:9600") -) diff --git a/config/server_config.go b/config/server_config.go deleted file mode 100644 index 55cf190d..00000000 --- a/config/server_config.go +++ /dev/null @@ -1,17 +0,0 @@ -package config - -var ( - // Port is the port the exporter will listen on. - // Defaults to 9198 - // Can be overridden by setting the PORT environment variable - Port = getEnvWithDefault("PORT", "9198") - - // Host is the host the exporter will listen on. - // Defaults to an empty string, which will listen on all interfaces - // Can be overridden by setting the HOST environment variable - // For windows, use "localhost", because an empty string will not work - // with the default windows firewall configuration. - // Alternatively you can change the firewall configuration to allow - // connections to the port from all interfaces. - Host = getEnvWithDefault("HOST", "") -) diff --git a/fixtures/invalid_config.toml b/fixtures/invalid_config.toml new file mode 100644 index 00000000..d6f905d7 --- /dev/null +++ b/fixtures/invalid_config.toml @@ -0,0 +1,2 @@ +[config] +theConfigShouldBe = 'yaml' diff --git a/fixtures/valid_config.yml b/fixtures/valid_config.yml new file mode 100644 index 00000000..48ddeb0d --- /dev/null +++ b/fixtures/valid_config.yml @@ -0,0 +1,8 @@ +logstash: + servers: + - url: "http://localhost:9601" +server: + host: "127.0.0.1" + port: 9200 +logging: + level: "debug" diff --git a/go.mod b/go.mod index 6f60f230..1c348a18 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ toolchain go1.21.4 require ( github.com/joho/godotenv v1.5.1 github.com/prometheus/client_golang v1.17.0 + gopkg.in/yaml.v2 v2.4.0 ) require ( diff --git a/go.sum b/go.sum index f51346af..b7fe8b60 100644 --- a/go.sum +++ b/go.sum @@ -53,3 +53,8 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/server/healthcheck.go b/server/healthcheck.go index 3dc636e7..8276f836 100644 --- a/server/healthcheck.go +++ b/server/healthcheck.go @@ -2,31 +2,55 @@ package server import ( "context" + "fmt" "net/http" + "sync" "github.com/kuskoman/logstash-exporter/config" ) -func getHealthCheck(logstashURL string) func(http.ResponseWriter, *http.Request) { +func getHealthCheck(logstashUrls []string) func(http.ResponseWriter, *http.Request) { client := &http.Client{} + return func(w http.ResponseWriter, r *http.Request) { - ctx, cancel := context.WithTimeout(r.Context(), config.HttpTimeout) - defer cancel() + var wg sync.WaitGroup + errorsChan := make(chan error, len(logstashUrls)) - req, err := http.NewRequestWithContext(ctx, http.MethodGet, logstashURL, nil) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - return - } + for _, url := range logstashUrls { + wg.Add(1) + go func(url string) { + defer wg.Done() + ctx, cancel := context.WithTimeout(r.Context(), config.HttpTimeout) + defer cancel() - resp, err := client.Do(req) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - return + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + if err != nil { + errorsChan <- fmt.Errorf("error creating request for %s: %v", url, err) + return + } + + resp, err := client.Do(req) + if err != nil { + errorsChan <- fmt.Errorf("error making request to %s: %v", url, err) + return + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + errorsChan <- fmt.Errorf("%s returned status %d", url, resp.StatusCode) + return + } + }(url) } - if resp.StatusCode != http.StatusOK { + wg.Wait() + close(errorsChan) + + if len(errorsChan) > 0 { w.WriteHeader(http.StatusInternalServerError) + for err := range errorsChan { + http.Error(w, err.Error(), http.StatusInternalServerError) + } return } diff --git a/server/healthcheck_test.go b/server/healthcheck_test.go index 28fec3ef..fc6bc05f 100644 --- a/server/healthcheck_test.go +++ b/server/healthcheck_test.go @@ -7,13 +7,17 @@ import ( ) func TestHealthCheck(t *testing.T) { - runTest := func(mockStatus int, expectedStatus int) { - mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(mockStatus) - })) - defer mockServer.Close() + runTest := func(mockStatuses []int, expectedStatus int) { + var urls []string + for _, status := range mockStatuses { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(status) + })) + defer server.Close() + urls = append(urls, server.URL) + } - handler := getHealthCheck(mockServer.URL) + handler := getHealthCheck(urls) req, err := http.NewRequest(http.MethodGet, "/", nil) if err != nil { t.Fatalf("Error creating request: %v", err) @@ -27,20 +31,28 @@ func TestHealthCheck(t *testing.T) { } } - t.Run("500 status", func(t *testing.T) { - runTest(http.StatusInternalServerError, http.StatusInternalServerError) + t.Run("single 500 status", func(t *testing.T) { + runTest([]int{http.StatusInternalServerError}, http.StatusInternalServerError) + }) + + t.Run("single 200 status", func(t *testing.T) { + runTest([]int{http.StatusOK}, http.StatusOK) + }) + + t.Run("single 404 status", func(t *testing.T) { + runTest([]int{http.StatusNotFound}, http.StatusInternalServerError) }) - t.Run("200 status", func(t *testing.T) { - runTest(http.StatusOK, http.StatusOK) + t.Run("multiple instances, mixed statuses", func(t *testing.T) { + runTest([]int{http.StatusOK, http.StatusNotFound, http.StatusInternalServerError}, http.StatusInternalServerError) }) - t.Run("404 status", func(t *testing.T) { - runTest(http.StatusNotFound, http.StatusInternalServerError) + t.Run("multiple instances, all OK", func(t *testing.T) { + runTest([]int{http.StatusOK, http.StatusOK, http.StatusOK}, http.StatusOK) }) t.Run("no response", func(t *testing.T) { - handler := getHealthCheck("http://localhost:12345") + handler := getHealthCheck([]string{"http://localhost:12345"}) req, err := http.NewRequest(http.MethodGet, "/", nil) if err != nil { t.Fatalf("Error creating request: %v", err) @@ -55,7 +67,7 @@ func TestHealthCheck(t *testing.T) { }) t.Run("invalid url", func(t *testing.T) { - handler := getHealthCheck("http://localhost:96010:invalidurl") + handler := getHealthCheck([]string{"http://localhost:96010:invalidurl"}) req, err := http.NewRequest(http.MethodGet, "/", nil) if err != nil { t.Fatalf("Error creating request: %v", err) diff --git a/server/server.go b/server/server.go index ab67af2a..18c655fb 100644 --- a/server/server.go +++ b/server/server.go @@ -12,13 +12,13 @@ import ( // and registers the prometheus handler and the healthcheck handler // to the server's mux. The prometheus handler is managed under the // hood by the prometheus client library. -func NewAppServer(host, port string) *http.Server { +func NewAppServer(host, port string, cfg *config.Config) *http.Server { mux := http.NewServeMux() mux.Handle("/metrics", promhttp.Handler()) mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/metrics", http.StatusMovedPermanently) }) - mux.HandleFunc("/healthcheck", getHealthCheck(config.LogstashUrl)) + mux.HandleFunc("/healthcheck", getHealthCheck(cfg.GetLogstashUrls())) mux.HandleFunc("/version", getVersionInfoHandler(config.GetVersionInfo())) listenUrl := fmt.Sprintf("%s:%s", host, port) diff --git a/server/server_test.go b/server/server_test.go index d262e0b6..036b10d9 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -1,17 +1,21 @@ package server import ( + "fmt" "net/http" "net/http/httptest" "testing" + + "github.com/kuskoman/logstash-exporter/config" ) func TestNewAppServer(t *testing.T) { - t.Run("Test handling of /metrics endpoint", func(t *testing.T) { - server := NewAppServer("", "8080") + t.Run("test handling of /metrics endpoint", func(t *testing.T) { + cfg := &config.Config{} + server := NewAppServer("", "8080", cfg) req, err := http.NewRequest("GET", "/metrics", nil) if err != nil { - t.Fatal(err) + t.Fatal(fmt.Errorf("error creating request: %v", err)) } rr := httptest.NewRecorder() server.Handler.ServeHTTP(rr, req) @@ -20,11 +24,12 @@ func TestNewAppServer(t *testing.T) { } }) - t.Run("Test handling of / endpoint", func(t *testing.T) { - server := NewAppServer("", "8080") + t.Run("test handling of / endpoint", func(t *testing.T) { + cfg := &config.Config{} + server := NewAppServer("", "8080", cfg) req, err := http.NewRequest("GET", "/", nil) if err != nil { - t.Fatal(err) + t.Fatal(fmt.Errorf("error creating request: %v", err)) } rr := httptest.NewRecorder() server.Handler.ServeHTTP(rr, req) @@ -35,4 +40,39 @@ func TestNewAppServer(t *testing.T) { t.Errorf("unexpected redirect location: got %v want %v", location, "/metrics") } }) + + t.Run("test handling of /healthcheck endpoint", func(t *testing.T) { + cfg := &config.Config{ + Logstash: config.LogstashConfig{ + Servers: []config.LogstashServer{ + {URL: "http://localhost:1234"}, + }, + }, + } + server := NewAppServer("", "8080", cfg) + req, err := http.NewRequest("GET", "/healthcheck", nil) + if err != nil { + t.Fatal(fmt.Errorf("error creating request: %v", err)) + } + rr := httptest.NewRecorder() + server.Handler.ServeHTTP(rr, req) + // Assuming the localhost:1234 is not serving, so the healthcheck should return InternalServerError + if status := rr.Code; status != http.StatusInternalServerError { + t.Errorf("unexpected status code: got %v want %v", status, http.StatusInternalServerError) + } + }) + + t.Run("test handling of /version endpoint", func(t *testing.T) { + cfg := &config.Config{} + server := NewAppServer("", "8080", cfg) + req, err := http.NewRequest("GET", "/version", nil) + if err != nil { + t.Fatal(fmt.Errorf("error creating request: %v", err)) + } + rr := httptest.NewRecorder() + server.Handler.ServeHTTP(rr, req) + if status := rr.Code; status != http.StatusOK { + t.Errorf("unexpected status code: got %v want %v", status, http.StatusOK) + } + }) } From 80f28020c1a4b5786a8011f0f8016a383d1cda25 Mon Sep 17 00:00:00 2001 From: Jakub Date: Sat, 30 Sep 2023 13:17:52 +0200 Subject: [PATCH 02/36] Start implementing collect for multiple endpoints --- .gitignore | 3 ++ cmd/exporter/main.go | 9 ++---- collectors/collector_manager.go | 43 ++++++++++++++-------------- collectors/collector_manager_test.go | 42 ++++++++++++++++++--------- 4 files changed, 55 insertions(+), 42 deletions(-) diff --git a/.gitignore b/.gitignore index 9d66978a..4ed02a67 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,6 @@ _debug*.yaml # Readme generator files helm-generator/ + +# local config +config.yml diff --git a/cmd/exporter/main.go b/cmd/exporter/main.go index 9db5da1c..5e678d70 100644 --- a/cmd/exporter/main.go +++ b/cmd/exporter/main.go @@ -48,13 +48,8 @@ func main() { versionInfo := config.GetVersionInfo() slog.Info(versionInfo.String()) - for _, logstashServerConfig := range exporterConfig.Logstash.Servers { - logstashUrl := logstashServerConfig.URL - slog.Info("booting collector manager for", "logstashUrl", logstashUrl) - - collectorManager := collectors.NewCollectorManager(logstashUrl) - prometheus.MustRegister(collectorManager) - } + collectorManager := collectors.NewCollectorManager(exporterConfig.GetLogstashUrls()) + prometheus.MustRegister(collectorManager) appServer := server.NewAppServer(host, port, exporterConfig) diff --git a/collectors/collector_manager.go b/collectors/collector_manager.go index d1cf446f..90d09411 100644 --- a/collectors/collector_manager.go +++ b/collectors/collector_manager.go @@ -2,10 +2,11 @@ package collectors import ( "context" - "log/slog" "sync" "time" + "log/slog" + "github.com/kuskoman/logstash-exporter/collectors/nodeinfo" "github.com/kuskoman/logstash-exporter/collectors/nodestats" "github.com/kuskoman/logstash-exporter/config" @@ -20,14 +21,17 @@ type Collector interface { // CollectorManager is a collector that executes all other collectors type CollectorManager struct { - collectors map[string]Collector + collectors map[string]map[string]Collector // map[endpoint]map[collectorName]Collector scrapeDurations *prometheus.SummaryVec } -func NewCollectorManager(endpoint string) *CollectorManager { - client := logstashclient.NewClient(endpoint) +func NewCollectorManager(endpoints []string) *CollectorManager { + collectors := make(map[string]map[string]Collector) - collectors := getCollectors(client) + for _, endpoint := range endpoints { + client := logstashclient.NewClient(endpoint) + collectors[endpoint] = getCollectors(client) + } scrapeDurations := getScrapeDurationsCollector() prometheus.MustRegister(version.NewCollector("logstash_exporter")) @@ -42,22 +46,19 @@ func getCollectors(client logstashclient.Client) map[string]Collector { return collectors } -// Collect executes all collectors and sends the collected metrics to the provided channel. -// It also sends the duration of the collection to the scrapeDurations collector. func (manager *CollectorManager) Collect(ch chan<- prometheus.Metric) { ctx, cancel := context.WithTimeout(context.Background(), config.HttpTimeout) - defer cancel() waitGroup := sync.WaitGroup{} - waitGroup.Add(len(manager.collectors)) - for name, collector := range manager.collectors { - go func(name string, collector Collector) { - slog.Debug("executing collector", "name", name) - manager.executeCollector(name, ctx, collector, ch) - slog.Debug("collector finished", "name", name) - waitGroup.Done() - }(name, collector) + for endpoint, endpointCollectors := range manager.collectors { + waitGroup.Add(len(endpointCollectors)) + for name, collector := range endpointCollectors { + go func(name string, endpoint string, collector Collector) { + manager.executeCollector(name, endpoint, ctx, collector, ch) + waitGroup.Done() + }(name, endpoint, collector) + } } waitGroup.Wait() } @@ -66,21 +67,21 @@ func (manager *CollectorManager) Describe(ch chan<- *prometheus.Desc) { manager.scrapeDurations.Describe(ch) } -func (manager *CollectorManager) executeCollector(name string, ctx context.Context, collector Collector, ch chan<- prometheus.Metric) { +func (manager *CollectorManager) executeCollector(name string, endpoint string, ctx context.Context, collector Collector, ch chan<- prometheus.Metric) { executionStart := time.Now() err := collector.Collect(ctx, ch) executionDuration := time.Since(executionStart) var executionStatus string if err != nil { - slog.Error("executor failed", "name", name, "duration", executionDuration, "err", err) + slog.Error("executor failed", "name", name, "endpoint", endpoint, "duration", executionDuration, "err", err) executionStatus = "error" } else { - slog.Debug("executor succeeded", "name", name, "duration", executionDuration) + slog.Debug("executor succeeded", "name", name, "endpoint", endpoint, "duration", executionDuration) executionStatus = "success" } - manager.scrapeDurations.WithLabelValues(name, executionStatus).Observe(executionDuration.Seconds()) + manager.scrapeDurations.WithLabelValues(name, endpoint, executionStatus).Observe(executionDuration.Seconds()) } func getScrapeDurationsCollector() *prometheus.SummaryVec { @@ -91,7 +92,7 @@ func getScrapeDurationsCollector() *prometheus.SummaryVec { Name: "scrape_duration_seconds", Help: "logstash_exporter: Duration of a scrape job.", }, - []string{"collector", "result"}, + []string{"collector", "endpoint", "result"}, ) return scrapeDurations diff --git a/collectors/collector_manager_test.go b/collectors/collector_manager_test.go index 58131155..69140813 100644 --- a/collectors/collector_manager_test.go +++ b/collectors/collector_manager_test.go @@ -10,12 +10,25 @@ import ( ) func TestNewCollectorManager(t *testing.T) { - mockEndpoint := "http://localhost:9600" - cm := NewCollectorManager(mockEndpoint) + t.Parallel() - if cm == nil { - t.Error("Expected collector manager to be initialized") - } + t.Run("single instance", func(t *testing.T) { + mockEndpoint := "http://localhost:9600" + cm := NewCollectorManager([]string{mockEndpoint}) + + if cm == nil { + t.Error("expected collector manager to be initialized") + } + }) + + t.Run("multiple instances", func(t *testing.T) { + mockEndpoints := []string{"http://localhost:9600", "http://localhost:9601"} + cm := NewCollectorManager(mockEndpoints) + + if cm == nil { + t.Error("expected collector manager to be initialized") + } + }) } type mockCollector struct { @@ -43,15 +56,15 @@ func (m *mockCollector) Collect(ctx context.Context, ch chan<- prometheus.Metric func TestCollect(t *testing.T) { t.Run("should fail", func(t *testing.T) { + mockEndpoint := "http://localhost:9600" cm := &CollectorManager{ - collectors: map[string]Collector{ - "mock": newMockCollector(true), + collectors: map[string]map[string]Collector{ + mockEndpoint: {"mock": newMockCollector(true)}, }, scrapeDurations: getScrapeDurationsCollector(), } ch := make(chan prometheus.Metric) - var wg sync.WaitGroup wg.Add(1) go func() { @@ -70,14 +83,14 @@ func TestCollect(t *testing.T) { }() return done }(): - // No metric was sent to the channel, which is expected. } }) t.Run("should succeed", func(t *testing.T) { + mockEndpoint := "http://localhost:9600" cm := &CollectorManager{ - collectors: map[string]Collector{ - "mock": newMockCollector(false), + collectors: map[string]map[string]Collector{ + mockEndpoint: {"mock": newMockCollector(false)}, }, scrapeDurations: getScrapeDurationsCollector(), } @@ -96,9 +109,10 @@ func TestCollect(t *testing.T) { } func TestDescribe(t *testing.T) { + mockEndpoint := "http://localhost:9600" cm := &CollectorManager{ - collectors: map[string]Collector{ - "mock": newMockCollector(false), + collectors: map[string]map[string]Collector{ + mockEndpoint: {"mock": newMockCollector(false)}, }, scrapeDurations: getScrapeDurationsCollector(), } @@ -107,7 +121,7 @@ func TestDescribe(t *testing.T) { cm.Describe(ch) desc := <-ch - expectedDesc := "Desc{fqName: \"logstash_exporter_scrape_duration_seconds\", help: \"logstash_exporter: Duration of a scrape job.\", constLabels: {}, variableLabels: {collector,result}}" + expectedDesc := "Desc{fqName: \"logstash_exporter_scrape_duration_seconds\", help: \"logstash_exporter: Duration of a scrape job.\", constLabels: {}, variableLabels: {collector,endpoint,result}}" if desc.String() != expectedDesc { t.Errorf("Expected metric description to be '%s', got %s", expectedDesc, desc.String()) } From bc72b52d61a31ce0f0f0d45dc58d3f3016283c9b Mon Sep 17 00:00:00 2001 From: Jakub Date: Sat, 30 Sep 2023 13:58:32 +0200 Subject: [PATCH 03/36] Revert "Start implementing collect for multiple endpoints" This reverts commit fa177a0768584f573cf314dd91ba30dbb0c4943e. --- .gitignore | 3 -- cmd/exporter/main.go | 9 ++++-- collectors/collector_manager.go | 43 ++++++++++++++-------------- collectors/collector_manager_test.go | 42 +++++++++------------------ 4 files changed, 42 insertions(+), 55 deletions(-) diff --git a/.gitignore b/.gitignore index 4ed02a67..9d66978a 100644 --- a/.gitignore +++ b/.gitignore @@ -42,6 +42,3 @@ _debug*.yaml # Readme generator files helm-generator/ - -# local config -config.yml diff --git a/cmd/exporter/main.go b/cmd/exporter/main.go index 5e678d70..9db5da1c 100644 --- a/cmd/exporter/main.go +++ b/cmd/exporter/main.go @@ -48,8 +48,13 @@ func main() { versionInfo := config.GetVersionInfo() slog.Info(versionInfo.String()) - collectorManager := collectors.NewCollectorManager(exporterConfig.GetLogstashUrls()) - prometheus.MustRegister(collectorManager) + for _, logstashServerConfig := range exporterConfig.Logstash.Servers { + logstashUrl := logstashServerConfig.URL + slog.Info("booting collector manager for", "logstashUrl", logstashUrl) + + collectorManager := collectors.NewCollectorManager(logstashUrl) + prometheus.MustRegister(collectorManager) + } appServer := server.NewAppServer(host, port, exporterConfig) diff --git a/collectors/collector_manager.go b/collectors/collector_manager.go index 90d09411..d1cf446f 100644 --- a/collectors/collector_manager.go +++ b/collectors/collector_manager.go @@ -2,11 +2,10 @@ package collectors import ( "context" + "log/slog" "sync" "time" - "log/slog" - "github.com/kuskoman/logstash-exporter/collectors/nodeinfo" "github.com/kuskoman/logstash-exporter/collectors/nodestats" "github.com/kuskoman/logstash-exporter/config" @@ -21,17 +20,14 @@ type Collector interface { // CollectorManager is a collector that executes all other collectors type CollectorManager struct { - collectors map[string]map[string]Collector // map[endpoint]map[collectorName]Collector + collectors map[string]Collector scrapeDurations *prometheus.SummaryVec } -func NewCollectorManager(endpoints []string) *CollectorManager { - collectors := make(map[string]map[string]Collector) +func NewCollectorManager(endpoint string) *CollectorManager { + client := logstashclient.NewClient(endpoint) - for _, endpoint := range endpoints { - client := logstashclient.NewClient(endpoint) - collectors[endpoint] = getCollectors(client) - } + collectors := getCollectors(client) scrapeDurations := getScrapeDurationsCollector() prometheus.MustRegister(version.NewCollector("logstash_exporter")) @@ -46,19 +42,22 @@ func getCollectors(client logstashclient.Client) map[string]Collector { return collectors } +// Collect executes all collectors and sends the collected metrics to the provided channel. +// It also sends the duration of the collection to the scrapeDurations collector. func (manager *CollectorManager) Collect(ch chan<- prometheus.Metric) { ctx, cancel := context.WithTimeout(context.Background(), config.HttpTimeout) + defer cancel() waitGroup := sync.WaitGroup{} - for endpoint, endpointCollectors := range manager.collectors { - waitGroup.Add(len(endpointCollectors)) - for name, collector := range endpointCollectors { - go func(name string, endpoint string, collector Collector) { - manager.executeCollector(name, endpoint, ctx, collector, ch) - waitGroup.Done() - }(name, endpoint, collector) - } + waitGroup.Add(len(manager.collectors)) + for name, collector := range manager.collectors { + go func(name string, collector Collector) { + slog.Debug("executing collector", "name", name) + manager.executeCollector(name, ctx, collector, ch) + slog.Debug("collector finished", "name", name) + waitGroup.Done() + }(name, collector) } waitGroup.Wait() } @@ -67,21 +66,21 @@ func (manager *CollectorManager) Describe(ch chan<- *prometheus.Desc) { manager.scrapeDurations.Describe(ch) } -func (manager *CollectorManager) executeCollector(name string, endpoint string, ctx context.Context, collector Collector, ch chan<- prometheus.Metric) { +func (manager *CollectorManager) executeCollector(name string, ctx context.Context, collector Collector, ch chan<- prometheus.Metric) { executionStart := time.Now() err := collector.Collect(ctx, ch) executionDuration := time.Since(executionStart) var executionStatus string if err != nil { - slog.Error("executor failed", "name", name, "endpoint", endpoint, "duration", executionDuration, "err", err) + slog.Error("executor failed", "name", name, "duration", executionDuration, "err", err) executionStatus = "error" } else { - slog.Debug("executor succeeded", "name", name, "endpoint", endpoint, "duration", executionDuration) + slog.Debug("executor succeeded", "name", name, "duration", executionDuration) executionStatus = "success" } - manager.scrapeDurations.WithLabelValues(name, endpoint, executionStatus).Observe(executionDuration.Seconds()) + manager.scrapeDurations.WithLabelValues(name, executionStatus).Observe(executionDuration.Seconds()) } func getScrapeDurationsCollector() *prometheus.SummaryVec { @@ -92,7 +91,7 @@ func getScrapeDurationsCollector() *prometheus.SummaryVec { Name: "scrape_duration_seconds", Help: "logstash_exporter: Duration of a scrape job.", }, - []string{"collector", "endpoint", "result"}, + []string{"collector", "result"}, ) return scrapeDurations diff --git a/collectors/collector_manager_test.go b/collectors/collector_manager_test.go index 69140813..58131155 100644 --- a/collectors/collector_manager_test.go +++ b/collectors/collector_manager_test.go @@ -10,25 +10,12 @@ import ( ) func TestNewCollectorManager(t *testing.T) { - t.Parallel() - - t.Run("single instance", func(t *testing.T) { - mockEndpoint := "http://localhost:9600" - cm := NewCollectorManager([]string{mockEndpoint}) - - if cm == nil { - t.Error("expected collector manager to be initialized") - } - }) - - t.Run("multiple instances", func(t *testing.T) { - mockEndpoints := []string{"http://localhost:9600", "http://localhost:9601"} - cm := NewCollectorManager(mockEndpoints) + mockEndpoint := "http://localhost:9600" + cm := NewCollectorManager(mockEndpoint) - if cm == nil { - t.Error("expected collector manager to be initialized") - } - }) + if cm == nil { + t.Error("Expected collector manager to be initialized") + } } type mockCollector struct { @@ -56,15 +43,15 @@ func (m *mockCollector) Collect(ctx context.Context, ch chan<- prometheus.Metric func TestCollect(t *testing.T) { t.Run("should fail", func(t *testing.T) { - mockEndpoint := "http://localhost:9600" cm := &CollectorManager{ - collectors: map[string]map[string]Collector{ - mockEndpoint: {"mock": newMockCollector(true)}, + collectors: map[string]Collector{ + "mock": newMockCollector(true), }, scrapeDurations: getScrapeDurationsCollector(), } ch := make(chan prometheus.Metric) + var wg sync.WaitGroup wg.Add(1) go func() { @@ -83,14 +70,14 @@ func TestCollect(t *testing.T) { }() return done }(): + // No metric was sent to the channel, which is expected. } }) t.Run("should succeed", func(t *testing.T) { - mockEndpoint := "http://localhost:9600" cm := &CollectorManager{ - collectors: map[string]map[string]Collector{ - mockEndpoint: {"mock": newMockCollector(false)}, + collectors: map[string]Collector{ + "mock": newMockCollector(false), }, scrapeDurations: getScrapeDurationsCollector(), } @@ -109,10 +96,9 @@ func TestCollect(t *testing.T) { } func TestDescribe(t *testing.T) { - mockEndpoint := "http://localhost:9600" cm := &CollectorManager{ - collectors: map[string]map[string]Collector{ - mockEndpoint: {"mock": newMockCollector(false)}, + collectors: map[string]Collector{ + "mock": newMockCollector(false), }, scrapeDurations: getScrapeDurationsCollector(), } @@ -121,7 +107,7 @@ func TestDescribe(t *testing.T) { cm.Describe(ch) desc := <-ch - expectedDesc := "Desc{fqName: \"logstash_exporter_scrape_duration_seconds\", help: \"logstash_exporter: Duration of a scrape job.\", constLabels: {}, variableLabels: {collector,endpoint,result}}" + expectedDesc := "Desc{fqName: \"logstash_exporter_scrape_duration_seconds\", help: \"logstash_exporter: Duration of a scrape job.\", constLabels: {}, variableLabels: {collector,result}}" if desc.String() != expectedDesc { t.Errorf("Expected metric description to be '%s', got %s", expectedDesc, desc.String()) } From 04ee79ab22b090e74d2f4903a1a62e68c8fc61ef Mon Sep 17 00:00:00 2001 From: Jakub Date: Sat, 30 Sep 2023 13:58:52 +0200 Subject: [PATCH 04/36] Add config.yml to gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 9d66978a..52a51b65 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,6 @@ _debug*.yaml # Readme generator files helm-generator/ + +# Local config.yml +config.yml From c5987af539b636da1c4eb03683a4218662b903ab Mon Sep 17 00:00:00 2001 From: Jakub Date: Sat, 30 Sep 2023 16:29:53 +0200 Subject: [PATCH 05/36] Add GetEndpoint() to clients --- collectors/nodeinfo/nodeinfo_collector_test.go | 8 ++++++++ collectors/nodestats/nodestats_collector_test.go | 12 ++++++++++-- fetcher/logstash_client/client.go | 6 ++++++ 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/collectors/nodeinfo/nodeinfo_collector_test.go b/collectors/nodeinfo/nodeinfo_collector_test.go index 62755c1b..bb6aa969 100644 --- a/collectors/nodeinfo/nodeinfo_collector_test.go +++ b/collectors/nodeinfo/nodeinfo_collector_test.go @@ -35,6 +35,10 @@ func (m *mockClient) GetNodeStats(ctx context.Context) (*responses.NodeStatsResp return nil, nil } +func (m *mockClient) GetEndpoint() string { + return "" +} + type errorMockClient struct{} func (m *errorMockClient) GetNodeInfo(ctx context.Context) (*responses.NodeInfoResponse, error) { @@ -45,6 +49,10 @@ func (m *errorMockClient) GetNodeStats(ctx context.Context) (*responses.NodeStat return nil, nil } +func (m *errorMockClient) GetEndpoint() string { + return "" +} + func TestCollectNotNil(t *testing.T) { collector := NewNodeinfoCollector(&mockClient{}) ch := make(chan prometheus.Metric) diff --git a/collectors/nodestats/nodestats_collector_test.go b/collectors/nodestats/nodestats_collector_test.go index 66381b04..857bb610 100644 --- a/collectors/nodestats/nodestats_collector_test.go +++ b/collectors/nodestats/nodestats_collector_test.go @@ -35,6 +35,10 @@ func (m *mockClient) GetNodeInfo(ctx context.Context) (*responses.NodeInfoRespon return nil, nil } +func (m *mockClient) GetEndpoint() string { + return "" +} + type errorMockClient struct{} func (m *errorMockClient) GetNodeInfo(ctx context.Context) (*responses.NodeInfoResponse, error) { @@ -45,6 +49,10 @@ func (m *errorMockClient) GetNodeStats(ctx context.Context) (*responses.NodeStat return nil, errors.New("could not connect to instance") } +func (m *errorMockClient) GetEndpoint() string { + return "" +} + func TestCollectNotNil(t *testing.T) { collector := NewNodestatsCollector(&mockClient{}) ch := make(chan prometheus.Metric) @@ -86,8 +94,8 @@ func TestCollectNotNil(t *testing.T) { "logstash_stats_pipeline_plugin_events_queue_push_duration", "logstash_stats_pipeline_plugin_documents_successes", "logstash_stats_pipeline_plugin_documents_non_retryable_failures", - "logstash_stats_pipeline_plugin_bulk_requests_errors", - "logstash_stats_pipeline_plugin_bulk_requests_responses", + "logstash_stats_pipeline_plugin_bulk_requests_errors", + "logstash_stats_pipeline_plugin_bulk_requests_responses", "logstash_stats_process_cpu_percent", "logstash_stats_process_cpu_total_millis", "logstash_stats_process_cpu_load_average_1m", diff --git a/fetcher/logstash_client/client.go b/fetcher/logstash_client/client.go index bced14a7..b1b0658e 100644 --- a/fetcher/logstash_client/client.go +++ b/fetcher/logstash_client/client.go @@ -12,6 +12,8 @@ import ( type Client interface { GetNodeInfo(ctx context.Context) (*responses.NodeInfoResponse, error) GetNodeStats(ctx context.Context) (*responses.NodeStatsResponse, error) + + GetEndpoint() string } // DefaultClient is the default implementation of the Client interface @@ -20,6 +22,10 @@ type DefaultClient struct { endpoint string } +func (client *DefaultClient) GetEndpoint() string { + return client.endpoint +} + const defaultLogstashEndpoint = "http://localhost:9600" // NewClient returns a new instance of the DefaultClient configured with the given endpoint From a071323fe0aae54073edb73380dd519a4f49c776 Mon Sep 17 00:00:00 2001 From: Jakub Date: Sat, 30 Sep 2023 16:39:45 +0200 Subject: [PATCH 06/36] Delete descHelper.NewDescWithHelp method --- collectors/nodeinfo/nodeinfo_collector.go | 6 +-- collectors/nodestats/nodestats_collector.go | 48 ++++++++++----------- prometheus_helper/prometheus_helper.go | 5 --- prometheus_helper/prometheus_helper_test.go | 10 +---- 4 files changed, 28 insertions(+), 41 deletions(-) diff --git a/collectors/nodeinfo/nodeinfo_collector.go b/collectors/nodeinfo/nodeinfo_collector.go index 4270666e..d76556bd 100644 --- a/collectors/nodeinfo/nodeinfo_collector.go +++ b/collectors/nodeinfo/nodeinfo_collector.go @@ -55,13 +55,13 @@ func NewNodeinfoCollector(client logstashclient.Client) *NodeinfoCollector { nil, ), - PipelineWorkers: descHelper.NewDescWithHelp("pipeline_workers", + PipelineWorkers: descHelper.NewDescWithHelpAndLabels("pipeline_workers", "Number of worker threads that will process pipeline events.", ), - PipelineBatchSize: descHelper.NewDescWithHelp("pipeline_batch_size", + PipelineBatchSize: descHelper.NewDescWithHelpAndLabels("pipeline_batch_size", "Number of events to retrieve from the input queue before sending to the filter and output stages.", ), - PipelineBatchDelay: descHelper.NewDescWithHelp("pipeline_batch_delay", + PipelineBatchDelay: descHelper.NewDescWithHelpAndLabels("pipeline_batch_delay", "Amount of time to wait for events to fill the batch before sending to the filter and output stages.", ), diff --git a/collectors/nodestats/nodestats_collector.go b/collectors/nodestats/nodestats_collector.go index 1cb826f1..0d0f7548 100644 --- a/collectors/nodestats/nodestats_collector.go +++ b/collectors/nodestats/nodestats_collector.go @@ -78,14 +78,14 @@ func NewNodestatsCollector(client logstashclient.Client) *NodestatsCollector { pipelineSubcollector: NewPipelineSubcollector(), - JvmThreadsCount: descHelper.NewDescWithHelp("jvm_threads_count", "Number of live threads including both daemon and non-daemon threads."), - JvmThreadsPeakCount: descHelper.NewDescWithHelp("jvm_threads_peak_count", "Peak live thread count since the Java virtual machine started or peak was reset."), + JvmThreadsCount: descHelper.NewDescWithHelpAndLabels("jvm_threads_count", "Number of live threads including both daemon and non-daemon threads."), + JvmThreadsPeakCount: descHelper.NewDescWithHelpAndLabels("jvm_threads_peak_count", "Peak live thread count since the Java virtual machine started or peak was reset."), - JvmMemHeapUsedPercent: descHelper.NewDescWithHelp("jvm_mem_heap_used_percent", "Percentage of the heap memory that is used."), - JvmMemHeapCommittedBytes: descHelper.NewDescWithHelp("jvm_mem_heap_committed_bytes", "Amount of heap memory in bytes that is committed for the Java virtual machine to use."), - JvmMemHeapMaxBytes: descHelper.NewDescWithHelp("jvm_mem_heap_max_bytes", "Maximum amount of heap memory in bytes that can be used for memory management."), - JvmMemHeapUsedBytes: descHelper.NewDescWithHelp("jvm_mem_heap_used_bytes", "Amount of used heap memory in bytes."), - JvmMemNonHeapCommittedBytes: descHelper.NewDescWithHelp("jvm_mem_non_heap_committed_bytes", "Amount of non-heap memory in bytes that is committed for the Java virtual machine to use."), + JvmMemHeapUsedPercent: descHelper.NewDescWithHelpAndLabels("jvm_mem_heap_used_percent", "Percentage of the heap memory that is used."), + JvmMemHeapCommittedBytes: descHelper.NewDescWithHelpAndLabels("jvm_mem_heap_committed_bytes", "Amount of heap memory in bytes that is committed for the Java virtual machine to use."), + JvmMemHeapMaxBytes: descHelper.NewDescWithHelpAndLabels("jvm_mem_heap_max_bytes", "Maximum amount of heap memory in bytes that can be used for memory management."), + JvmMemHeapUsedBytes: descHelper.NewDescWithHelpAndLabels("jvm_mem_heap_used_bytes", "Amount of used heap memory in bytes."), + JvmMemNonHeapCommittedBytes: descHelper.NewDescWithHelpAndLabels("jvm_mem_non_heap_committed_bytes", "Amount of non-heap memory in bytes that is committed for the Java virtual machine to use."), JvmMemPoolPeakUsedInBytes: descHelper.NewDescWithHelpAndLabels( "jvm_mem_pool_peak_used_bytes", "Peak used bytes of a given JVM memory pool.", "pool"), @@ -98,28 +98,28 @@ func NewNodestatsCollector(client logstashclient.Client) *NodestatsCollector { JvmMemPoolCommittedInBytes: descHelper.NewDescWithHelpAndLabels( "jvm_mem_pool_committed_bytes", "Amount of bytes that are committed for the Java virtual machine to use in a given JVM memory pool.", "pool"), - JvmUptimeMillis: descHelper.NewDescWithHelp("jvm_uptime_millis", "Uptime of the JVM in milliseconds."), + JvmUptimeMillis: descHelper.NewDescWithHelpAndLabels("jvm_uptime_millis", "Uptime of the JVM in milliseconds."), - ProcessOpenFileDescriptors: descHelper.NewDescWithHelp("process_open_file_descriptors", "Number of currently open file descriptors."), - ProcessMaxFileDescriptors: descHelper.NewDescWithHelp("process_max_file_descriptors", "Limit of open file descriptors."), - ProcessCpuPercent: descHelper.NewDescWithHelp("process_cpu_percent", "CPU usage of the process."), - ProcessCpuTotalMillis: descHelper.NewDescWithHelp("process_cpu_total_millis", "Total CPU time used by the process."), - ProcessCpuLoadAverageOneM: descHelper.NewDescWithHelp("process_cpu_load_average_1m", "Total 1m system load average."), - ProcessCpuLoadAverageFiveM: descHelper.NewDescWithHelp("process_cpu_load_average_5m", "Total 5m system load average."), - ProcessCpuLoadAverageFifteenM: descHelper.NewDescWithHelp("process_cpu_load_average_15m", "Total 15m system load average."), + ProcessOpenFileDescriptors: descHelper.NewDescWithHelpAndLabels("process_open_file_descriptors", "Number of currently open file descriptors."), + ProcessMaxFileDescriptors: descHelper.NewDescWithHelpAndLabels("process_max_file_descriptors", "Limit of open file descriptors."), + ProcessCpuPercent: descHelper.NewDescWithHelpAndLabels("process_cpu_percent", "CPU usage of the process."), + ProcessCpuTotalMillis: descHelper.NewDescWithHelpAndLabels("process_cpu_total_millis", "Total CPU time used by the process."), + ProcessCpuLoadAverageOneM: descHelper.NewDescWithHelpAndLabels("process_cpu_load_average_1m", "Total 1m system load average."), + ProcessCpuLoadAverageFiveM: descHelper.NewDescWithHelpAndLabels("process_cpu_load_average_5m", "Total 5m system load average."), + ProcessCpuLoadAverageFifteenM: descHelper.NewDescWithHelpAndLabels("process_cpu_load_average_15m", "Total 15m system load average."), - ProcessMemTotalVirtual: descHelper.NewDescWithHelp("process_mem_total_virtual", "Total virtual memory used by the process."), + ProcessMemTotalVirtual: descHelper.NewDescWithHelpAndLabels("process_mem_total_virtual", "Total virtual memory used by the process."), - ReloadSuccesses: descHelper.NewDescWithHelp("reload_successes", "Number of successful reloads."), - ReloadFailures: descHelper.NewDescWithHelp("reload_failures", "Number of failed reloads."), + ReloadSuccesses: descHelper.NewDescWithHelpAndLabels("reload_successes", "Number of successful reloads."), + ReloadFailures: descHelper.NewDescWithHelpAndLabels("reload_failures", "Number of failed reloads."), - QueueEventsCount: descHelper.NewDescWithHelp("queue_events_count", "Number of events in the queue."), + QueueEventsCount: descHelper.NewDescWithHelpAndLabels("queue_events_count", "Number of events in the queue."), - EventsIn: descHelper.NewDescWithHelp("events_in", "Number of events received."), - EventsFiltered: descHelper.NewDescWithHelp("events_filtered", "Number of events filtered out."), - EventsOut: descHelper.NewDescWithHelp("events_out", "Number of events out."), - EventsDurationInMillis: descHelper.NewDescWithHelp("events_duration_millis", "Duration of events processing in milliseconds."), - EventsQueuePushDurationInMillis: descHelper.NewDescWithHelp("events_queue_push_duration_millis", "Duration of events push to queue in milliseconds."), + EventsIn: descHelper.NewDescWithHelpAndLabels("events_in", "Number of events received."), + EventsFiltered: descHelper.NewDescWithHelpAndLabels("events_filtered", "Number of events filtered out."), + EventsOut: descHelper.NewDescWithHelpAndLabels("events_out", "Number of events out."), + EventsDurationInMillis: descHelper.NewDescWithHelpAndLabels("events_duration_millis", "Duration of events processing in milliseconds."), + EventsQueuePushDurationInMillis: descHelper.NewDescWithHelpAndLabels("events_queue_push_duration_millis", "Duration of events push to queue in milliseconds."), FlowInputCurrent: descHelper.NewDescWithHelpAndLabels("flow_input_current", "Current number of events in the input queue."), FlowInputLifetime: descHelper.NewDescWithHelpAndLabels("flow_input_lifetime", "Lifetime number of events in the input queue."), diff --git a/prometheus_helper/prometheus_helper.go b/prometheus_helper/prometheus_helper.go index 033209ed..947ca5af 100644 --- a/prometheus_helper/prometheus_helper.go +++ b/prometheus_helper/prometheus_helper.go @@ -15,11 +15,6 @@ type SimpleDescHelper struct { Subsystem string } -// NewDescWithHelp creates a new prometheus.Desc with the namespace and subsystem. -func (h *SimpleDescHelper) NewDescWithHelp(name string, help string) *prometheus.Desc { - return prometheus.NewDesc(prometheus.BuildFQName(h.Namespace, h.Subsystem, name), help, nil, nil) -} - // NewDescWithLabel creates a new prometheus.Desc with the namespace and subsystem. // Labels are used to differentiate between different sources of the same metric. func (h *SimpleDescHelper) NewDescWithHelpAndLabels(name string, help string, labels ...string) *prometheus.Desc { diff --git a/prometheus_helper/prometheus_helper_test.go b/prometheus_helper/prometheus_helper_test.go index 0d4269c7..12301ff9 100644 --- a/prometheus_helper/prometheus_helper_test.go +++ b/prometheus_helper/prometheus_helper_test.go @@ -15,14 +15,6 @@ func TestSimpleDescHelper(t *testing.T) { Subsystem: "test", } - t.Run("NewDescWithHelp", func(t *testing.T) { - desc := helper.NewDescWithHelp("metric", "help") - expectedDesc := "Desc{fqName: \"logstash_exporter_test_metric\", help: \"help\", constLabels: {}, variableLabels: {}}" - if desc.String() != expectedDesc { - t.Errorf("incorrect metric description, expected %s but got %s", expectedDesc, desc.String()) - } - }) - t.Run("NewDescWithHelpAndLabel", func(t *testing.T) { desc := helper.NewDescWithHelpAndLabels("metric", "help", "customLabel") expectedDesc := "Desc{fqName: \"logstash_exporter_test_metric\", help: \"help\", constLabels: {}, variableLabels: {customLabel}}" @@ -42,7 +34,7 @@ func TestExtractFqdnName(t *testing.T) { metricSubname := "fqdn_metric" descriptors := []*prometheus.Desc{ - helper.NewDescWithHelp(metricSubname, "help"), + helper.NewDescWithHelpAndLabels(metricSubname, "help"), helper.NewDescWithHelpAndLabels(metricSubname, "help", "label"), } From f37585c641fa6996a194dc026c85b14cfedee41b Mon Sep 17 00:00:00 2001 From: Jakub Date: Sat, 30 Sep 2023 16:40:38 +0200 Subject: [PATCH 07/36] Rename NewDescWithHelpAndLabels to NewDesc --- collectors/nodeinfo/nodeinfo_collector.go | 6 +- collectors/nodestats/nodestats_collector.go | 90 +++++++++---------- collectors/nodestats/pipeline_subcollector.go | 84 ++++++++--------- prometheus_helper/prometheus_helper.go | 2 +- prometheus_helper/prometheus_helper_test.go | 6 +- 5 files changed, 94 insertions(+), 94 deletions(-) diff --git a/collectors/nodeinfo/nodeinfo_collector.go b/collectors/nodeinfo/nodeinfo_collector.go index d76556bd..571d26d1 100644 --- a/collectors/nodeinfo/nodeinfo_collector.go +++ b/collectors/nodeinfo/nodeinfo_collector.go @@ -55,13 +55,13 @@ func NewNodeinfoCollector(client logstashclient.Client) *NodeinfoCollector { nil, ), - PipelineWorkers: descHelper.NewDescWithHelpAndLabels("pipeline_workers", + PipelineWorkers: descHelper.NewDesc("pipeline_workers", "Number of worker threads that will process pipeline events.", ), - PipelineBatchSize: descHelper.NewDescWithHelpAndLabels("pipeline_batch_size", + PipelineBatchSize: descHelper.NewDesc("pipeline_batch_size", "Number of events to retrieve from the input queue before sending to the filter and output stages.", ), - PipelineBatchDelay: descHelper.NewDescWithHelpAndLabels("pipeline_batch_delay", + PipelineBatchDelay: descHelper.NewDesc("pipeline_batch_delay", "Amount of time to wait for events to fill the batch before sending to the filter and output stages.", ), diff --git a/collectors/nodestats/nodestats_collector.go b/collectors/nodestats/nodestats_collector.go index 0d0f7548..e1a042e3 100644 --- a/collectors/nodestats/nodestats_collector.go +++ b/collectors/nodestats/nodestats_collector.go @@ -78,59 +78,59 @@ func NewNodestatsCollector(client logstashclient.Client) *NodestatsCollector { pipelineSubcollector: NewPipelineSubcollector(), - JvmThreadsCount: descHelper.NewDescWithHelpAndLabels("jvm_threads_count", "Number of live threads including both daemon and non-daemon threads."), - JvmThreadsPeakCount: descHelper.NewDescWithHelpAndLabels("jvm_threads_peak_count", "Peak live thread count since the Java virtual machine started or peak was reset."), + JvmThreadsCount: descHelper.NewDesc("jvm_threads_count", "Number of live threads including both daemon and non-daemon threads."), + JvmThreadsPeakCount: descHelper.NewDesc("jvm_threads_peak_count", "Peak live thread count since the Java virtual machine started or peak was reset."), - JvmMemHeapUsedPercent: descHelper.NewDescWithHelpAndLabels("jvm_mem_heap_used_percent", "Percentage of the heap memory that is used."), - JvmMemHeapCommittedBytes: descHelper.NewDescWithHelpAndLabels("jvm_mem_heap_committed_bytes", "Amount of heap memory in bytes that is committed for the Java virtual machine to use."), - JvmMemHeapMaxBytes: descHelper.NewDescWithHelpAndLabels("jvm_mem_heap_max_bytes", "Maximum amount of heap memory in bytes that can be used for memory management."), - JvmMemHeapUsedBytes: descHelper.NewDescWithHelpAndLabels("jvm_mem_heap_used_bytes", "Amount of used heap memory in bytes."), - JvmMemNonHeapCommittedBytes: descHelper.NewDescWithHelpAndLabels("jvm_mem_non_heap_committed_bytes", "Amount of non-heap memory in bytes that is committed for the Java virtual machine to use."), + JvmMemHeapUsedPercent: descHelper.NewDesc("jvm_mem_heap_used_percent", "Percentage of the heap memory that is used."), + JvmMemHeapCommittedBytes: descHelper.NewDesc("jvm_mem_heap_committed_bytes", "Amount of heap memory in bytes that is committed for the Java virtual machine to use."), + JvmMemHeapMaxBytes: descHelper.NewDesc("jvm_mem_heap_max_bytes", "Maximum amount of heap memory in bytes that can be used for memory management."), + JvmMemHeapUsedBytes: descHelper.NewDesc("jvm_mem_heap_used_bytes", "Amount of used heap memory in bytes."), + JvmMemNonHeapCommittedBytes: descHelper.NewDesc("jvm_mem_non_heap_committed_bytes", "Amount of non-heap memory in bytes that is committed for the Java virtual machine to use."), - JvmMemPoolPeakUsedInBytes: descHelper.NewDescWithHelpAndLabels( + JvmMemPoolPeakUsedInBytes: descHelper.NewDesc( "jvm_mem_pool_peak_used_bytes", "Peak used bytes of a given JVM memory pool.", "pool"), - JvmMemPoolUsedInBytes: descHelper.NewDescWithHelpAndLabels( + JvmMemPoolUsedInBytes: descHelper.NewDesc( "jvm_mem_pool_used_bytes", "Currently used bytes of a given JVM memory pool.", "pool"), - JvmMemPoolPeakMaxInBytes: descHelper.NewDescWithHelpAndLabels( + JvmMemPoolPeakMaxInBytes: descHelper.NewDesc( "jvm_mem_pool_peak_max_bytes", "Highest value of bytes that were used in a given JVM memory pool.", "pool"), - JvmMemPoolMaxInBytes: descHelper.NewDescWithHelpAndLabels( + JvmMemPoolMaxInBytes: descHelper.NewDesc( "jvm_mem_pool_max_bytes", "Maximum amount of bytes that can be used in a given JVM memory pool.", "pool"), - JvmMemPoolCommittedInBytes: descHelper.NewDescWithHelpAndLabels( + JvmMemPoolCommittedInBytes: descHelper.NewDesc( "jvm_mem_pool_committed_bytes", "Amount of bytes that are committed for the Java virtual machine to use in a given JVM memory pool.", "pool"), - JvmUptimeMillis: descHelper.NewDescWithHelpAndLabels("jvm_uptime_millis", "Uptime of the JVM in milliseconds."), - - ProcessOpenFileDescriptors: descHelper.NewDescWithHelpAndLabels("process_open_file_descriptors", "Number of currently open file descriptors."), - ProcessMaxFileDescriptors: descHelper.NewDescWithHelpAndLabels("process_max_file_descriptors", "Limit of open file descriptors."), - ProcessCpuPercent: descHelper.NewDescWithHelpAndLabels("process_cpu_percent", "CPU usage of the process."), - ProcessCpuTotalMillis: descHelper.NewDescWithHelpAndLabels("process_cpu_total_millis", "Total CPU time used by the process."), - ProcessCpuLoadAverageOneM: descHelper.NewDescWithHelpAndLabels("process_cpu_load_average_1m", "Total 1m system load average."), - ProcessCpuLoadAverageFiveM: descHelper.NewDescWithHelpAndLabels("process_cpu_load_average_5m", "Total 5m system load average."), - ProcessCpuLoadAverageFifteenM: descHelper.NewDescWithHelpAndLabels("process_cpu_load_average_15m", "Total 15m system load average."), - - ProcessMemTotalVirtual: descHelper.NewDescWithHelpAndLabels("process_mem_total_virtual", "Total virtual memory used by the process."), - - ReloadSuccesses: descHelper.NewDescWithHelpAndLabels("reload_successes", "Number of successful reloads."), - ReloadFailures: descHelper.NewDescWithHelpAndLabels("reload_failures", "Number of failed reloads."), - - QueueEventsCount: descHelper.NewDescWithHelpAndLabels("queue_events_count", "Number of events in the queue."), - - EventsIn: descHelper.NewDescWithHelpAndLabels("events_in", "Number of events received."), - EventsFiltered: descHelper.NewDescWithHelpAndLabels("events_filtered", "Number of events filtered out."), - EventsOut: descHelper.NewDescWithHelpAndLabels("events_out", "Number of events out."), - EventsDurationInMillis: descHelper.NewDescWithHelpAndLabels("events_duration_millis", "Duration of events processing in milliseconds."), - EventsQueuePushDurationInMillis: descHelper.NewDescWithHelpAndLabels("events_queue_push_duration_millis", "Duration of events push to queue in milliseconds."), - - FlowInputCurrent: descHelper.NewDescWithHelpAndLabels("flow_input_current", "Current number of events in the input queue."), - FlowInputLifetime: descHelper.NewDescWithHelpAndLabels("flow_input_lifetime", "Lifetime number of events in the input queue."), - FlowFilterCurrent: descHelper.NewDescWithHelpAndLabels("flow_filter_current", "Current number of events in the filter queue."), - FlowFilterLifetime: descHelper.NewDescWithHelpAndLabels("flow_filter_lifetime", "Lifetime number of events in the filter queue."), - FlowOutputCurrent: descHelper.NewDescWithHelpAndLabels("flow_output_current", "Current number of events in the output queue."), - FlowOutputLifetime: descHelper.NewDescWithHelpAndLabels("flow_output_lifetime", "Lifetime number of events in the output queue."), - FlowQueueBackpressureCurrent: descHelper.NewDescWithHelpAndLabels("flow_queue_backpressure_current", "Current number of events in the backpressure queue."), - FlowQueueBackpressureLifetime: descHelper.NewDescWithHelpAndLabels("flow_queue_backpressure_lifetime", "Lifetime number of events in the backpressure queue."), - FlowWorkerConcurrencyCurrent: descHelper.NewDescWithHelpAndLabels("flow_worker_concurrency_current", "Current number of workers."), - FlowWorkerConcurrencyLifetime: descHelper.NewDescWithHelpAndLabels("flow_worker_concurrency_lifetime", "Lifetime number of workers."), + JvmUptimeMillis: descHelper.NewDesc("jvm_uptime_millis", "Uptime of the JVM in milliseconds."), + + ProcessOpenFileDescriptors: descHelper.NewDesc("process_open_file_descriptors", "Number of currently open file descriptors."), + ProcessMaxFileDescriptors: descHelper.NewDesc("process_max_file_descriptors", "Limit of open file descriptors."), + ProcessCpuPercent: descHelper.NewDesc("process_cpu_percent", "CPU usage of the process."), + ProcessCpuTotalMillis: descHelper.NewDesc("process_cpu_total_millis", "Total CPU time used by the process."), + ProcessCpuLoadAverageOneM: descHelper.NewDesc("process_cpu_load_average_1m", "Total 1m system load average."), + ProcessCpuLoadAverageFiveM: descHelper.NewDesc("process_cpu_load_average_5m", "Total 5m system load average."), + ProcessCpuLoadAverageFifteenM: descHelper.NewDesc("process_cpu_load_average_15m", "Total 15m system load average."), + + ProcessMemTotalVirtual: descHelper.NewDesc("process_mem_total_virtual", "Total virtual memory used by the process."), + + ReloadSuccesses: descHelper.NewDesc("reload_successes", "Number of successful reloads."), + ReloadFailures: descHelper.NewDesc("reload_failures", "Number of failed reloads."), + + QueueEventsCount: descHelper.NewDesc("queue_events_count", "Number of events in the queue."), + + EventsIn: descHelper.NewDesc("events_in", "Number of events received."), + EventsFiltered: descHelper.NewDesc("events_filtered", "Number of events filtered out."), + EventsOut: descHelper.NewDesc("events_out", "Number of events out."), + EventsDurationInMillis: descHelper.NewDesc("events_duration_millis", "Duration of events processing in milliseconds."), + EventsQueuePushDurationInMillis: descHelper.NewDesc("events_queue_push_duration_millis", "Duration of events push to queue in milliseconds."), + + FlowInputCurrent: descHelper.NewDesc("flow_input_current", "Current number of events in the input queue."), + FlowInputLifetime: descHelper.NewDesc("flow_input_lifetime", "Lifetime number of events in the input queue."), + FlowFilterCurrent: descHelper.NewDesc("flow_filter_current", "Current number of events in the filter queue."), + FlowFilterLifetime: descHelper.NewDesc("flow_filter_lifetime", "Lifetime number of events in the filter queue."), + FlowOutputCurrent: descHelper.NewDesc("flow_output_current", "Current number of events in the output queue."), + FlowOutputLifetime: descHelper.NewDesc("flow_output_lifetime", "Lifetime number of events in the output queue."), + FlowQueueBackpressureCurrent: descHelper.NewDesc("flow_queue_backpressure_current", "Current number of events in the backpressure queue."), + FlowQueueBackpressureLifetime: descHelper.NewDesc("flow_queue_backpressure_lifetime", "Lifetime number of events in the backpressure queue."), + FlowWorkerConcurrencyCurrent: descHelper.NewDesc("flow_worker_concurrency_current", "Current number of workers."), + FlowWorkerConcurrencyLifetime: descHelper.NewDesc("flow_worker_concurrency_lifetime", "Lifetime number of workers."), } } diff --git a/collectors/nodestats/pipeline_subcollector.go b/collectors/nodestats/pipeline_subcollector.go index bc5abef3..5a8905b0 100644 --- a/collectors/nodestats/pipeline_subcollector.go +++ b/collectors/nodestats/pipeline_subcollector.go @@ -65,48 +65,48 @@ type PipelineSubcollector struct { func NewPipelineSubcollector() *PipelineSubcollector { descHelper := prometheus_helper.SimpleDescHelper{Namespace: namespace, Subsystem: fmt.Sprintf("%s_pipeline", subsystem)} return &PipelineSubcollector{ - Up: descHelper.NewDescWithHelpAndLabels("up", "Whether the pipeline is up or not.", "pipeline"), - EventsOut: descHelper.NewDescWithHelpAndLabels("events_out", "Number of events that have been processed by this pipeline.", "pipeline"), - EventsFiltered: descHelper.NewDescWithHelpAndLabels("events_filtered", "Number of events that have been filtered out by this pipeline.", "pipeline"), - EventsIn: descHelper.NewDescWithHelpAndLabels("events_in", "Number of events that have been inputted into this pipeline.", "pipeline"), - EventsDuration: descHelper.NewDescWithHelpAndLabels("events_duration", "Time needed to process event.", "pipeline"), - EventsQueuePushDuration: descHelper.NewDescWithHelpAndLabels("events_queue_push_duration", "Time needed to push event to queue.", "pipeline"), - - ReloadsSuccesses: descHelper.NewDescWithHelpAndLabels("reloads_successes", "Number of successful pipeline reloads.", "pipeline"), - ReloadsFailures: descHelper.NewDescWithHelpAndLabels("reloads_failures", "Number of failed pipeline reloads.", "pipeline"), - - ReloadsLastSuccessTimestamp: descHelper.NewDescWithHelpAndLabels("reloads_last_success_timestamp", "Timestamp of last successful pipeline reload.", "pipeline"), - ReloadsLastFailureTimestamp: descHelper.NewDescWithHelpAndLabels("reloads_last_failure_timestamp", "Timestamp of last failed pipeline reload.", "pipeline"), - - QueueEventsCount: descHelper.NewDescWithHelpAndLabels("queue_events_count", "Number of events in the queue.", "pipeline"), - QueueEventsQueueSize: descHelper.NewDescWithHelpAndLabels("queue_events_queue_size", "Number of events that the queue can accommodate", "pipeline"), - QueueMaxQueueSizeInBytes: descHelper.NewDescWithHelpAndLabels("queue_max_size_in_bytes", "Maximum size of given queue in bytes.", "pipeline"), - - PipelinePluginEventsIn: descHelper.NewDescWithHelpAndLabels("plugin_events_in", "Number of events received this pipeline.", "pipeline", "plugin_type", "plugin", "plugin_id"), - PipelinePluginEventsOut: descHelper.NewDescWithHelpAndLabels("plugin_events_out", "Number of events output by this pipeline.", "pipeline", "plugin_type", "plugin", "plugin_id"), - PipelinePluginEventsDuration: descHelper.NewDescWithHelpAndLabels("plugin_events_duration", "Time spent processing events in this plugin.", "pipeline", "plugin_type", "plugin", "plugin_id"), - PipelinePluginEventsQueuePushDuration: descHelper.NewDescWithHelpAndLabels("plugin_events_queue_push_duration", "Time spent pushing events into the input queue.", "pipeline", "plugin_type", "plugin", "plugin_id"), - - PipelinePluginDocumentsSuccesses: descHelper.NewDescWithHelpAndLabels("plugin_documents_successes", "Number of successful bulk requests.", "pipeline", "plugin_type", "plugin", "plugin_id"), - PipelinePluginDocumentsNonRetryableFailures: descHelper.NewDescWithHelpAndLabels("plugin_documents_non_retryable_failures", "Number of output events with non-retryable failures.", "pipeline", "plugin_type", "plugin", "plugin_id"), - PipelinePluginBulkRequestErrors: descHelper.NewDescWithHelpAndLabels("plugin_bulk_requests_errors", "Number of bulk request errors.", "pipeline", "plugin_type", "plugin", "plugin_id"), - PipelinePluginBulkRequestResponses: descHelper.NewDescWithHelpAndLabels("plugin_bulk_requests_responses", "Bulk request HTTP response counts by code.", "pipeline", "plugin_type", "plugin", "plugin_id", "code"), - - FlowInputCurrent: descHelper.NewDescWithHelpAndLabels("flow_input_current", "Current number of events in the input queue.", "pipeline"), - FlowInputLifetime: descHelper.NewDescWithHelpAndLabels("flow_input_lifetime", "Lifetime number of events in the input queue.", "pipeline"), - FlowFilterCurrent: descHelper.NewDescWithHelpAndLabels("flow_filter_current", "Current number of events in the filter queue.", "pipeline"), - FlowFilterLifetime: descHelper.NewDescWithHelpAndLabels("flow_filter_lifetime", "Lifetime number of events in the filter queue.", "pipeline"), - FlowOutputCurrent: descHelper.NewDescWithHelpAndLabels("flow_output_current", "Current number of events in the output queue.", "pipeline"), - FlowOutputLifetime: descHelper.NewDescWithHelpAndLabels("flow_output_lifetime", "Lifetime number of events in the output queue.", "pipeline"), - FlowQueueBackpressureCurrent: descHelper.NewDescWithHelpAndLabels("flow_queue_backpressure_current", "Current number of events in the backpressure queue.", "pipeline"), - FlowQueueBackpressureLifetime: descHelper.NewDescWithHelpAndLabels("flow_queue_backpressure_lifetime", "Lifetime number of events in the backpressure queue.", "pipeline"), - FlowWorkerConcurrencyCurrent: descHelper.NewDescWithHelpAndLabels("flow_worker_concurrency_current", "Current number of workers.", "pipeline"), - FlowWorkerConcurrencyLifetime: descHelper.NewDescWithHelpAndLabels("flow_worker_concurrency_lifetime", "Lifetime number of workers.", "pipeline"), - - DeadLetterQueueMaxSizeInBytes: descHelper.NewDescWithHelpAndLabels("dead_letter_queue_max_size_in_bytes", "Maximum size of the dead letter queue in bytes.", "pipeline"), - DeadLetterQueueSizeInBytes: descHelper.NewDescWithHelpAndLabels("dead_letter_queue_size_in_bytes", "Current size of the dead letter queue in bytes.", "pipeline"), - DeadLetterQueueDroppedEvents: descHelper.NewDescWithHelpAndLabels("dead_letter_queue_dropped_events", "Number of events dropped by the dead letter queue.", "pipeline"), - DeadLetterQueueExpiredEvents: descHelper.NewDescWithHelpAndLabels("dead_letter_queue_expired_events", "Number of events expired in the dead letter queue.", "pipeline"), + Up: descHelper.NewDesc("up", "Whether the pipeline is up or not.", "pipeline"), + EventsOut: descHelper.NewDesc("events_out", "Number of events that have been processed by this pipeline.", "pipeline"), + EventsFiltered: descHelper.NewDesc("events_filtered", "Number of events that have been filtered out by this pipeline.", "pipeline"), + EventsIn: descHelper.NewDesc("events_in", "Number of events that have been inputted into this pipeline.", "pipeline"), + EventsDuration: descHelper.NewDesc("events_duration", "Time needed to process event.", "pipeline"), + EventsQueuePushDuration: descHelper.NewDesc("events_queue_push_duration", "Time needed to push event to queue.", "pipeline"), + + ReloadsSuccesses: descHelper.NewDesc("reloads_successes", "Number of successful pipeline reloads.", "pipeline"), + ReloadsFailures: descHelper.NewDesc("reloads_failures", "Number of failed pipeline reloads.", "pipeline"), + + ReloadsLastSuccessTimestamp: descHelper.NewDesc("reloads_last_success_timestamp", "Timestamp of last successful pipeline reload.", "pipeline"), + ReloadsLastFailureTimestamp: descHelper.NewDesc("reloads_last_failure_timestamp", "Timestamp of last failed pipeline reload.", "pipeline"), + + QueueEventsCount: descHelper.NewDesc("queue_events_count", "Number of events in the queue.", "pipeline"), + QueueEventsQueueSize: descHelper.NewDesc("queue_events_queue_size", "Number of events that the queue can accommodate", "pipeline"), + QueueMaxQueueSizeInBytes: descHelper.NewDesc("queue_max_size_in_bytes", "Maximum size of given queue in bytes.", "pipeline"), + + PipelinePluginEventsIn: descHelper.NewDesc("plugin_events_in", "Number of events received this pipeline.", "pipeline", "plugin_type", "plugin", "plugin_id"), + PipelinePluginEventsOut: descHelper.NewDesc("plugin_events_out", "Number of events output by this pipeline.", "pipeline", "plugin_type", "plugin", "plugin_id"), + PipelinePluginEventsDuration: descHelper.NewDesc("plugin_events_duration", "Time spent processing events in this plugin.", "pipeline", "plugin_type", "plugin", "plugin_id"), + PipelinePluginEventsQueuePushDuration: descHelper.NewDesc("plugin_events_queue_push_duration", "Time spent pushing events into the input queue.", "pipeline", "plugin_type", "plugin", "plugin_id"), + + PipelinePluginDocumentsSuccesses: descHelper.NewDesc("plugin_documents_successes", "Number of successful bulk requests.", "pipeline", "plugin_type", "plugin", "plugin_id"), + PipelinePluginDocumentsNonRetryableFailures: descHelper.NewDesc("plugin_documents_non_retryable_failures", "Number of output events with non-retryable failures.", "pipeline", "plugin_type", "plugin", "plugin_id"), + PipelinePluginBulkRequestErrors: descHelper.NewDesc("plugin_bulk_requests_errors", "Number of bulk request errors.", "pipeline", "plugin_type", "plugin", "plugin_id"), + PipelinePluginBulkRequestResponses: descHelper.NewDesc("plugin_bulk_requests_responses", "Bulk request HTTP response counts by code.", "pipeline", "plugin_type", "plugin", "plugin_id", "code"), + + FlowInputCurrent: descHelper.NewDesc("flow_input_current", "Current number of events in the input queue.", "pipeline"), + FlowInputLifetime: descHelper.NewDesc("flow_input_lifetime", "Lifetime number of events in the input queue.", "pipeline"), + FlowFilterCurrent: descHelper.NewDesc("flow_filter_current", "Current number of events in the filter queue.", "pipeline"), + FlowFilterLifetime: descHelper.NewDesc("flow_filter_lifetime", "Lifetime number of events in the filter queue.", "pipeline"), + FlowOutputCurrent: descHelper.NewDesc("flow_output_current", "Current number of events in the output queue.", "pipeline"), + FlowOutputLifetime: descHelper.NewDesc("flow_output_lifetime", "Lifetime number of events in the output queue.", "pipeline"), + FlowQueueBackpressureCurrent: descHelper.NewDesc("flow_queue_backpressure_current", "Current number of events in the backpressure queue.", "pipeline"), + FlowQueueBackpressureLifetime: descHelper.NewDesc("flow_queue_backpressure_lifetime", "Lifetime number of events in the backpressure queue.", "pipeline"), + FlowWorkerConcurrencyCurrent: descHelper.NewDesc("flow_worker_concurrency_current", "Current number of workers.", "pipeline"), + FlowWorkerConcurrencyLifetime: descHelper.NewDesc("flow_worker_concurrency_lifetime", "Lifetime number of workers.", "pipeline"), + + DeadLetterQueueMaxSizeInBytes: descHelper.NewDesc("dead_letter_queue_max_size_in_bytes", "Maximum size of the dead letter queue in bytes.", "pipeline"), + DeadLetterQueueSizeInBytes: descHelper.NewDesc("dead_letter_queue_size_in_bytes", "Current size of the dead letter queue in bytes.", "pipeline"), + DeadLetterQueueDroppedEvents: descHelper.NewDesc("dead_letter_queue_dropped_events", "Number of events dropped by the dead letter queue.", "pipeline"), + DeadLetterQueueExpiredEvents: descHelper.NewDesc("dead_letter_queue_expired_events", "Number of events expired in the dead letter queue.", "pipeline"), } } diff --git a/prometheus_helper/prometheus_helper.go b/prometheus_helper/prometheus_helper.go index 947ca5af..093d8e5f 100644 --- a/prometheus_helper/prometheus_helper.go +++ b/prometheus_helper/prometheus_helper.go @@ -17,7 +17,7 @@ type SimpleDescHelper struct { // NewDescWithLabel creates a new prometheus.Desc with the namespace and subsystem. // Labels are used to differentiate between different sources of the same metric. -func (h *SimpleDescHelper) NewDescWithHelpAndLabels(name string, help string, labels ...string) *prometheus.Desc { +func (h *SimpleDescHelper) NewDesc(name string, help string, labels ...string) *prometheus.Desc { return prometheus.NewDesc(prometheus.BuildFQName(h.Namespace, h.Subsystem, name), help, labels, nil) } diff --git a/prometheus_helper/prometheus_helper_test.go b/prometheus_helper/prometheus_helper_test.go index 12301ff9..afe7adbd 100644 --- a/prometheus_helper/prometheus_helper_test.go +++ b/prometheus_helper/prometheus_helper_test.go @@ -16,7 +16,7 @@ func TestSimpleDescHelper(t *testing.T) { } t.Run("NewDescWithHelpAndLabel", func(t *testing.T) { - desc := helper.NewDescWithHelpAndLabels("metric", "help", "customLabel") + desc := helper.NewDesc("metric", "help", "customLabel") expectedDesc := "Desc{fqName: \"logstash_exporter_test_metric\", help: \"help\", constLabels: {}, variableLabels: {customLabel}}" if desc.String() != expectedDesc { t.Errorf("incorrect metric description, expected %s but got %s", expectedDesc, desc.String()) @@ -34,8 +34,8 @@ func TestExtractFqdnName(t *testing.T) { metricSubname := "fqdn_metric" descriptors := []*prometheus.Desc{ - helper.NewDescWithHelpAndLabels(metricSubname, "help"), - helper.NewDescWithHelpAndLabels(metricSubname, "help", "label"), + helper.NewDesc(metricSubname, "help"), + helper.NewDesc(metricSubname, "help", "label"), } for _, desc := range descriptors { From eff93a94c2897eacb3434a4902c2e4186e89a7a8 Mon Sep 17 00:00:00 2001 From: Jakub Date: Sat, 30 Sep 2023 17:16:32 +0200 Subject: [PATCH 08/36] Add endpoint to all nodeinfo metrics --- collectors/nodeinfo/nodeinfo_collector.go | 45 ++++++++++++++++++----- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/collectors/nodeinfo/nodeinfo_collector.go b/collectors/nodeinfo/nodeinfo_collector.go index 571d26d1..2b5503ae 100644 --- a/collectors/nodeinfo/nodeinfo_collector.go +++ b/collectors/nodeinfo/nodeinfo_collector.go @@ -9,7 +9,6 @@ import ( "github.com/kuskoman/logstash-exporter/config" logstashclient "github.com/kuskoman/logstash-exporter/fetcher/logstash_client" "github.com/kuskoman/logstash-exporter/fetcher/responses" - "github.com/kuskoman/logstash-exporter/prometheus_helper" ) // NodeinfoCollector is a custom collector for the /_node/stats endpoint @@ -31,50 +30,66 @@ type NodeinfoCollector struct { func NewNodeinfoCollector(client logstashclient.Client) *NodeinfoCollector { const subsystem = "info" namespace := config.PrometheusNamespace - descHelper := prometheus_helper.SimpleDescHelper{Namespace: namespace, Subsystem: subsystem} return &NodeinfoCollector{ client: client, NodeInfos: prometheus.NewDesc( prometheus.BuildFQName(namespace, subsystem, "node"), "A metric with a constant '1' value labeled by node name, version, host, http_address, and id of the logstash instance.", - []string{"name", "version", "http_address", "host", "id"}, + []string{"name", "version", "http_address", "host", "id", "hostname"}, nil, ), BuildInfos: prometheus.NewDesc( prometheus.BuildFQName(namespace, subsystem, "build"), "A metric with a constant '1' value labeled by build date, sha, and snapshot of the logstash instance.", - []string{"date", "sha", "snapshot"}, + []string{"date", "sha", "snapshot", "hostname"}, nil, ), Up: prometheus.NewDesc( prometheus.BuildFQName(namespace, subsystem, "up"), "A metric that returns 1 if the node is up, 0 otherwise.", - nil, + []string{"hostname"}, nil, ), - - PipelineWorkers: descHelper.NewDesc("pipeline_workers", + PipelineWorkers: prometheus.NewDesc( + prometheus.BuildFQName(namespace, subsystem, "pipeline_workers"), "Number of worker threads that will process pipeline events.", + []string{"hostname"}, + nil, ), - PipelineBatchSize: descHelper.NewDesc("pipeline_batch_size", + PipelineBatchSize: prometheus.NewDesc( + prometheus.BuildFQName(namespace, subsystem, "pipeline_batch_size"), "Number of events to retrieve from the input queue before sending to the filter and output stages.", + []string{"hostname"}, + nil, ), - PipelineBatchDelay: descHelper.NewDesc("pipeline_batch_delay", + PipelineBatchDelay: prometheus.NewDesc( + prometheus.BuildFQName(namespace, subsystem, "pipeline_batch_delay"), "Amount of time to wait for events to fill the batch before sending to the filter and output stages.", + []string{"hostname"}, + nil, ), Status: prometheus.NewDesc( prometheus.BuildFQName(namespace, subsystem, "status"), "A metric with a constant '1' value labeled by status.", - []string{"status"}, + []string{"status", "hostname"}, nil, ), } } func (c *NodeinfoCollector) Collect(ctx context.Context, ch chan<- prometheus.Metric) error { + err := c.collectSingleInstance(ctx, ch) + if err != nil { + return err + } + + return nil +} + +func (c *NodeinfoCollector) collectSingleInstance(ctx context.Context, ch chan<- prometheus.Metric) error { nodeInfo, err := c.client.GetNodeInfo(ctx) if err != nil { ch <- c.getUpStatus(nodeInfo, err) @@ -82,6 +97,8 @@ func (c *NodeinfoCollector) Collect(ctx context.Context, ch chan<- prometheus.Me return err } + endpoint := c.client.GetEndpoint() + ch <- prometheus.MustNewConstMetric( c.NodeInfos, prometheus.CounterValue, @@ -91,6 +108,7 @@ func (c *NodeinfoCollector) Collect(ctx context.Context, ch chan<- prometheus.Me nodeInfo.Host, nodeInfo.HTTPAddress, nodeInfo.ID, + endpoint, ) ch <- prometheus.MustNewConstMetric( @@ -100,30 +118,35 @@ func (c *NodeinfoCollector) Collect(ctx context.Context, ch chan<- prometheus.Me nodeInfo.BuildDate, nodeInfo.BuildSHA, strconv.FormatBool(nodeInfo.BuildSnapshot), + endpoint, ) ch <- prometheus.MustNewConstMetric( c.Up, prometheus.GaugeValue, float64(1), + endpoint, ) ch <- prometheus.MustNewConstMetric( c.PipelineWorkers, prometheus.CounterValue, float64(nodeInfo.Pipeline.Workers), + endpoint, ) ch <- prometheus.MustNewConstMetric( c.PipelineBatchSize, prometheus.CounterValue, float64(nodeInfo.Pipeline.BatchSize), + endpoint, ) ch <- prometheus.MustNewConstMetric( c.PipelineBatchDelay, prometheus.CounterValue, float64(nodeInfo.Pipeline.BatchDelay), + endpoint, ) ch <- prometheus.MustNewConstMetric( @@ -131,6 +154,7 @@ func (c *NodeinfoCollector) Collect(ctx context.Context, ch chan<- prometheus.Me prometheus.CounterValue, float64(1), nodeInfo.Status, + endpoint, ) return nil @@ -148,5 +172,6 @@ func (c *NodeinfoCollector) getUpStatus(nodeinfo *responses.NodeInfoResponse, er c.Up, prometheus.GaugeValue, float64(status), + c.client.GetEndpoint(), ) } From cf662ddf3616a66ad8b6d23e7eef3a3c4d3cd86a Mon Sep 17 00:00:00 2001 From: Jakub Date: Sat, 30 Sep 2023 17:23:12 +0200 Subject: [PATCH 09/36] Make prototype of nodeinfo using multiple logstash endpoints --- cmd/exporter/main.go | 9 +--- collectors/collector_manager.go | 22 ++++++--- collectors/nodeinfo/nodeinfo_collector.go | 55 +++++++++++++++++------ 3 files changed, 60 insertions(+), 26 deletions(-) diff --git a/cmd/exporter/main.go b/cmd/exporter/main.go index 9db5da1c..5e678d70 100644 --- a/cmd/exporter/main.go +++ b/cmd/exporter/main.go @@ -48,13 +48,8 @@ func main() { versionInfo := config.GetVersionInfo() slog.Info(versionInfo.String()) - for _, logstashServerConfig := range exporterConfig.Logstash.Servers { - logstashUrl := logstashServerConfig.URL - slog.Info("booting collector manager for", "logstashUrl", logstashUrl) - - collectorManager := collectors.NewCollectorManager(logstashUrl) - prometheus.MustRegister(collectorManager) - } + collectorManager := collectors.NewCollectorManager(exporterConfig.GetLogstashUrls()) + prometheus.MustRegister(collectorManager) appServer := server.NewAppServer(host, port, exporterConfig) diff --git a/collectors/collector_manager.go b/collectors/collector_manager.go index d1cf446f..783b55a9 100644 --- a/collectors/collector_manager.go +++ b/collectors/collector_manager.go @@ -24,10 +24,20 @@ type CollectorManager struct { scrapeDurations *prometheus.SummaryVec } -func NewCollectorManager(endpoint string) *CollectorManager { - client := logstashclient.NewClient(endpoint) +func getClientsForEndpoints(endpoints []string) []logstashclient.Client { + clients := make([]logstashclient.Client, len(endpoints)) - collectors := getCollectors(client) + for i, endpoint := range endpoints { + clients[i] = logstashclient.NewClient(endpoint) + } + + return clients +} + +func NewCollectorManager(endpoints []string) *CollectorManager { + clients := getClientsForEndpoints(endpoints) + + collectors := getCollectors(clients) scrapeDurations := getScrapeDurationsCollector() prometheus.MustRegister(version.NewCollector("logstash_exporter")) @@ -35,10 +45,10 @@ func NewCollectorManager(endpoint string) *CollectorManager { return &CollectorManager{collectors: collectors, scrapeDurations: scrapeDurations} } -func getCollectors(client logstashclient.Client) map[string]Collector { +func getCollectors(clients []logstashclient.Client) map[string]Collector { collectors := make(map[string]Collector) - collectors["nodeinfo"] = nodeinfo.NewNodeinfoCollector(client) - collectors["nodestats"] = nodestats.NewNodestatsCollector(client) + collectors["nodeinfo"] = nodeinfo.NewNodeinfoCollector(clients) + collectors["nodestats"] = nodestats.NewNodestatsCollector(clients[0]) // TODO: support multiple clients return collectors } diff --git a/collectors/nodeinfo/nodeinfo_collector.go b/collectors/nodeinfo/nodeinfo_collector.go index 2b5503ae..d0de4fb2 100644 --- a/collectors/nodeinfo/nodeinfo_collector.go +++ b/collectors/nodeinfo/nodeinfo_collector.go @@ -2,7 +2,10 @@ package nodeinfo import ( "context" + "errors" + "fmt" "strconv" + "sync" "github.com/prometheus/client_golang/prometheus" @@ -13,7 +16,7 @@ import ( // NodeinfoCollector is a custom collector for the /_node/stats endpoint type NodeinfoCollector struct { - client logstashclient.Client + clients []logstashclient.Client NodeInfos *prometheus.Desc BuildInfos *prometheus.Desc @@ -27,12 +30,12 @@ type NodeinfoCollector struct { Status *prometheus.Desc } -func NewNodeinfoCollector(client logstashclient.Client) *NodeinfoCollector { +func NewNodeinfoCollector(clients []logstashclient.Client) *NodeinfoCollector { const subsystem = "info" namespace := config.PrometheusNamespace return &NodeinfoCollector{ - client: client, + clients: clients, NodeInfos: prometheus.NewDesc( prometheus.BuildFQName(namespace, subsystem, "node"), "A metric with a constant '1' value labeled by node name, version, host, http_address, and id of the logstash instance.", @@ -81,23 +84,49 @@ func NewNodeinfoCollector(client logstashclient.Client) *NodeinfoCollector { } func (c *NodeinfoCollector) Collect(ctx context.Context, ch chan<- prometheus.Metric) error { - err := c.collectSingleInstance(ctx, ch) - if err != nil { - return err + wg := sync.WaitGroup{} + wg.Add(len(c.clients)) + + errorChannel := make(chan error, len(c.clients)) + + for _, client := range c.clients { + go func(client logstashclient.Client) { + err := c.collectSingleInstance(client, ctx, ch) + if err != nil { + errorChannel <- err + } + wg.Done() + }(client) } - return nil + wg.Wait() + close(errorChannel) + + if len(errorChannel) == 0 { + return nil + } + + if len(errorChannel) == 1 { + return <-errorChannel + } + + errorString := fmt.Sprintf("encountered %d errors while collecting nodeinfo metrics", len(errorChannel)) + for err := range errorChannel { + errorString += fmt.Sprintf("\n\t%s", err.Error()) + } + + return errors.New(errorString) } -func (c *NodeinfoCollector) collectSingleInstance(ctx context.Context, ch chan<- prometheus.Metric) error { - nodeInfo, err := c.client.GetNodeInfo(ctx) +func (c *NodeinfoCollector) collectSingleInstance(client logstashclient.Client, ctx context.Context, ch chan<- prometheus.Metric) error { + nodeInfo, err := client.GetNodeInfo(ctx) if err != nil { - ch <- c.getUpStatus(nodeInfo, err) + ch <- c.getUpStatus(nodeInfo, err, client.GetEndpoint()) return err } - endpoint := c.client.GetEndpoint() + endpoint := client.GetEndpoint() ch <- prometheus.MustNewConstMetric( c.NodeInfos, @@ -160,7 +189,7 @@ func (c *NodeinfoCollector) collectSingleInstance(ctx context.Context, ch chan<- return nil } -func (c *NodeinfoCollector) getUpStatus(nodeinfo *responses.NodeInfoResponse, err error) prometheus.Metric { +func (c *NodeinfoCollector) getUpStatus(nodeinfo *responses.NodeInfoResponse, err error, endpoint string) prometheus.Metric { status := 1 if err != nil { status = 0 @@ -172,6 +201,6 @@ func (c *NodeinfoCollector) getUpStatus(nodeinfo *responses.NodeInfoResponse, er c.Up, prometheus.GaugeValue, float64(status), - c.client.GetEndpoint(), + endpoint, ) } From bb6b1f7e40f52906457929aa8b5009af69d5fd13 Mon Sep 17 00:00:00 2001 From: Jakub Date: Sat, 30 Sep 2023 17:34:45 +0200 Subject: [PATCH 10/36] Fix tests after allowing to specify multiple hosts --- collectors/collector_manager_test.go | 19 ++- .../nodeinfo/nodeinfo_collector_test.go | 146 +++++++++++------- 2 files changed, 105 insertions(+), 60 deletions(-) diff --git a/collectors/collector_manager_test.go b/collectors/collector_manager_test.go index 58131155..8834c5a9 100644 --- a/collectors/collector_manager_test.go +++ b/collectors/collector_manager_test.go @@ -10,12 +10,21 @@ import ( ) func TestNewCollectorManager(t *testing.T) { - mockEndpoint := "http://localhost:9600" - cm := NewCollectorManager(mockEndpoint) + t.Parallel() - if cm == nil { - t.Error("Expected collector manager to be initialized") - } + t.Run("multiple endpoints", func(t *testing.T) { + mockEndpoints := []string{ + "http://localhost:9600", + "http://localhost:9601", + } + cm := NewCollectorManager(mockEndpoints) + + if cm == nil { + t.Error("expected collector manager to be initialized") + } + }) + + // prometheus has a global state, so we cannot register the same collector twice, therefore there is no single endpoint test } type mockCollector struct { diff --git a/collectors/nodeinfo/nodeinfo_collector_test.go b/collectors/nodeinfo/nodeinfo_collector_test.go index bb6aa969..e6f0d8c7 100644 --- a/collectors/nodeinfo/nodeinfo_collector_test.go +++ b/collectors/nodeinfo/nodeinfo_collector_test.go @@ -10,6 +10,7 @@ import ( "github.com/prometheus/client_golang/prometheus" + logstashclient "github.com/kuskoman/logstash-exporter/fetcher/logstash_client" "github.com/kuskoman/logstash-exporter/fetcher/responses" "github.com/kuskoman/logstash-exporter/prometheus_helper" ) @@ -54,81 +55,116 @@ func (m *errorMockClient) GetEndpoint() string { } func TestCollectNotNil(t *testing.T) { - collector := NewNodeinfoCollector(&mockClient{}) - ch := make(chan prometheus.Metric) - ctx := context.Background() + runTest := func(t *testing.T, clients []logstashclient.Client) { + collector := NewNodeinfoCollector(clients) + ch := make(chan prometheus.Metric) + ctx := context.Background() - go func() { - err := collector.Collect(ctx, ch) - if err != nil { - t.Errorf("Expected no error, got %v", err) + go func() { + err := collector.Collect(ctx, ch) + if err != nil { + t.Errorf("Expected no error, got %v", err) + } + close(ch) + }() + + expectedMetrics := []string{ + "logstash_info_build", + "logstash_info_node", + "logstash_info_pipeline_batch_delay", + "logstash_info_pipeline_batch_size", + "logstash_info_pipeline_workers", + "logstash_info_status", + "logstash_info_up", } - close(ch) - }() - - expectedMetrics := []string{ - "logstash_info_build", - "logstash_info_node", - "logstash_info_pipeline_batch_delay", - "logstash_info_pipeline_batch_size", - "logstash_info_pipeline_workers", - "logstash_info_status", - "logstash_info_up", - } - var foundMetrics []string - for metric := range ch { - if metric == nil { - t.Errorf("expected metric %s not to be nil", metric.Desc().String()) - } + var foundMetrics []string + for metric := range ch { + if metric == nil { + t.Errorf("expected metric %s not to be nil", metric.Desc().String()) + } - foundMetricDesc := metric.Desc().String() - foundMetricFqName, err := prometheus_helper.ExtractFqName(foundMetricDesc) - if err != nil { - t.Errorf("failed to extract fqName from metric %s", foundMetricDesc) - } + foundMetricDesc := metric.Desc().String() + foundMetricFqName, err := prometheus_helper.ExtractFqName(foundMetricDesc) + if err != nil { + t.Errorf("failed to extract fqName from metric %s", foundMetricDesc) + } - foundMetrics = append(foundMetrics, foundMetricFqName) - } + foundMetrics = append(foundMetrics, foundMetricFqName) + } - for _, expectedMetric := range expectedMetrics { - found := false - for _, foundMetric := range foundMetrics { - if foundMetric == expectedMetric { - found = true - break + for _, expectedMetric := range expectedMetrics { + found := false + for _, foundMetric := range foundMetrics { + if foundMetric == expectedMetric { + found = true + break + } } - } - if !found { - t.Errorf("Expected metric %s to be found", expectedMetric) + if !found { + t.Errorf("Expected metric %s to be found", expectedMetric) + } } } + + t.Run("single client", func(t *testing.T) { + t.Parallel() + + runTest(t, []logstashclient.Client{&mockClient{}}) + }) + + t.Run("multiple clients", func(t *testing.T) { + t.Parallel() + + runTest(t, []logstashclient.Client{&mockClient{}, &mockClient{}}) + }) } func TestCollectError(t *testing.T) { - collector := NewNodeinfoCollector(&errorMockClient{}) - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - defer cancel() + runTest := func(t *testing.T, clients []logstashclient.Client) { + collector := NewNodeinfoCollector(clients) + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() - ch := make(chan prometheus.Metric) + ch := make(chan prometheus.Metric) - go func() { - for range ch { - // simulate reading from the channel - } - }() + go func() { + for range ch { + // simulate reading from the channel + } + }() - err := collector.Collect(ctx, ch) - close(ch) + err := collector.Collect(ctx, ch) + close(ch) - if err == nil { - t.Error("Expected err not to be nil") + if err == nil { + t.Error("Expected err not to be nil") + } } + + t.Run("single faulty client", func(t *testing.T) { + t.Parallel() + + runTest(t, []logstashclient.Client{&errorMockClient{}}) + }) + + t.Run("multiple faulty clients", func(t *testing.T) { + t.Parallel() + + runTest(t, []logstashclient.Client{&errorMockClient{}, &errorMockClient{}}) + }) + + t.Run("multiple clients, one faulty", func(t *testing.T) { + t.Parallel() + + runTest(t, []logstashclient.Client{&mockClient{}, &errorMockClient{}}) + }) } func TestGetUpStatus(t *testing.T) { - collector := NewNodeinfoCollector(&mockClient{}) + clients := []logstashclient.Client{&mockClient{}} + collector := NewNodeinfoCollector(clients) tests := []struct { name string @@ -164,7 +200,7 @@ func TestGetUpStatus(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - metric := collector.getUpStatus(test.nodeInfo, test.err) + metric := collector.getUpStatus(test.nodeInfo, test.err, "test_endpoint") metricValue, err := prometheus_helper.ExtractValueFromMetric(metric) if err != nil { From 87ec6a42afd21add30c48a092f3a3f0db240ba81 Mon Sep 17 00:00:00 2001 From: Jakub Date: Sat, 30 Sep 2023 18:22:29 +0200 Subject: [PATCH 11/36] Start refactoring nodestats collector to collect multiple instances --- collectors/collector_manager.go | 2 +- collectors/nodestats/nodestats_collector.go | 177 ++++++++++++------ .../nodestats/nodestats_collector_test.go | 7 +- collectors/nodestats/pipeline_subcollector.go | 118 +++++++----- prometheus_helper/prometheus_helper.go | 2 + prometheus_helper/prometheus_helper_test.go | 2 +- 6 files changed, 195 insertions(+), 113 deletions(-) diff --git a/collectors/collector_manager.go b/collectors/collector_manager.go index 783b55a9..fdd1ff05 100644 --- a/collectors/collector_manager.go +++ b/collectors/collector_manager.go @@ -48,7 +48,7 @@ func NewCollectorManager(endpoints []string) *CollectorManager { func getCollectors(clients []logstashclient.Client) map[string]Collector { collectors := make(map[string]Collector) collectors["nodeinfo"] = nodeinfo.NewNodeinfoCollector(clients) - collectors["nodestats"] = nodestats.NewNodestatsCollector(clients[0]) // TODO: support multiple clients + collectors["nodestats"] = nodestats.NewNodestatsCollector(clients) // TODO: support multiple clients return collectors } diff --git a/collectors/nodestats/nodestats_collector.go b/collectors/nodestats/nodestats_collector.go index e1a042e3..d8a0d1a5 100644 --- a/collectors/nodestats/nodestats_collector.go +++ b/collectors/nodestats/nodestats_collector.go @@ -2,6 +2,9 @@ package nodestats import ( "context" + "errors" + "fmt" + "sync" "github.com/prometheus/client_golang/prometheus" @@ -18,7 +21,7 @@ var ( // NodestatsCollector is a custom collector for the /_node/stats endpoint type NodestatsCollector struct { - client logstashclient.Client + clients []logstashclient.Client pipelineSubcollector *PipelineSubcollector JvmThreadsCount *prometheus.Desc @@ -70,11 +73,11 @@ type NodestatsCollector struct { FlowWorkerConcurrencyLifetime *prometheus.Desc } -func NewNodestatsCollector(client logstashclient.Client) *NodestatsCollector { +func NewNodestatsCollector(clients []logstashclient.Client) *NodestatsCollector { descHelper := prometheus_helper.SimpleDescHelper{Namespace: namespace, Subsystem: subsystem} return &NodestatsCollector{ - client: client, + clients: clients, pipelineSubcollector: NewPipelineSubcollector(), @@ -135,75 +138,129 @@ func NewNodestatsCollector(client logstashclient.Client) *NodestatsCollector { } func (c *NodestatsCollector) Collect(ctx context.Context, ch chan<- prometheus.Metric) error { - nodeStats, err := c.client.GetNodeStats(ctx) + wg := sync.WaitGroup{} + wg.Add(len(c.clients)) + + errorChannel := make(chan error, len(c.clients)) + + for _, client := range c.clients { + go func(client logstashclient.Client) { + err := c.collectSingleInstance(client, ctx, ch) + if err != nil { + errorChannel <- err + } + wg.Done() + }(client) + } + + wg.Wait() + close(errorChannel) + + if len(errorChannel) == 0 { + return nil + } + + if len(errorChannel) == 1 { + return <-errorChannel + } + + errorString := fmt.Sprintf("encountered %d errors while collecting nodeinfo metrics", len(errorChannel)) + for err := range errorChannel { + errorString += fmt.Sprintf("\n\t%s", err.Error()) + } + + return errors.New(errorString) +} + +func (c *NodestatsCollector) collectSingleInstance(client logstashclient.Client, ctx context.Context, ch chan<- prometheus.Metric) error { + nodeStats, err := client.GetNodeStats(ctx) if err != nil { return err } - ch <- prometheus.MustNewConstMetric(c.JvmThreadsCount, prometheus.GaugeValue, float64(nodeStats.Jvm.Threads.Count)) - ch <- prometheus.MustNewConstMetric(c.JvmThreadsPeakCount, prometheus.GaugeValue, float64(nodeStats.Jvm.Threads.PeakCount)) + endpoint := client.GetEndpoint() - ch <- prometheus.MustNewConstMetric(c.JvmMemHeapUsedPercent, prometheus.GaugeValue, float64(nodeStats.Jvm.Mem.HeapUsedPercent)) - ch <- prometheus.MustNewConstMetric(c.JvmMemHeapCommittedBytes, prometheus.GaugeValue, float64(nodeStats.Jvm.Mem.HeapCommittedInBytes)) - ch <- prometheus.MustNewConstMetric(c.JvmMemHeapMaxBytes, prometheus.GaugeValue, float64(nodeStats.Jvm.Mem.HeapMaxInBytes)) - ch <- prometheus.MustNewConstMetric(c.JvmMemHeapUsedBytes, prometheus.GaugeValue, float64(nodeStats.Jvm.Mem.HeapUsedInBytes)) - ch <- prometheus.MustNewConstMetric(c.JvmMemNonHeapCommittedBytes, prometheus.GaugeValue, float64(nodeStats.Jvm.Mem.NonHeapCommittedInBytes)) + newFloatMetric := func(desc *prometheus.Desc, metricType prometheus.ValueType, value float64, labels ...string) { + labels = append(labels, endpoint) + metric := prometheus.MustNewConstMetric(desc, metricType, value, labels...) + + ch <- metric + } + + newInt64Metric := func(desc *prometheus.Desc, metricType prometheus.ValueType, value int64, labels ...string) { + newFloatMetric(desc, metricType, float64(value), labels...) + } + + newIntMetric := func(desc *prometheus.Desc, metricType prometheus.ValueType, value int, labels ...string) { + newFloatMetric(desc, metricType, float64(value), labels...) + } + + newIntMetric(c.JvmThreadsCount, prometheus.GaugeValue, nodeStats.Jvm.Threads.Count) + newIntMetric(c.JvmThreadsPeakCount, prometheus.GaugeValue, nodeStats.Jvm.Threads.PeakCount) + + newIntMetric(c.JvmMemHeapUsedPercent, prometheus.GaugeValue, nodeStats.Jvm.Mem.HeapUsedPercent) + newIntMetric(c.JvmMemHeapCommittedBytes, prometheus.GaugeValue, nodeStats.Jvm.Mem.HeapCommittedInBytes) + newIntMetric(c.JvmMemHeapMaxBytes, prometheus.GaugeValue, nodeStats.Jvm.Mem.HeapMaxInBytes) + newIntMetric(c.JvmMemHeapUsedBytes, prometheus.GaugeValue, nodeStats.Jvm.Mem.HeapUsedInBytes) + newIntMetric(c.JvmMemNonHeapCommittedBytes, prometheus.GaugeValue, nodeStats.Jvm.Mem.NonHeapCommittedInBytes) // POOLS // young - ch <- prometheus.MustNewConstMetric(c.JvmMemPoolPeakUsedInBytes, prometheus.GaugeValue, float64(nodeStats.Jvm.Mem.Pools.Young.PeakUsedInBytes), "young") - ch <- prometheus.MustNewConstMetric(c.JvmMemPoolUsedInBytes, prometheus.GaugeValue, float64(nodeStats.Jvm.Mem.Pools.Young.UsedInBytes), "young") - ch <- prometheus.MustNewConstMetric(c.JvmMemPoolPeakMaxInBytes, prometheus.GaugeValue, float64(nodeStats.Jvm.Mem.Pools.Young.PeakMaxInBytes), "young") - ch <- prometheus.MustNewConstMetric(c.JvmMemPoolMaxInBytes, prometheus.GaugeValue, float64(nodeStats.Jvm.Mem.Pools.Young.MaxInBytes), "young") - ch <- prometheus.MustNewConstMetric(c.JvmMemPoolCommittedInBytes, prometheus.GaugeValue, float64(nodeStats.Jvm.Mem.Pools.Young.CommittedInBytes), "young") + newIntMetric(c.JvmMemPoolPeakUsedInBytes, prometheus.GaugeValue, nodeStats.Jvm.Mem.Pools.Young.PeakUsedInBytes, "young") + newIntMetric(c.JvmMemPoolUsedInBytes, prometheus.GaugeValue, nodeStats.Jvm.Mem.Pools.Young.UsedInBytes, "young") + newIntMetric(c.JvmMemPoolPeakMaxInBytes, prometheus.GaugeValue, nodeStats.Jvm.Mem.Pools.Young.PeakMaxInBytes, "young") + newIntMetric(c.JvmMemPoolMaxInBytes, prometheus.GaugeValue, nodeStats.Jvm.Mem.Pools.Young.MaxInBytes, "young") + newIntMetric(c.JvmMemPoolCommittedInBytes, prometheus.GaugeValue, nodeStats.Jvm.Mem.Pools.Young.CommittedInBytes, "young") + // old - ch <- prometheus.MustNewConstMetric(c.JvmMemPoolPeakUsedInBytes, prometheus.GaugeValue, float64(nodeStats.Jvm.Mem.Pools.Old.PeakUsedInBytes), "old") - ch <- prometheus.MustNewConstMetric(c.JvmMemPoolUsedInBytes, prometheus.GaugeValue, float64(nodeStats.Jvm.Mem.Pools.Old.UsedInBytes), "old") - ch <- prometheus.MustNewConstMetric(c.JvmMemPoolPeakMaxInBytes, prometheus.GaugeValue, float64(nodeStats.Jvm.Mem.Pools.Old.PeakMaxInBytes), "old") - ch <- prometheus.MustNewConstMetric(c.JvmMemPoolMaxInBytes, prometheus.GaugeValue, float64(nodeStats.Jvm.Mem.Pools.Old.MaxInBytes), "old") - ch <- prometheus.MustNewConstMetric(c.JvmMemPoolCommittedInBytes, prometheus.GaugeValue, float64(nodeStats.Jvm.Mem.Pools.Old.CommittedInBytes), "old") + newIntMetric(c.JvmMemPoolPeakUsedInBytes, prometheus.GaugeValue, nodeStats.Jvm.Mem.Pools.Old.PeakUsedInBytes, "old") + newIntMetric(c.JvmMemPoolUsedInBytes, prometheus.GaugeValue, nodeStats.Jvm.Mem.Pools.Old.UsedInBytes, "old") + newIntMetric(c.JvmMemPoolPeakMaxInBytes, prometheus.GaugeValue, nodeStats.Jvm.Mem.Pools.Old.PeakMaxInBytes, "old") + newIntMetric(c.JvmMemPoolMaxInBytes, prometheus.GaugeValue, nodeStats.Jvm.Mem.Pools.Old.MaxInBytes, "old") + newIntMetric(c.JvmMemPoolCommittedInBytes, prometheus.GaugeValue, nodeStats.Jvm.Mem.Pools.Old.CommittedInBytes, "old") + // survivor - ch <- prometheus.MustNewConstMetric(c.JvmMemPoolPeakUsedInBytes, prometheus.GaugeValue, float64(nodeStats.Jvm.Mem.Pools.Survivor.PeakUsedInBytes), "survivor") - ch <- prometheus.MustNewConstMetric(c.JvmMemPoolUsedInBytes, prometheus.GaugeValue, float64(nodeStats.Jvm.Mem.Pools.Survivor.UsedInBytes), "survivor") - ch <- prometheus.MustNewConstMetric(c.JvmMemPoolPeakMaxInBytes, prometheus.GaugeValue, float64(nodeStats.Jvm.Mem.Pools.Survivor.PeakMaxInBytes), "survivor") - ch <- prometheus.MustNewConstMetric(c.JvmMemPoolMaxInBytes, prometheus.GaugeValue, float64(nodeStats.Jvm.Mem.Pools.Survivor.MaxInBytes), "survivor") - ch <- prometheus.MustNewConstMetric(c.JvmMemPoolCommittedInBytes, prometheus.GaugeValue, float64(nodeStats.Jvm.Mem.Pools.Survivor.CommittedInBytes), "survivor") - - ch <- prometheus.MustNewConstMetric(c.JvmUptimeMillis, prometheus.GaugeValue, float64(nodeStats.Jvm.UptimeInMillis)) - - ch <- prometheus.MustNewConstMetric(c.ProcessOpenFileDescriptors, prometheus.GaugeValue, float64(nodeStats.Process.OpenFileDescriptors)) - ch <- prometheus.MustNewConstMetric(c.ProcessMaxFileDescriptors, prometheus.GaugeValue, float64(nodeStats.Process.MaxFileDescriptors)) - ch <- prometheus.MustNewConstMetric(c.ProcessCpuPercent, prometheus.GaugeValue, float64(nodeStats.Process.CPU.Percent)) - ch <- prometheus.MustNewConstMetric(c.ProcessCpuTotalMillis, prometheus.GaugeValue, float64(nodeStats.Process.CPU.TotalInMillis)) - ch <- prometheus.MustNewConstMetric(c.ProcessCpuLoadAverageOneM, prometheus.GaugeValue, float64(nodeStats.Process.CPU.LoadAverage.OneM)) - ch <- prometheus.MustNewConstMetric(c.ProcessCpuLoadAverageFiveM, prometheus.GaugeValue, float64(nodeStats.Process.CPU.LoadAverage.FiveM)) - ch <- prometheus.MustNewConstMetric(c.ProcessCpuLoadAverageFifteenM, prometheus.GaugeValue, float64(nodeStats.Process.CPU.LoadAverage.FifteenM)) - ch <- prometheus.MustNewConstMetric(c.ProcessMemTotalVirtual, prometheus.GaugeValue, float64(nodeStats.Process.Mem.TotalVirtualInBytes)) - - ch <- prometheus.MustNewConstMetric(c.ReloadSuccesses, prometheus.GaugeValue, float64(nodeStats.Reloads.Successes)) - ch <- prometheus.MustNewConstMetric(c.ReloadFailures, prometheus.GaugeValue, float64(nodeStats.Reloads.Failures)) - - ch <- prometheus.MustNewConstMetric(c.QueueEventsCount, prometheus.GaugeValue, float64(nodeStats.Queue.EventsCount)) - - ch <- prometheus.MustNewConstMetric(c.EventsIn, prometheus.GaugeValue, float64(nodeStats.Events.In)) - ch <- prometheus.MustNewConstMetric(c.EventsFiltered, prometheus.GaugeValue, float64(nodeStats.Events.Filtered)) - ch <- prometheus.MustNewConstMetric(c.EventsOut, prometheus.GaugeValue, float64(nodeStats.Events.Out)) - ch <- prometheus.MustNewConstMetric(c.EventsDurationInMillis, prometheus.GaugeValue, float64(nodeStats.Events.DurationInMillis)) - ch <- prometheus.MustNewConstMetric(c.EventsQueuePushDurationInMillis, prometheus.GaugeValue, float64(nodeStats.Events.QueuePushDurationInMillis)) - - ch <- prometheus.MustNewConstMetric(c.FlowInputCurrent, prometheus.GaugeValue, float64(nodeStats.Flow.InputThroughput.Current)) - ch <- prometheus.MustNewConstMetric(c.FlowInputLifetime, prometheus.GaugeValue, float64(nodeStats.Flow.InputThroughput.Lifetime)) - ch <- prometheus.MustNewConstMetric(c.FlowFilterCurrent, prometheus.GaugeValue, float64(nodeStats.Flow.FilterThroughput.Current)) - ch <- prometheus.MustNewConstMetric(c.FlowFilterLifetime, prometheus.GaugeValue, float64(nodeStats.Flow.FilterThroughput.Lifetime)) - ch <- prometheus.MustNewConstMetric(c.FlowOutputCurrent, prometheus.GaugeValue, float64(nodeStats.Flow.OutputThroughput.Current)) - ch <- prometheus.MustNewConstMetric(c.FlowOutputLifetime, prometheus.GaugeValue, float64(nodeStats.Flow.OutputThroughput.Lifetime)) - ch <- prometheus.MustNewConstMetric(c.FlowQueueBackpressureCurrent, prometheus.GaugeValue, float64(nodeStats.Flow.QueueBackpressure.Current)) - ch <- prometheus.MustNewConstMetric(c.FlowQueueBackpressureLifetime, prometheus.GaugeValue, float64(nodeStats.Flow.QueueBackpressure.Lifetime)) - ch <- prometheus.MustNewConstMetric(c.FlowWorkerConcurrencyCurrent, prometheus.GaugeValue, float64(nodeStats.Flow.WorkerConcurrency.Current)) - ch <- prometheus.MustNewConstMetric(c.FlowWorkerConcurrencyLifetime, prometheus.GaugeValue, float64(nodeStats.Flow.WorkerConcurrency.Lifetime)) + newIntMetric(c.JvmMemPoolPeakUsedInBytes, prometheus.GaugeValue, nodeStats.Jvm.Mem.Pools.Survivor.PeakUsedInBytes, "survivor") + newIntMetric(c.JvmMemPoolUsedInBytes, prometheus.GaugeValue, nodeStats.Jvm.Mem.Pools.Survivor.UsedInBytes, "survivor") + newIntMetric(c.JvmMemPoolPeakMaxInBytes, prometheus.GaugeValue, nodeStats.Jvm.Mem.Pools.Survivor.PeakMaxInBytes, "survivor") + newIntMetric(c.JvmMemPoolMaxInBytes, prometheus.GaugeValue, nodeStats.Jvm.Mem.Pools.Survivor.MaxInBytes, "survivor") + newIntMetric(c.JvmMemPoolCommittedInBytes, prometheus.GaugeValue, nodeStats.Jvm.Mem.Pools.Survivor.CommittedInBytes, "survivor") + + newIntMetric(c.JvmUptimeMillis, prometheus.GaugeValue, nodeStats.Jvm.UptimeInMillis) + + newInt64Metric(c.ProcessOpenFileDescriptors, prometheus.GaugeValue, nodeStats.Process.OpenFileDescriptors) + newInt64Metric(c.ProcessMaxFileDescriptors, prometheus.GaugeValue, nodeStats.Process.MaxFileDescriptors) + newInt64Metric(c.ProcessCpuPercent, prometheus.GaugeValue, nodeStats.Process.CPU.Percent) + newInt64Metric(c.ProcessCpuTotalMillis, prometheus.GaugeValue, nodeStats.Process.CPU.TotalInMillis) + newFloatMetric(c.ProcessCpuLoadAverageOneM, prometheus.GaugeValue, nodeStats.Process.CPU.LoadAverage.OneM) + newFloatMetric(c.ProcessCpuLoadAverageFiveM, prometheus.GaugeValue, nodeStats.Process.CPU.LoadAverage.FiveM) + newFloatMetric(c.ProcessCpuLoadAverageFifteenM, prometheus.GaugeValue, nodeStats.Process.CPU.LoadAverage.FifteenM) + newInt64Metric(c.ProcessMemTotalVirtual, prometheus.GaugeValue, nodeStats.Process.Mem.TotalVirtualInBytes) + + newIntMetric(c.ReloadSuccesses, prometheus.GaugeValue, nodeStats.Reloads.Successes) + newIntMetric(c.ReloadFailures, prometheus.GaugeValue, nodeStats.Reloads.Failures) + + newIntMetric(c.QueueEventsCount, prometheus.GaugeValue, nodeStats.Queue.EventsCount) + + newInt64Metric(c.EventsIn, prometheus.GaugeValue, nodeStats.Events.In) + newInt64Metric(c.EventsFiltered, prometheus.GaugeValue, nodeStats.Events.Filtered) + newInt64Metric(c.EventsOut, prometheus.GaugeValue, nodeStats.Events.Out) + newInt64Metric(c.EventsDurationInMillis, prometheus.GaugeValue, nodeStats.Events.DurationInMillis) + newInt64Metric(c.EventsQueuePushDurationInMillis, prometheus.GaugeValue, nodeStats.Events.QueuePushDurationInMillis) + + newFloatMetric(c.FlowInputCurrent, prometheus.GaugeValue, nodeStats.Flow.InputThroughput.Current) + newFloatMetric(c.FlowInputLifetime, prometheus.GaugeValue, nodeStats.Flow.InputThroughput.Lifetime) + newFloatMetric(c.FlowFilterCurrent, prometheus.GaugeValue, nodeStats.Flow.FilterThroughput.Current) + newFloatMetric(c.FlowFilterLifetime, prometheus.GaugeValue, nodeStats.Flow.FilterThroughput.Lifetime) + newFloatMetric(c.FlowOutputCurrent, prometheus.GaugeValue, nodeStats.Flow.OutputThroughput.Current) + newFloatMetric(c.FlowOutputLifetime, prometheus.GaugeValue, nodeStats.Flow.OutputThroughput.Lifetime) + newFloatMetric(c.FlowQueueBackpressureCurrent, prometheus.GaugeValue, nodeStats.Flow.QueueBackpressure.Current) + newFloatMetric(c.FlowQueueBackpressureLifetime, prometheus.GaugeValue, nodeStats.Flow.QueueBackpressure.Lifetime) + newFloatMetric(c.FlowWorkerConcurrencyCurrent, prometheus.GaugeValue, nodeStats.Flow.WorkerConcurrency.Current) + newFloatMetric(c.FlowWorkerConcurrencyLifetime, prometheus.GaugeValue, nodeStats.Flow.WorkerConcurrency.Lifetime) for pipelineId, pipelineStats := range nodeStats.Pipelines { - c.pipelineSubcollector.Collect(&pipelineStats, pipelineId, ch) + c.pipelineSubcollector.Collect(&pipelineStats, pipelineId, ch, endpoint) } return nil diff --git a/collectors/nodestats/nodestats_collector_test.go b/collectors/nodestats/nodestats_collector_test.go index 857bb610..c8e12f77 100644 --- a/collectors/nodestats/nodestats_collector_test.go +++ b/collectors/nodestats/nodestats_collector_test.go @@ -10,6 +10,7 @@ import ( "github.com/prometheus/client_golang/prometheus" + "github.com/kuskoman/logstash-exporter/fetcher/logstash_client" "github.com/kuskoman/logstash-exporter/fetcher/responses" "github.com/kuskoman/logstash-exporter/prometheus_helper" ) @@ -54,7 +55,10 @@ func (m *errorMockClient) GetEndpoint() string { } func TestCollectNotNil(t *testing.T) { - collector := NewNodestatsCollector(&mockClient{}) + t.Parallel() + + clients := []logstash_client.Client{&mockClient{}, &mockClient{}} + collector := NewNodestatsCollector(clients) ch := make(chan prometheus.Metric) ctx := context.Background() @@ -126,6 +130,7 @@ func TestCollectNotNil(t *testing.T) { t.Errorf("failed to extract fqName from metric %s", foundMetricDesc) } + // todo: check if exists foundMetrics = append(foundMetrics, foundMetricFqName) } diff --git a/collectors/nodestats/pipeline_subcollector.go b/collectors/nodestats/pipeline_subcollector.go index 5a8905b0..d29e6cb0 100644 --- a/collectors/nodestats/pipeline_subcollector.go +++ b/collectors/nodestats/pipeline_subcollector.go @@ -110,49 +110,67 @@ func NewPipelineSubcollector() *PipelineSubcollector { } } -func (collector *PipelineSubcollector) Collect(pipeStats *responses.SinglePipelineResponse, pipelineID string, ch chan<- prometheus.Metric) { +func (collector *PipelineSubcollector) Collect(pipeStats *responses.SinglePipelineResponse, pipelineID string, ch chan<- prometheus.Metric, endpoint string) { collectingStart := time.Now() slog.Debug("collecting pipeline stats for pipeline", "pipelineID", pipelineID) - ch <- prometheus.MustNewConstMetric(collector.EventsOut, prometheus.CounterValue, float64(pipeStats.Events.Out), pipelineID) - ch <- prometheus.MustNewConstMetric(collector.EventsFiltered, prometheus.CounterValue, float64(pipeStats.Events.Filtered), pipelineID) - ch <- prometheus.MustNewConstMetric(collector.EventsIn, prometheus.CounterValue, float64(pipeStats.Events.In), pipelineID) - ch <- prometheus.MustNewConstMetric(collector.EventsDuration, prometheus.CounterValue, float64(pipeStats.Events.DurationInMillis), pipelineID) - ch <- prometheus.MustNewConstMetric(collector.EventsQueuePushDuration, prometheus.CounterValue, float64(pipeStats.Events.QueuePushDurationInMillis), pipelineID) + newFloatMetric := func(desc *prometheus.Desc, metricType prometheus.ValueType, value float64, labels ...string) { + labels = append(labels, pipelineID, endpoint) + metric := prometheus.MustNewConstMetric(desc, metricType, value, labels...) - ch <- prometheus.MustNewConstMetric(collector.Up, prometheus.GaugeValue, float64(collector.isPipelineHealthy(pipeStats.Reloads)), pipelineID) + ch <- metric + } + + newTimestampMetric := func(desc *prometheus.Desc, metricType prometheus.ValueType, value time.Time, labels ...string) { + labels = append(labels, pipelineID, endpoint) + metric := prometheus.NewMetricWithTimestamp(value, prometheus.MustNewConstMetric(desc, metricType, 1, labels...)) + + ch <- metric + } + + newIntMetric := func(desc *prometheus.Desc, metricType prometheus.ValueType, value int, labels ...string) { + newFloatMetric(desc, metricType, float64(value), labels...) + } + + newIntMetric(collector.EventsOut, prometheus.CounterValue, pipeStats.Events.Out) + newIntMetric(collector.EventsFiltered, prometheus.CounterValue, pipeStats.Events.Filtered) + newIntMetric(collector.EventsIn, prometheus.CounterValue, pipeStats.Events.In) + newIntMetric(collector.EventsDuration, prometheus.CounterValue, pipeStats.Events.DurationInMillis) + newIntMetric(collector.EventsQueuePushDuration, prometheus.CounterValue, pipeStats.Events.QueuePushDurationInMillis) + + newFloatMetric(collector.Up, prometheus.GaugeValue, collector.isPipelineHealthy(pipeStats.Reloads)) - ch <- prometheus.MustNewConstMetric(collector.ReloadsSuccesses, prometheus.CounterValue, float64(pipeStats.Reloads.Successes), pipelineID) - ch <- prometheus.MustNewConstMetric(collector.ReloadsFailures, prometheus.CounterValue, float64(pipeStats.Reloads.Failures), pipelineID) + newIntMetric(collector.ReloadsSuccesses, prometheus.CounterValue, pipeStats.Reloads.Successes) + newIntMetric(collector.ReloadsFailures, prometheus.CounterValue, pipeStats.Reloads.Failures) if pipeStats.Reloads.LastSuccessTimestamp != nil { - ch <- prometheus.NewMetricWithTimestamp(*pipeStats.Reloads.LastSuccessTimestamp, prometheus.MustNewConstMetric(collector.ReloadsLastSuccessTimestamp, prometheus.GaugeValue, 1, pipelineID)) + newTimestampMetric(collector.ReloadsLastSuccessTimestamp, prometheus.GaugeValue, *pipeStats.Reloads.LastSuccessTimestamp) } if pipeStats.Reloads.LastFailureTimestamp != nil { - ch <- prometheus.NewMetricWithTimestamp(*pipeStats.Reloads.LastFailureTimestamp, prometheus.MustNewConstMetric(collector.ReloadsLastFailureTimestamp, prometheus.GaugeValue, 1, pipelineID)) + newTimestampMetric(collector.ReloadsLastFailureTimestamp, prometheus.GaugeValue, *pipeStats.Reloads.LastFailureTimestamp) } - ch <- prometheus.MustNewConstMetric(collector.QueueEventsCount, prometheus.CounterValue, float64(pipeStats.Queue.EventsCount), pipelineID) - ch <- prometheus.MustNewConstMetric(collector.QueueEventsQueueSize, prometheus.CounterValue, float64(pipeStats.Queue.QueueSizeInBytes), pipelineID) - ch <- prometheus.MustNewConstMetric(collector.QueueMaxQueueSizeInBytes, prometheus.CounterValue, float64(pipeStats.Queue.MaxQueueSizeInBytes), pipelineID) + newIntMetric(collector.QueueEventsCount, prometheus.CounterValue, pipeStats.Queue.EventsCount) + newIntMetric(collector.QueueEventsQueueSize, prometheus.CounterValue, pipeStats.Queue.QueueSizeInBytes) + newIntMetric(collector.QueueMaxQueueSizeInBytes, prometheus.CounterValue, pipeStats.Queue.MaxQueueSizeInBytes) flowStats := pipeStats.Flow - ch <- prometheus.MustNewConstMetric(collector.FlowInputCurrent, prometheus.GaugeValue, float64(flowStats.InputThroughput.Current), pipelineID) - ch <- prometheus.MustNewConstMetric(collector.FlowInputLifetime, prometheus.CounterValue, float64(flowStats.InputThroughput.Lifetime), pipelineID) - ch <- prometheus.MustNewConstMetric(collector.FlowFilterCurrent, prometheus.GaugeValue, float64(flowStats.FilterThroughput.Current), pipelineID) - ch <- prometheus.MustNewConstMetric(collector.FlowFilterLifetime, prometheus.CounterValue, float64(flowStats.FilterThroughput.Lifetime), pipelineID) - ch <- prometheus.MustNewConstMetric(collector.FlowOutputCurrent, prometheus.GaugeValue, float64(flowStats.OutputThroughput.Current), pipelineID) - ch <- prometheus.MustNewConstMetric(collector.FlowOutputLifetime, prometheus.CounterValue, float64(flowStats.OutputThroughput.Lifetime), pipelineID) - ch <- prometheus.MustNewConstMetric(collector.FlowQueueBackpressureCurrent, prometheus.GaugeValue, float64(flowStats.QueueBackpressure.Current), pipelineID) - ch <- prometheus.MustNewConstMetric(collector.FlowQueueBackpressureLifetime, prometheus.CounterValue, float64(flowStats.QueueBackpressure.Lifetime), pipelineID) - ch <- prometheus.MustNewConstMetric(collector.FlowWorkerConcurrencyCurrent, prometheus.GaugeValue, float64(flowStats.WorkerConcurrency.Current), pipelineID) - ch <- prometheus.MustNewConstMetric(collector.FlowWorkerConcurrencyLifetime, prometheus.CounterValue, float64(flowStats.WorkerConcurrency.Lifetime), pipelineID) + newFloatMetric(collector.FlowInputCurrent, prometheus.GaugeValue, flowStats.InputThroughput.Current) + newFloatMetric(collector.FlowInputLifetime, prometheus.CounterValue, flowStats.InputThroughput.Lifetime) + newFloatMetric(collector.FlowFilterCurrent, prometheus.GaugeValue, flowStats.FilterThroughput.Current) + newFloatMetric(collector.FlowFilterLifetime, prometheus.CounterValue, flowStats.FilterThroughput.Lifetime) + newFloatMetric(collector.FlowOutputCurrent, prometheus.GaugeValue, flowStats.OutputThroughput.Current) + newFloatMetric(collector.FlowOutputLifetime, prometheus.CounterValue, flowStats.OutputThroughput.Lifetime) + newFloatMetric(collector.FlowQueueBackpressureCurrent, prometheus.GaugeValue, flowStats.QueueBackpressure.Current) + newFloatMetric(collector.FlowQueueBackpressureLifetime, prometheus.CounterValue, flowStats.QueueBackpressure.Lifetime) + newFloatMetric(collector.FlowWorkerConcurrencyCurrent, prometheus.GaugeValue, flowStats.WorkerConcurrency.Current) + newFloatMetric(collector.FlowWorkerConcurrencyLifetime, prometheus.CounterValue, flowStats.WorkerConcurrency.Lifetime) deadLetterQueueStats := pipeStats.DeadLetterQueue - ch <- prometheus.MustNewConstMetric(collector.DeadLetterQueueMaxSizeInBytes, prometheus.GaugeValue, float64(deadLetterQueueStats.MaxQueueSizeInBytes), pipelineID) - ch <- prometheus.MustNewConstMetric(collector.DeadLetterQueueSizeInBytes, prometheus.GaugeValue, float64(deadLetterQueueStats.QueueSizeInBytes), pipelineID) - ch <- prometheus.MustNewConstMetric(collector.DeadLetterQueueDroppedEvents, prometheus.CounterValue, float64(deadLetterQueueStats.DroppedEvents), pipelineID) - ch <- prometheus.MustNewConstMetric(collector.DeadLetterQueueExpiredEvents, prometheus.CounterValue, float64(deadLetterQueueStats.ExpiredEvents), pipelineID) + newIntMetric(collector.DeadLetterQueueMaxSizeInBytes, prometheus.GaugeValue, deadLetterQueueStats.MaxQueueSizeInBytes) + newIntMetric(collector.DeadLetterQueueSizeInBytes, prometheus.GaugeValue, deadLetterQueueStats.QueueSizeInBytes) + newIntMetric(collector.DeadLetterQueueDroppedEvents, prometheus.CounterValue, deadLetterQueueStats.DroppedEvents) + newIntMetric(collector.DeadLetterQueueExpiredEvents, prometheus.CounterValue, deadLetterQueueStats.ExpiredEvents) // Output error metrics for _, output := range pipeStats.Plugins.Outputs { @@ -162,49 +180,49 @@ func (collector *PipelineSubcollector) Collect(pipeStats *responses.SinglePipeli // Response codes returned by output Bulk Requests for code, count := range output.BulkRequests.Responses { - ch <- prometheus.MustNewConstMetric(collector.PipelinePluginBulkRequestResponses, prometheus.CounterValue, float64(count), pipelineID, pluginType, output.Name, pluginID, code) + ch <- prometheus.MustNewConstMetric(collector.PipelinePluginBulkRequestResponses, prometheus.CounterValue, float64(count), pipelineID, pluginType, output.Name, pluginID, code, endpoint) } - ch <- prometheus.MustNewConstMetric(collector.PipelinePluginDocumentsSuccesses, prometheus.CounterValue, float64(output.Documents.Successes), pipelineID, pluginType, output.Name, pluginID) - ch <- prometheus.MustNewConstMetric(collector.PipelinePluginDocumentsNonRetryableFailures, prometheus.CounterValue, float64(output.Documents.NonRetryableFailures), pipelineID, pluginType, output.Name, pluginID) - ch <- prometheus.MustNewConstMetric(collector.PipelinePluginBulkRequestErrors, prometheus.CounterValue, float64(output.BulkRequests.WithErrors), pipelineID, pluginType, output.Name, pluginID) + ch <- prometheus.MustNewConstMetric(collector.PipelinePluginDocumentsSuccesses, prometheus.CounterValue, float64(output.Documents.Successes), pipelineID, pluginType, output.Name, pluginID, endpoint) + ch <- prometheus.MustNewConstMetric(collector.PipelinePluginDocumentsNonRetryableFailures, prometheus.CounterValue, float64(output.Documents.NonRetryableFailures), pipelineID, pluginType, output.Name, pluginID, endpoint) + ch <- prometheus.MustNewConstMetric(collector.PipelinePluginBulkRequestErrors, prometheus.CounterValue, float64(output.BulkRequests.WithErrors), pipelineID, pluginType, output.Name, pluginID, endpoint) } // Pipeline plugins metrics for _, plugin := range pipeStats.Plugins.Inputs { pluginType := "input" - slog.Debug("collecting pipeline plugin stats for pipeline", "pipelineID", pipelineID, "plugin type", pluginType, "name", plugin.Name, "id", plugin.ID) - ch <- prometheus.MustNewConstMetric(collector.PipelinePluginEventsOut, prometheus.CounterValue, float64(plugin.Events.Out), pipelineID, pluginType, plugin.Name, plugin.ID) - ch <- prometheus.MustNewConstMetric(collector.PipelinePluginEventsQueuePushDuration, prometheus.CounterValue, float64(plugin.Events.QueuePushDurationInMillis), pipelineID, pluginType, plugin.Name, plugin.ID) + slog.Debug("collecting pipeline plugin stats for pipeline", "pipelineID", pipelineID, "plugin type", pluginType, "name", plugin.Name, "id", plugin.ID, "endpoint", endpoint) + ch <- prometheus.MustNewConstMetric(collector.PipelinePluginEventsOut, prometheus.CounterValue, float64(plugin.Events.Out), pipelineID, pluginType, plugin.Name, plugin.ID, endpoint) + ch <- prometheus.MustNewConstMetric(collector.PipelinePluginEventsQueuePushDuration, prometheus.CounterValue, float64(plugin.Events.QueuePushDurationInMillis), pipelineID, pluginType, plugin.Name, plugin.ID, endpoint) } for _, plugin := range pipeStats.Plugins.Codecs { - slog.Debug("collecting pipeline plugin stats for pipeline", "pipelineID", pipelineID, "plugin type", "codec", "name", plugin.Name, "id", plugin.ID) - ch <- prometheus.MustNewConstMetric(collector.PipelinePluginEventsIn, prometheus.CounterValue, float64(plugin.Encode.WritesIn), pipelineID, "codec:encode", plugin.Name, plugin.ID) - ch <- prometheus.MustNewConstMetric(collector.PipelinePluginEventsIn, prometheus.CounterValue, float64(plugin.Decode.WritesIn), pipelineID, "codec:decode", plugin.Name, plugin.ID) - ch <- prometheus.MustNewConstMetric(collector.PipelinePluginEventsOut, prometheus.CounterValue, float64(plugin.Decode.Out), pipelineID, "codec:decode", plugin.Name, plugin.ID) - ch <- prometheus.MustNewConstMetric(collector.PipelinePluginEventsDuration, prometheus.CounterValue, float64(plugin.Encode.DurationInMillis), pipelineID, "codec:encode", plugin.Name, plugin.ID) - ch <- prometheus.MustNewConstMetric(collector.PipelinePluginEventsDuration, prometheus.CounterValue, float64(plugin.Decode.DurationInMillis), pipelineID, "codec:decode", plugin.Name, plugin.ID) + slog.Debug("collecting pipeline plugin stats for pipeline", "pipelineID", pipelineID, "plugin type", "codec", "name", plugin.Name, "id", plugin.ID, "endpoint", endpoint) + ch <- prometheus.MustNewConstMetric(collector.PipelinePluginEventsIn, prometheus.CounterValue, float64(plugin.Encode.WritesIn), pipelineID, "codec:encode", plugin.Name, plugin.ID, endpoint) + ch <- prometheus.MustNewConstMetric(collector.PipelinePluginEventsIn, prometheus.CounterValue, float64(plugin.Decode.WritesIn), pipelineID, "codec:decode", plugin.Name, plugin.ID, endpoint) + ch <- prometheus.MustNewConstMetric(collector.PipelinePluginEventsOut, prometheus.CounterValue, float64(plugin.Decode.Out), pipelineID, "codec:decode", plugin.Name, plugin.ID, endpoint) + ch <- prometheus.MustNewConstMetric(collector.PipelinePluginEventsDuration, prometheus.CounterValue, float64(plugin.Encode.DurationInMillis), pipelineID, "codec:encode", plugin.Name, plugin.ID, endpoint) + ch <- prometheus.MustNewConstMetric(collector.PipelinePluginEventsDuration, prometheus.CounterValue, float64(plugin.Decode.DurationInMillis), pipelineID, "codec:decode", plugin.Name, plugin.ID, endpoint) } for _, plugin := range pipeStats.Plugins.Filters { pluginType := "filter" - slog.Debug("collecting pipeline plugin stats for pipeline", "pipelineID", pipelineID, "plugin type", pluginType, "name", plugin.Name, "id", plugin.ID) - ch <- prometheus.MustNewConstMetric(collector.PipelinePluginEventsIn, prometheus.CounterValue, float64(plugin.Events.In), pipelineID, pluginType, plugin.Name, plugin.ID) - ch <- prometheus.MustNewConstMetric(collector.PipelinePluginEventsOut, prometheus.CounterValue, float64(plugin.Events.Out), pipelineID, pluginType, plugin.Name, plugin.ID) - ch <- prometheus.MustNewConstMetric(collector.PipelinePluginEventsDuration, prometheus.CounterValue, float64(plugin.Events.DurationInMillis), pipelineID, pluginType, plugin.Name, plugin.ID) + slog.Debug("collecting pipeline plugin stats for pipeline", "pipelineID", pipelineID, "plugin type", pluginType, "name", plugin.Name, "id", plugin.ID, "endpoint", endpoint) + ch <- prometheus.MustNewConstMetric(collector.PipelinePluginEventsIn, prometheus.CounterValue, float64(plugin.Events.In), pipelineID, pluginType, plugin.Name, plugin.ID, endpoint) + ch <- prometheus.MustNewConstMetric(collector.PipelinePluginEventsOut, prometheus.CounterValue, float64(plugin.Events.Out), pipelineID, pluginType, plugin.Name, plugin.ID, endpoint) + ch <- prometheus.MustNewConstMetric(collector.PipelinePluginEventsDuration, prometheus.CounterValue, float64(plugin.Events.DurationInMillis), pipelineID, pluginType, plugin.Name, plugin.ID, endpoint) } for _, plugin := range pipeStats.Plugins.Outputs { pluginType := "output" - slog.Debug("collecting pipeline plugin stats for pipeline", "pipelineID", pipelineID, "plugin type", pluginType, "name", plugin.Name, "id", plugin.ID) - ch <- prometheus.MustNewConstMetric(collector.PipelinePluginEventsIn, prometheus.CounterValue, float64(plugin.Events.In), pipelineID, pluginType, plugin.Name, plugin.ID) - ch <- prometheus.MustNewConstMetric(collector.PipelinePluginEventsOut, prometheus.CounterValue, float64(plugin.Events.Out), pipelineID, pluginType, plugin.Name, plugin.ID) - ch <- prometheus.MustNewConstMetric(collector.PipelinePluginEventsDuration, prometheus.CounterValue, float64(plugin.Events.DurationInMillis), pipelineID, pluginType, plugin.Name, plugin.ID) + slog.Debug("collecting pipeline plugin stats for pipeline", "pipelineID", pipelineID, "plugin type", pluginType, "name", plugin.Name, "id", plugin.ID, "endpoint", endpoint) + ch <- prometheus.MustNewConstMetric(collector.PipelinePluginEventsIn, prometheus.CounterValue, float64(plugin.Events.In), pipelineID, pluginType, plugin.Name, plugin.ID, endpoint) + ch <- prometheus.MustNewConstMetric(collector.PipelinePluginEventsOut, prometheus.CounterValue, float64(plugin.Events.Out), pipelineID, pluginType, plugin.Name, plugin.ID, endpoint) + ch <- prometheus.MustNewConstMetric(collector.PipelinePluginEventsDuration, prometheus.CounterValue, float64(plugin.Events.DurationInMillis), pipelineID, pluginType, plugin.Name, plugin.ID, endpoint) } collectingEnd := time.Now() - slog.Debug("collected pipeline stats for pipeline", "pipelineID", pipelineID, "duration", collectingEnd.Sub(collectingStart)) + slog.Debug("collected pipeline stats for pipeline", "pipelineID", pipelineID, "duration", collectingEnd.Sub(collectingStart), "endpoint", endpoint) } // isPipelineHealthy returns 1 if the pipeline is healthy, 0 if it is not diff --git a/prometheus_helper/prometheus_helper.go b/prometheus_helper/prometheus_helper.go index 093d8e5f..05a0b030 100644 --- a/prometheus_helper/prometheus_helper.go +++ b/prometheus_helper/prometheus_helper.go @@ -17,7 +17,9 @@ type SimpleDescHelper struct { // NewDescWithLabel creates a new prometheus.Desc with the namespace and subsystem. // Labels are used to differentiate between different sources of the same metric. +// Labels are always appended with "hostname" to differentiate between different instances. func (h *SimpleDescHelper) NewDesc(name string, help string, labels ...string) *prometheus.Desc { + labels = append(labels, "hostname") return prometheus.NewDesc(prometheus.BuildFQName(h.Namespace, h.Subsystem, name), help, labels, nil) } diff --git a/prometheus_helper/prometheus_helper_test.go b/prometheus_helper/prometheus_helper_test.go index afe7adbd..17654e81 100644 --- a/prometheus_helper/prometheus_helper_test.go +++ b/prometheus_helper/prometheus_helper_test.go @@ -17,7 +17,7 @@ func TestSimpleDescHelper(t *testing.T) { t.Run("NewDescWithHelpAndLabel", func(t *testing.T) { desc := helper.NewDesc("metric", "help", "customLabel") - expectedDesc := "Desc{fqName: \"logstash_exporter_test_metric\", help: \"help\", constLabels: {}, variableLabels: {customLabel}}" + expectedDesc := "Desc{fqName: \"logstash_exporter_test_metric\", help: \"help\", constLabels: {}, variableLabels: {customLabel,hostname}}" if desc.String() != expectedDesc { t.Errorf("incorrect metric description, expected %s but got %s", expectedDesc, desc.String()) } From eaf14ed13e47f5ffcea1f2339acf03f53022fd37 Mon Sep 17 00:00:00 2001 From: Jakub Date: Sat, 16 Dec 2023 18:11:25 +0100 Subject: [PATCH 12/36] Fix nodestats_collector_test --- collectors/nodestats/nodestats_collector_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/collectors/nodestats/nodestats_collector_test.go b/collectors/nodestats/nodestats_collector_test.go index c8e12f77..997f7a7a 100644 --- a/collectors/nodestats/nodestats_collector_test.go +++ b/collectors/nodestats/nodestats_collector_test.go @@ -150,7 +150,9 @@ func TestCollectNotNil(t *testing.T) { } func TestCollectError(t *testing.T) { - collector := NewNodestatsCollector(&errorMockClient{}) + t.Parallel() + clients := []logstash_client.Client{&errorMockClient{}, &errorMockClient{}} + collector := NewNodestatsCollector(clients) ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() From 0f2089d5cf64dc0d89e2166730e36cca9c98c41d Mon Sep 17 00:00:00 2001 From: Jakub Date: Sat, 16 Dec 2023 18:19:43 +0100 Subject: [PATCH 13/36] Add missing test case for logstash client --- fetcher/logstash_client/client_test.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/fetcher/logstash_client/client_test.go b/fetcher/logstash_client/client_test.go index a7e100fd..7ec3608d 100644 --- a/fetcher/logstash_client/client_test.go +++ b/fetcher/logstash_client/client_test.go @@ -24,11 +24,12 @@ func TestNewClient(t *testing.T) { }) t.Run("should return a new client for the given endpoint", func(t *testing.T) { - endpoint := "http://localhost:9601" - client := NewClient(endpoint) + expectedEndpoint := "http://localhost:9601" + client := NewClient(expectedEndpoint) - if client.(*DefaultClient).endpoint != endpoint { - t.Errorf("expected endpoint to be %s, got %s", endpoint, client.(*DefaultClient).endpoint) + receivedEndpoint := client.GetEndpoint() + if receivedEndpoint != expectedEndpoint { + t.Errorf("expected endpoint to be %s, got %s", expectedEndpoint, receivedEndpoint) } }) } From 370ffe47d4f7193752a49cff4fbaffee9991ebe0 Mon Sep 17 00:00:00 2001 From: Jakub Date: Sat, 16 Dec 2023 18:25:50 +0100 Subject: [PATCH 14/36] Bring back 100% coverage --- .../nodestats/nodestats_collector_test.go | 47 +++++++++++++------ 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/collectors/nodestats/nodestats_collector_test.go b/collectors/nodestats/nodestats_collector_test.go index 997f7a7a..f54e396b 100644 --- a/collectors/nodestats/nodestats_collector_test.go +++ b/collectors/nodestats/nodestats_collector_test.go @@ -149,25 +149,42 @@ func TestCollectNotNil(t *testing.T) { } } -func TestCollectError(t *testing.T) { +func TestCollectsErrors(t *testing.T) { t.Parallel() - clients := []logstash_client.Client{&errorMockClient{}, &errorMockClient{}} - collector := NewNodestatsCollector(clients) - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - defer cancel() - ch := make(chan prometheus.Metric) + testCollectorForClients := func(clients []logstash_client.Client) { + collector := NewNodestatsCollector(clients) + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() - go func() { - for range ch { - // simulate reading from the channel - } - }() + ch := make(chan prometheus.Metric) - err := collector.Collect(ctx, ch) - close(ch) + go func() { + for range ch { + // simulate reading from the channel + } + }() - if err == nil { - t.Error("Expected err not to be nil") + err := collector.Collect(ctx, ch) + close(ch) + + if err == nil { + t.Error("Expected err not to be nil") + } } + + t.Run("should return an error if the only client returns an error", func(t *testing.T) { + t.Parallel() + testCollectorForClients([]logstash_client.Client{&errorMockClient{}}) + }) + + t.Run("should return an error if one of the clients returns an error", func(t *testing.T) { + t.Parallel() + testCollectorForClients([]logstash_client.Client{&mockClient{}, &errorMockClient{}}) + }) + + t.Run("should return an error if all clients return an error", func(t *testing.T) { + t.Parallel() + testCollectorForClients([]logstash_client.Client{&errorMockClient{}, &errorMockClient{}}) + }) } From c87d54551c11ad5bde5f1eb75aa6cd7b9d7ac291 Mon Sep 17 00:00:00 2001 From: Jakub Date: Sat, 16 Dec 2023 18:33:34 +0100 Subject: [PATCH 15/36] Add second logstash instance to docker-compose --- docker-compose.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 6cf29ac5..dbf85a0a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,6 @@ version: "3.8" services: - logstash: + logstash: &logstash image: docker.elastic.co/logstash/logstash:8.9.0 restart: unless-stopped volumes: @@ -22,6 +22,15 @@ services: ports: - ${LOGSTASH_PORT:-5044}:5044 - ${LOGSTASH_STATS_PORT:-9600}:9600 + logstash2: + <<: *logstash + ports: + - ${LOGSTASH2_PORT:-5045}:5044 + - ${LOGSTASH2_STATS_PORT:-9601}:9600 + volumes: + - logstash2-data:/usr/share/logstash/data + - ./.docker/logstash.conf:/usr/share/logstash/pipeline/logstash.conf:ro + - ./.docker/logstash.yml:/usr/share/logstash/config/logstash.yml:ro prometheus: image: prom/prometheus:v2.46.0 restart: unless-stopped @@ -101,6 +110,7 @@ services: - ${EXPORTER_PORT:-9198}:9198 volumes: logstash-data: + logstash2-data: prometheus-data: elasticsearch-data: elasticsearch-logs: From c76f289af308cecee85b454145fa635afb5b0d34 Mon Sep 17 00:00:00 2001 From: Jakub Date: Sat, 16 Dec 2023 18:41:59 +0100 Subject: [PATCH 16/36] Improve log message about missing env file --- cmd/exporter/main.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/cmd/exporter/main.go b/cmd/exporter/main.go index 5e678d70..0b763d69 100644 --- a/cmd/exporter/main.go +++ b/cmd/exporter/main.go @@ -25,14 +25,19 @@ func main() { } warn := godotenv.Load() - if warn != nil { - log.Println(warn) - } - logger, err := config.SetupSlog() if err != nil { + if warn != nil { + log.Printf("failed to load .env file: %s", warn) + } + log.Fatalf("failed to setup slog: %s", err) + } else { + if warn != nil { + slog.Warn("failed to load .env file", "err", warn) + } } + slog.SetDefault(logger) exporterConfig, err := config.GetConfig(config.ExporterConfigLocation) From 9a04b6e19d094ac76340206a53b8fba1e0c6aab2 Mon Sep 17 00:00:00 2001 From: Jakub Date: Sat, 16 Dec 2023 18:52:25 +0100 Subject: [PATCH 17/36] Add new config to docker-compose --- .docker/exporter-config.yml | 9 +++++++++ docker-compose.yml | 1 + 2 files changed, 10 insertions(+) create mode 100644 .docker/exporter-config.yml diff --git a/.docker/exporter-config.yml b/.docker/exporter-config.yml new file mode 100644 index 00000000..8bb8ace9 --- /dev/null +++ b/.docker/exporter-config.yml @@ -0,0 +1,9 @@ +logstash: + servers: + - url: "http://logstash:9600" + - url: "http://logstash2:9600" +server: + host: "127.0.0.1" + port: 9200 +logging: + level: "debug" diff --git a/docker-compose.yml b/docker-compose.yml index dbf85a0a..451bb0bd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -106,6 +106,7 @@ services: environment: - LOGSTASH_URL=${LOGSTASH_URL:-http://logstash:9600} - LOG_LEVEL=${LOG_LEVEL:-debug} + - EXPORTER_CONFIG_LOCATION=${EXPORTER_CONFIG_LOCATION:-/app/.docker/exporter-config.yml} ports: - ${EXPORTER_PORT:-9198}:9198 volumes: From 8cf04c7c3f113465e8cddbd6574f3e81de156e5a Mon Sep 17 00:00:00 2001 From: Jakub Date: Sat, 16 Dec 2023 18:53:28 +0100 Subject: [PATCH 18/36] Move setting default slog logger into the right place --- cmd/exporter/main.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cmd/exporter/main.go b/cmd/exporter/main.go index 0b763d69..9f432a46 100644 --- a/cmd/exporter/main.go +++ b/cmd/exporter/main.go @@ -33,13 +33,12 @@ func main() { log.Fatalf("failed to setup slog: %s", err) } else { + slog.SetDefault(logger) if warn != nil { slog.Warn("failed to load .env file", "err", warn) } } - slog.SetDefault(logger) - exporterConfig, err := config.GetConfig(config.ExporterConfigLocation) if err != nil { slog.Error("failed to get exporter config", "err", err) From ac80900df3a3ce29941a35d62c6995e95ce715aa Mon Sep 17 00:00:00 2001 From: Jakub Date: Sat, 16 Dec 2023 18:56:16 +0100 Subject: [PATCH 19/36] Add example config to the repo --- config.example.yml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 config.example.yml diff --git a/config.example.yml b/config.example.yml new file mode 100644 index 00000000..6597e930 --- /dev/null +++ b/config.example.yml @@ -0,0 +1,8 @@ +logstash: + servers: + - url: "http://localhost:9600" +server: + host: "127.0.0.1" + port: 9200 +logging: + level: "info" From 8e06fbd02cda0da3bf490ac1ea976f706e64be11 Mon Sep 17 00:00:00 2001 From: Jakub Date: Sat, 16 Dec 2023 19:13:49 +0100 Subject: [PATCH 20/36] Change default port --- .docker/exporter-config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.docker/exporter-config.yml b/.docker/exporter-config.yml index 8bb8ace9..48083418 100644 --- a/.docker/exporter-config.yml +++ b/.docker/exporter-config.yml @@ -4,6 +4,6 @@ logstash: - url: "http://logstash2:9600" server: host: "127.0.0.1" - port: 9200 + port: 9198 logging: level: "debug" From 2e122edd4574379b7f3f5a7377ebf1334593c1b4 Mon Sep 17 00:00:00 2001 From: Jakub Date: Sat, 16 Dec 2023 19:21:58 +0100 Subject: [PATCH 21/36] Change default listen string for docker to be any ip v4 --- .docker/exporter-config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.docker/exporter-config.yml b/.docker/exporter-config.yml index 48083418..42429cf7 100644 --- a/.docker/exporter-config.yml +++ b/.docker/exporter-config.yml @@ -3,7 +3,7 @@ logstash: - url: "http://logstash:9600" - url: "http://logstash2:9600" server: - host: "127.0.0.1" + host: "0.0.0.0" port: 9198 logging: level: "debug" From 90a9dc2ab5b94b8aa4a11f864f216b9a90f9ac93 Mon Sep 17 00:00:00 2001 From: Jakub Date: Sat, 16 Dec 2023 19:37:30 +0100 Subject: [PATCH 22/36] Add labels to logstash clients --- cmd/exporter/main.go | 2 +- collectors/collector_manager.go | 8 ++-- collectors/collector_manager_test.go | 13 ++++-- .../nodeinfo/nodeinfo_collector_test.go | 8 ++++ .../nodestats/nodestats_collector_test.go | 8 ++++ config/exporter_config.go | 17 +++---- config/exporter_config_test.go | 44 ++++++------------- fetcher/logstash_client/client.go | 13 +++++- fetcher/logstash_client/client_test.go | 33 +++++++++++++- fetcher/logstash_client/queries_test.go | 4 +- server/healthcheck.go | 9 ++++ server/server.go | 4 +- server/server_test.go | 4 +- 13 files changed, 108 insertions(+), 59 deletions(-) diff --git a/cmd/exporter/main.go b/cmd/exporter/main.go index 9f432a46..f26d2527 100644 --- a/cmd/exporter/main.go +++ b/cmd/exporter/main.go @@ -52,7 +52,7 @@ func main() { versionInfo := config.GetVersionInfo() slog.Info(versionInfo.String()) - collectorManager := collectors.NewCollectorManager(exporterConfig.GetLogstashUrls()) + collectorManager := collectors.NewCollectorManager(exporterConfig.Logstash.Servers) prometheus.MustRegister(collectorManager) appServer := server.NewAppServer(host, port, exporterConfig) diff --git a/collectors/collector_manager.go b/collectors/collector_manager.go index fdd1ff05..e8ca5ca6 100644 --- a/collectors/collector_manager.go +++ b/collectors/collector_manager.go @@ -24,18 +24,18 @@ type CollectorManager struct { scrapeDurations *prometheus.SummaryVec } -func getClientsForEndpoints(endpoints []string) []logstashclient.Client { +func getClientsForEndpoints(endpoints []*config.LogstashServer) []logstashclient.Client { clients := make([]logstashclient.Client, len(endpoints)) for i, endpoint := range endpoints { - clients[i] = logstashclient.NewClient(endpoint) + clients[i] = logstashclient.NewClient(endpoint.Host, endpoint.Labels) } return clients } -func NewCollectorManager(endpoints []string) *CollectorManager { - clients := getClientsForEndpoints(endpoints) +func NewCollectorManager(servers []*config.LogstashServer) *CollectorManager { + clients := getClientsForEndpoints(servers) collectors := getCollectors(clients) diff --git a/collectors/collector_manager_test.go b/collectors/collector_manager_test.go index 8834c5a9..0809ad35 100644 --- a/collectors/collector_manager_test.go +++ b/collectors/collector_manager_test.go @@ -6,6 +6,7 @@ import ( "sync" "testing" + "github.com/kuskoman/logstash-exporter/config" "github.com/prometheus/client_golang/prometheus" ) @@ -13,10 +14,16 @@ func TestNewCollectorManager(t *testing.T) { t.Parallel() t.Run("multiple endpoints", func(t *testing.T) { - mockEndpoints := []string{ - "http://localhost:9600", - "http://localhost:9601", + endpoint1 := &config.LogstashServer{ + Host: "http://localhost:9600", + Labels: map[string]string{"foo": "bar"}, } + + endpoint2 := &config.LogstashServer{ + Host: "http://localhost:9601", + } + + mockEndpoints := []*config.LogstashServer{endpoint1, endpoint2} cm := NewCollectorManager(mockEndpoints) if cm == nil { diff --git a/collectors/nodeinfo/nodeinfo_collector_test.go b/collectors/nodeinfo/nodeinfo_collector_test.go index e6f0d8c7..d864181c 100644 --- a/collectors/nodeinfo/nodeinfo_collector_test.go +++ b/collectors/nodeinfo/nodeinfo_collector_test.go @@ -40,6 +40,10 @@ func (m *mockClient) GetEndpoint() string { return "" } +func (m *mockClient) GetLabels() map[string]string { + return nil +} + type errorMockClient struct{} func (m *errorMockClient) GetNodeInfo(ctx context.Context) (*responses.NodeInfoResponse, error) { @@ -54,6 +58,10 @@ func (m *errorMockClient) GetEndpoint() string { return "" } +func (m *errorMockClient) GetLabels() map[string]string { + return nil +} + func TestCollectNotNil(t *testing.T) { runTest := func(t *testing.T, clients []logstashclient.Client) { collector := NewNodeinfoCollector(clients) diff --git a/collectors/nodestats/nodestats_collector_test.go b/collectors/nodestats/nodestats_collector_test.go index f54e396b..69524151 100644 --- a/collectors/nodestats/nodestats_collector_test.go +++ b/collectors/nodestats/nodestats_collector_test.go @@ -40,6 +40,10 @@ func (m *mockClient) GetEndpoint() string { return "" } +func (m *mockClient) GetLabels() map[string]string { + return nil +} + type errorMockClient struct{} func (m *errorMockClient) GetNodeInfo(ctx context.Context) (*responses.NodeInfoResponse, error) { @@ -54,6 +58,10 @@ func (m *errorMockClient) GetEndpoint() string { return "" } +func (m *errorMockClient) GetLabels() map[string]string { + return nil +} + func TestCollectNotNil(t *testing.T) { t.Parallel() diff --git a/config/exporter_config.go b/config/exporter_config.go index 3b9b0053..08f34a1d 100644 --- a/config/exporter_config.go +++ b/config/exporter_config.go @@ -20,12 +20,13 @@ var ( // LogstashServer represents individual Logstash server configuration type LogstashServer struct { - URL string `yaml:"url"` + Host string `yaml:"url"` + Labels map[string]string `yaml:"labels"` } // LogstashConfig holds the configuration for all Logstash servers type LogstashConfig struct { - Servers []LogstashServer `yaml:"servers"` + Servers []*LogstashServer `yaml:"servers"` } // ServerConfig represents the server configuration @@ -87,8 +88,8 @@ func mergeWithDefault(config *Config) *Config { if len(config.Logstash.Servers) == 0 { slog.Debug("using default logstash server", "url", defaultLogstashURL) - config.Logstash.Servers = append(config.Logstash.Servers, LogstashServer{ - URL: defaultLogstashURL, + config.Logstash.Servers = append(config.Logstash.Servers, &LogstashServer{ + Host: defaultLogstashURL, }) } @@ -105,11 +106,3 @@ func GetConfig(location string) (*Config, error) { mergedConfig := mergeWithDefault(config) return mergedConfig, nil } - -func (cfg *Config) GetLogstashUrls() []string { - urls := make([]string, len(cfg.Logstash.Servers)) - for i, server := range cfg.Logstash.Servers { - urls[i] = server.URL - } - return urls -} diff --git a/config/exporter_config_test.go b/config/exporter_config_test.go index 3632b43b..a230f438 100644 --- a/config/exporter_config_test.go +++ b/config/exporter_config_test.go @@ -1,7 +1,6 @@ package config import ( - "reflect" "testing" ) @@ -19,8 +18,8 @@ func TestLoadConfig(t *testing.T) { if config == nil { t.Fatal("expected config to be non-nil") } - if config.Logstash.Servers[0].URL != "http://localhost:9601" { - t.Errorf("expected URL to be %v, got %v", "http://localhost:9601", config.Logstash.Servers[0].URL) + if config.Logstash.Servers[0].Host != "http://localhost:9601" { + t.Errorf("expected URL to be %v, got %v", "http://localhost:9601", config.Logstash.Servers[0].Host) } }) @@ -69,8 +68,8 @@ func TestMergeWithDefault(t *testing.T) { if mergedConfig.Logging.Level != defaultLogLevel { t.Errorf("expected level to be %v, got %v", defaultLogLevel, mergedConfig.Logging.Level) } - if mergedConfig.Logstash.Servers[0].URL != defaultLogstashURL { - t.Errorf("expected URL to be %v, got %v", defaultLogstashURL, mergedConfig.Logstash.Servers[0].URL) + if mergedConfig.Logstash.Servers[0].Host != defaultLogstashURL { + t.Errorf("expected URL to be %v, got %v", defaultLogstashURL, mergedConfig.Logstash.Servers[0].Host) } }) @@ -85,8 +84,8 @@ func TestMergeWithDefault(t *testing.T) { if mergedConfig.Logging.Level != defaultLogLevel { t.Errorf("expected level to be %v, got %v", defaultLogLevel, mergedConfig.Logging.Level) } - if mergedConfig.Logstash.Servers[0].URL != defaultLogstashURL { - t.Errorf("expected URL to be %v, got %v", defaultLogstashURL, mergedConfig.Logstash.Servers[0].URL) + if mergedConfig.Logstash.Servers[0].Host != defaultLogstashURL { + t.Errorf("expected URL to be %v, got %v", defaultLogstashURL, mergedConfig.Logstash.Servers[0].Host) } }) @@ -101,9 +100,9 @@ func TestMergeWithDefault(t *testing.T) { Level: "debug", }, Logstash: LogstashConfig{ - Servers: []LogstashServer{ - {URL: "http://localhost:9601"}, - {URL: "http://localhost:9602"}, + Servers: []*LogstashServer{ + {Host: "http://localhost:9601"}, + {Host: "http://localhost:9602"}, }, }, } @@ -118,12 +117,12 @@ func TestMergeWithDefault(t *testing.T) { t.Errorf("expected level to be %v, got %v", "debug", mergedConfig.Logging.Level) } - if mergedConfig.Logstash.Servers[0].URL != "http://localhost:9601" { - t.Errorf("expected URL to be %v, got %v", "http://localhost:9601", mergedConfig.Logstash.Servers[0].URL) + if mergedConfig.Logstash.Servers[0].Host != "http://localhost:9601" { + t.Errorf("expected URL to be %v, got %v", "http://localhost:9601", mergedConfig.Logstash.Servers[0].Host) } - if mergedConfig.Logstash.Servers[1].URL != "http://localhost:9602" { - t.Errorf("expected URL to be %v, got %v", "http://localhost:9602", mergedConfig.Logstash.Servers[1].URL) + if mergedConfig.Logstash.Servers[1].Host != "http://localhost:9602" { + t.Errorf("expected URL to be %v, got %v", "http://localhost:9602", mergedConfig.Logstash.Servers[1].Host) } }) } @@ -154,20 +153,3 @@ func TestGetConfig(t *testing.T) { } }) } - -func TestGetLogstashUrls(t *testing.T) { - t.Run("gets Logstash URLs correctly", func(t *testing.T) { - config := &Config{ - Logstash: LogstashConfig{ - Servers: []LogstashServer{{URL: "http://localhost:9601"}}, - }, - } - - urls := config.GetLogstashUrls() - expectedUrls := []string{"http://localhost:9601"} - - if !reflect.DeepEqual(urls, expectedUrls) { - t.Errorf("expected urls to be %v, got %v", expectedUrls, urls) - } - }) -} diff --git a/fetcher/logstash_client/client.go b/fetcher/logstash_client/client.go index b1b0658e..83601fd5 100644 --- a/fetcher/logstash_client/client.go +++ b/fetcher/logstash_client/client.go @@ -14,29 +14,40 @@ type Client interface { GetNodeStats(ctx context.Context) (*responses.NodeStatsResponse, error) GetEndpoint() string + GetLabels() map[string]string } // DefaultClient is the default implementation of the Client interface type DefaultClient struct { httpClient *http.Client endpoint string + labels map[string]string } func (client *DefaultClient) GetEndpoint() string { return client.endpoint } +func (client *DefaultClient) GetLabels() map[string]string { + return client.labels +} + const defaultLogstashEndpoint = "http://localhost:9600" // NewClient returns a new instance of the DefaultClient configured with the given endpoint -func NewClient(endpoint string) Client { +func NewClient(endpoint string, labels map[string]string) Client { if endpoint == "" { endpoint = defaultLogstashEndpoint } + if labels == nil { + labels = make(map[string]string) + } + return &DefaultClient{ httpClient: &http.Client{}, endpoint: endpoint, + labels: labels, } } diff --git a/fetcher/logstash_client/client_test.go b/fetcher/logstash_client/client_test.go index 7ec3608d..38bcbb16 100644 --- a/fetcher/logstash_client/client_test.go +++ b/fetcher/logstash_client/client_test.go @@ -15,8 +15,12 @@ type TestResponse struct { } func TestNewClient(t *testing.T) { + t.Parallel() + t.Run("should return a new client for the default endpoint", func(t *testing.T) { - client := NewClient("") + t.Parallel() + + client := NewClient("", nil) if client.(*DefaultClient).endpoint != defaultLogstashEndpoint { t.Errorf("expected endpoint to be %s, got %s", defaultLogstashEndpoint, client.(*DefaultClient).endpoint) @@ -24,14 +28,39 @@ func TestNewClient(t *testing.T) { }) t.Run("should return a new client for the given endpoint", func(t *testing.T) { + t.Parallel() + expectedEndpoint := "http://localhost:9601" - client := NewClient(expectedEndpoint) + client := NewClient(expectedEndpoint, nil) receivedEndpoint := client.GetEndpoint() if receivedEndpoint != expectedEndpoint { t.Errorf("expected endpoint to be %s, got %s", expectedEndpoint, receivedEndpoint) } }) + + t.Run("should return a new client with the given labels", func(t *testing.T) { + t.Parallel() + + expectedLabels := map[string]string{"foo": "bar"} + client := NewClient("", expectedLabels) + + receivedLabels := client.GetLabels() + if receivedLabels["foo"] != expectedLabels["foo"] { + t.Errorf("expected label to be %s, got %s", expectedLabels["foo"], receivedLabels["foo"]) + } + }) + + t.Run("should return a new client with an empty map if no labels are given", func(t *testing.T) { + t.Parallel() + + client := NewClient("", nil) + + receivedLabels := client.GetLabels() + if len(receivedLabels) != 0 { + t.Errorf("expected labels to be empty, got %v", receivedLabels) + } + }) } func TestGetMetrics(t *testing.T) { diff --git a/fetcher/logstash_client/queries_test.go b/fetcher/logstash_client/queries_test.go index faa6701b..b69ff4ff 100644 --- a/fetcher/logstash_client/queries_test.go +++ b/fetcher/logstash_client/queries_test.go @@ -22,7 +22,7 @@ func TestGetNodeInfo(t *testing.T) { })) defer ts.Close() - client := NewClient(ts.URL) + client := NewClient(ts.URL, nil) response, err := client.GetNodeInfo(context.Background()) if err != nil { @@ -49,7 +49,7 @@ func TestGetNodeStats(t *testing.T) { })) defer ts.Close() - client := NewClient(ts.URL) + client := NewClient(ts.URL, nil) response, err := client.GetNodeStats(context.Background()) if err != nil { diff --git a/server/healthcheck.go b/server/healthcheck.go index 8276f836..d93d9461 100644 --- a/server/healthcheck.go +++ b/server/healthcheck.go @@ -9,6 +9,15 @@ import ( "github.com/kuskoman/logstash-exporter/config" ) +func convertServersToUrls(servers []*config.LogstashServer) []string { + urls := make([]string, len(servers)) + for i, server := range servers { + urls[i] = server.Host + } + + return urls +} + func getHealthCheck(logstashUrls []string) func(http.ResponseWriter, *http.Request) { client := &http.Client{} diff --git a/server/server.go b/server/server.go index 18c655fb..b8ac2a45 100644 --- a/server/server.go +++ b/server/server.go @@ -18,7 +18,9 @@ func NewAppServer(host, port string, cfg *config.Config) *http.Server { mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/metrics", http.StatusMovedPermanently) }) - mux.HandleFunc("/healthcheck", getHealthCheck(cfg.GetLogstashUrls())) + + logstashUrls := convertServersToUrls(cfg.Logstash.Servers) + mux.HandleFunc("/healthcheck", getHealthCheck(logstashUrls)) mux.HandleFunc("/version", getVersionInfoHandler(config.GetVersionInfo())) listenUrl := fmt.Sprintf("%s:%s", host, port) diff --git a/server/server_test.go b/server/server_test.go index 036b10d9..aac22d7e 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -44,8 +44,8 @@ func TestNewAppServer(t *testing.T) { t.Run("test handling of /healthcheck endpoint", func(t *testing.T) { cfg := &config.Config{ Logstash: config.LogstashConfig{ - Servers: []config.LogstashServer{ - {URL: "http://localhost:1234"}, + Servers: []*config.LogstashServer{ + {Host: "http://localhost:1234"}, }, }, } From 74a3624f674e277632e9d0a9ede514bfac851480 Mon Sep 17 00:00:00 2001 From: Jakub Date: Tue, 26 Dec 2023 18:41:25 +0100 Subject: [PATCH 23/36] Use newer version of *-artifact workflows --- .github/workflows/go-application.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/go-application.yml b/.github/workflows/go-application.yml index 169e88cb..09dec8c3 100644 --- a/.github/workflows/go-application.yml +++ b/.github/workflows/go-application.yml @@ -157,7 +157,7 @@ jobs: VERSION: ${{ github.ref }} - name: Upload Linux binary - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: logstash-exporter-linux path: out/main-linux @@ -182,7 +182,7 @@ jobs: VERSION: ${{ github.ref }} - name: Upload Mac binary - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: logstash-exporter-darwin path: out/main-darwin @@ -207,7 +207,7 @@ jobs: VERSION: ${{ github.ref }} - name: Upload Windows binary - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: logstash-exporter-windows path: out/main-windows @@ -227,7 +227,7 @@ jobs: run: docker save logstash-exporter:latest | gzip > logstash-exporter.tar.gz - name: Upload Docker image as an artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: logstash-exporter-docker-image path: logstash-exporter.tar.gz @@ -297,7 +297,7 @@ jobs: uses: actions/checkout@v4 - name: Download Docker image - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: logstash-exporter-docker-image path: .helm/files @@ -306,7 +306,7 @@ jobs: run: docker load -i .helm/files/logstash-exporter.tar.gz - name: Install Helm - uses: azure/setup-helm@v3 + uses: azure/setup-helm@v4 with: version: 3.11.2 @@ -379,7 +379,7 @@ jobs: if: startsWith(github.ref, 'refs/tags/') steps: - name: Download binary - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: logstash-exporter-${{ matrix.binary }} - name: Generate sha256 checksum From f4cb4b9e1c7a457d7a8c8a8e2297566b2a9b72a2 Mon Sep 17 00:00:00 2001 From: Jakub Date: Tue, 26 Dec 2023 19:11:43 +0100 Subject: [PATCH 24/36] Update Helm chart to match v2 configuration --- chart/README.md | 31 +++++++++++++++++--- chart/schema.json | 51 +++++++++++++++++++++++++++++++-- chart/templates/configmap.yaml | 22 ++++++++++++++ chart/templates/deployment.yaml | 4 --- chart/values.yaml | 35 ++++++++++++++++++++-- 5 files changed, 129 insertions(+), 14 deletions(-) create mode 100644 chart/templates/configmap.yaml diff --git a/chart/README.md b/chart/README.md index f9195f43..dc7277cc 100644 --- a/chart/README.md +++ b/chart/README.md @@ -2,11 +2,34 @@ ## Parameters -### Logstash configuration +### Logstash configuration. Discarded if customConfig is enabled -| Name | Description | Value | -| -------------- | --------------------- | ---------------------- | -| `logstash.url` | Logstash instance URL | `http://logstash:9600` | +| Name | Description | Value | +| --------------- | --------------------------------------------------------------------------------------- | -------------------------- | +| `logstash.urls` | Logstash instance URL. Multiple URLs can be specified. Defaults to http://logstash:9600 | `["http://logstash:9600"]` | + +### logstash.server Logstash exporter server configuration + +| Name | Description | Value | +| ---------------------- | ------------------------------------- | --------- | +| `logstash.server.host` | Host for the logstash exporter server | `0.0.0.0` | +| `logstash.server.port` | Port for the logstash exporter server | `9198` | + +### logstash.logging Logstash exporter logging configuration + +| Name | Description | Value | +| ------------------------ | ------------------------------- | ------ | +| `logstash.logging.level` | Logstash exporter logging level | `info` | + +### Custom logstash-exporter configuration. Overrides the default .logstash section + +| Name | Description | Value | +| ---------------------- | --------------------------- | ------------------------------------------------- | +| `customConfig.enabled` | Enable custom configuration | `false` | +| `customConfig.config` | Custom configuration | `logstash: + urls: + - "http://logstash:9600" +` | ### Image settings diff --git a/chart/schema.json b/chart/schema.json index 8cde27ba..e07d1179 100644 --- a/chart/schema.json +++ b/chart/schema.json @@ -5,10 +5,55 @@ "logstash": { "type": "object", "properties": { - "url": { + "urls": { + "type": "array", + "description": "Logstash instance URL. Multiple URLs can be specified. Defaults to http://logstash:9600", + "default": [ + "http://logstash:9600" + ], + "items": { + "type": "string" + } + }, + "server": { + "type": "object", + "properties": { + "host": { + "type": "string", + "description": "Host for the logstash exporter server", + "default": "0.0.0.0" + }, + "port": { + "type": "number", + "description": "Port for the logstash exporter server", + "default": 9198 + } + } + }, + "logging": { + "type": "object", + "properties": { + "level": { + "type": "string", + "description": "Logstash exporter logging level", + "default": "info" + } + } + } + } + }, + "customConfig": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable custom configuration", + "default": false + }, + "config": { "type": "string", - "description": "Logstash instance URL", - "default": "http://logstash:9600" + "description": "Custom configuration", + "default": "logstash:\n urls:\n - \"http://logstash:9600\"\n" } } }, diff --git a/chart/templates/configmap.yaml b/chart/templates/configmap.yaml new file mode 100644 index 00000000..8e9a8010 --- /dev/null +++ b/chart/templates/configmap.yaml @@ -0,0 +1,22 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "logstash-exporter.name" . }} + labels: + app: {{ include "logstash-exporter.name" . }} + release: {{ .Release.Name }} +data: | + {{- if .Values.customConfig.enabled }} + {{- .Values.customConfig.config | nindent 4 }} + {{- else }} + logstash: + servers: + {{ range .Values.logstash.urls -}} + - url: {{ . | quote }} + {{- end }} + server: + host: {{ .Values.logstash.server.host | quote }} + port: {{ .Values.logstash.server.port }} + logging: + level: {{ .Values.logstash.logging.level | quote }} + {{- end -}} diff --git a/chart/templates/deployment.yaml b/chart/templates/deployment.yaml index 3efece3e..ddfb07e8 100644 --- a/chart/templates/deployment.yaml +++ b/chart/templates/deployment.yaml @@ -85,10 +85,6 @@ spec: containers: - name: exporter env: - - name: LOGSTASH_URL - value: {{ required "logstash.url is required" .Values.logstash.url | quote }} - - name: PORT - value: {{ required "service.port is required" .Values.service.port | quote }} {{- range $key, $value := .Values.deployment.env }} - name: {{ $key | quote }} value: {{ $value | quote }} diff --git a/chart/values.yaml b/chart/values.yaml index f36c0a6a..989fb620 100644 --- a/chart/values.yaml +++ b/chart/values.yaml @@ -1,9 +1,38 @@ -## @section Logstash configuration +## @section Logstash configuration. Discarded if customConfig is enabled ## logstash: - ## @param logstash.url Logstash instance URL + ## @param logstash.urls Logstash instance URL. Multiple URLs can be specified. Defaults to http://logstash:9600 ## - url: "http://logstash:9600" + urls: + - "http://logstash:9600" + ## @section logstash.server Logstash exporter server configuration + ## + server: + ## @param logstash.server.host Host for the logstash exporter server + ## + host: "0.0.0.0" + ## @param logstash.server.port Port for the logstash exporter server + ## + port: 9198 + ## @section logstash.logging Logstash exporter logging configuration + ## + logging: + ## @param logstash.logging.level Logstash exporter logging level + ## + level: "info" + +## @section Custom logstash-exporter configuration. Overrides the default .logstash section +## +customConfig: + ## @param customConfig.enabled Enable custom configuration + ## + enabled: false + ## @param customConfig.config Custom configuration + ## + config: | + logstash: + urls: + - "http://logstash:9600" ## @section Image settings ## From f2c9c1fce099d20620fd37040ab92ed92e3e8c03 Mon Sep 17 00:00:00 2001 From: Jakub Date: Tue, 26 Dec 2023 19:15:24 +0100 Subject: [PATCH 25/36] Fix version of setup-helm job --- .github/workflows/go-application.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/go-application.yml b/.github/workflows/go-application.yml index 09dec8c3..24431f8c 100644 --- a/.github/workflows/go-application.yml +++ b/.github/workflows/go-application.yml @@ -306,7 +306,7 @@ jobs: run: docker load -i .helm/files/logstash-exporter.tar.gz - name: Install Helm - uses: azure/setup-helm@v4 + uses: azure/setup-helm@v3 with: version: 3.11.2 From 77fb977a7196e5e68aeef569f46dd7f41aac798e Mon Sep 17 00:00:00 2001 From: Jakub Date: Tue, 26 Dec 2023 19:16:09 +0100 Subject: [PATCH 26/36] Update Helm version --- .github/workflows/go-application.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/go-application.yml b/.github/workflows/go-application.yml index 24431f8c..b2ef469d 100644 --- a/.github/workflows/go-application.yml +++ b/.github/workflows/go-application.yml @@ -308,7 +308,7 @@ jobs: - name: Install Helm uses: azure/setup-helm@v3 with: - version: 3.11.2 + version: 3.13.3 - name: Include current chart versions working-directory: .helm/logstash-integration-test-chart From 0f3d5b042a74aeaa8de9a758bf49af325c39c471 Mon Sep 17 00:00:00 2001 From: Jakub Date: Tue, 26 Dec 2023 19:22:15 +0100 Subject: [PATCH 27/36] Remove two of todos in code: test and one obsolete one --- collectors/collector_manager.go | 2 +- collectors/nodestats/nodestats_collector_test.go | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/collectors/collector_manager.go b/collectors/collector_manager.go index e8ca5ca6..056b76a0 100644 --- a/collectors/collector_manager.go +++ b/collectors/collector_manager.go @@ -48,7 +48,7 @@ func NewCollectorManager(servers []*config.LogstashServer) *CollectorManager { func getCollectors(clients []logstashclient.Client) map[string]Collector { collectors := make(map[string]Collector) collectors["nodeinfo"] = nodeinfo.NewNodeinfoCollector(clients) - collectors["nodestats"] = nodestats.NewNodestatsCollector(clients) // TODO: support multiple clients + collectors["nodestats"] = nodestats.NewNodestatsCollector(clients) return collectors } diff --git a/collectors/nodestats/nodestats_collector_test.go b/collectors/nodestats/nodestats_collector_test.go index 69524151..7c6316e7 100644 --- a/collectors/nodestats/nodestats_collector_test.go +++ b/collectors/nodestats/nodestats_collector_test.go @@ -138,8 +138,18 @@ func TestCollectNotNil(t *testing.T) { t.Errorf("failed to extract fqName from metric %s", foundMetricDesc) } - // todo: check if exists - foundMetrics = append(foundMetrics, foundMetricFqName) + // todo: optimize this + found := false + for _, foundMetric := range foundMetrics { + if foundMetric == foundMetricFqName { + found = true + break + } + } + + if !found { + foundMetrics = append(foundMetrics, foundMetricFqName) + } } for _, expectedMetric := range expectedBaseMetrics { From dde4641c462508c5c971aef26c4a95aab39ad76f Mon Sep 17 00:00:00 2001 From: Jakub Date: Tue, 26 Dec 2023 19:25:57 +0100 Subject: [PATCH 28/36] Add missing config volume to chart --- chart/templates/configmap.yaml | 1 + chart/templates/deployment.yaml | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/chart/templates/configmap.yaml b/chart/templates/configmap.yaml index 8e9a8010..323ad23a 100644 --- a/chart/templates/configmap.yaml +++ b/chart/templates/configmap.yaml @@ -6,6 +6,7 @@ metadata: app: {{ include "logstash-exporter.name" . }} release: {{ .Release.Name }} data: | + config.yml: |- {{- if .Values.customConfig.enabled }} {{- .Values.customConfig.config | nindent 4 }} {{- else }} diff --git a/chart/templates/deployment.yaml b/chart/templates/deployment.yaml index ddfb07e8..d4d37a85 100644 --- a/chart/templates/deployment.yaml +++ b/chart/templates/deployment.yaml @@ -119,4 +119,11 @@ spec: ports: - name: http containerPort: {{ required "service.port is required" .Values.service.port }} - + volumeMounts: + - name: config-volume + mountPath: /app/config.yml + subPath: config.yml + volumes: + - name: config-volume + configMap: + name: {{ include "logstash-exporter.name" . }} From 851c1f8cfb0d3dbb9e28ecf57d24636ac91eff49 Mon Sep 17 00:00:00 2001 From: Jakub Date: Tue, 26 Dec 2023 19:29:28 +0100 Subject: [PATCH 29/36] Fix configmap keys once more --- chart/templates/configmap.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chart/templates/configmap.yaml b/chart/templates/configmap.yaml index 323ad23a..d720ee5a 100644 --- a/chart/templates/configmap.yaml +++ b/chart/templates/configmap.yaml @@ -5,7 +5,7 @@ metadata: labels: app: {{ include "logstash-exporter.name" . }} release: {{ .Release.Name }} -data: | +data: config.yml: |- {{- if .Values.customConfig.enabled }} {{- .Values.customConfig.config | nindent 4 }} From c9172151cfebbbc244ac76a30d60cecb53eeef87 Mon Sep 17 00:00:00 2001 From: Jakub Date: Tue, 26 Dec 2023 19:38:13 +0100 Subject: [PATCH 30/36] Add script to migrate v1 config to v2 --- Makefile | 4 ++++ scripts/migrate_v1_to_v2.sh | 38 +++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100755 scripts/migrate_v1_to_v2.sh diff --git a/Makefile b/Makefile index 14b3ad5d..567a762e 100644 --- a/Makefile +++ b/Makefile @@ -110,6 +110,10 @@ clean-elasticsearch: upgrade-dependencies: go get -u ./... +#: Migrates configuration from v1 to v2 +migrate-v1-to-v2: + ./scripts/migrate_v1_to_v2.sh + #: Shows info about available commands help: @grep -B1 -E "^[a-zA-Z0-9_-]+\:([^\=]|$$)" Makefile \ diff --git a/scripts/migrate_v1_to_v2.sh b/scripts/migrate_v1_to_v2.sh new file mode 100755 index 00000000..6a06146e --- /dev/null +++ b/scripts/migrate_v1_to_v2.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +default_env_file=".env" + +env_file="${1:-$default_env_file}" + +if [ -f "$env_file" ]; then + export $(cat "$env_file" | xargs) +else + echo "Warning: .env file not found at $env_file" >&2 +fi + +get_env_with_default() { + local var_name=$1 + local default_value=$2 + local value=$(printenv "$var_name") + if [ -z "$value" ]; then + echo "$default_value" + else + echo "$value" + fi +} + +logstash_url=$(get_env_with_default "LOGSTASH_URL" "http://localhost:9600") +port=$(get_env_with_default "PORT" "9198") +host=$(get_env_with_default "HOST" "") +log_level=$(get_env_with_default "LOG_LEVEL" "info") + +cat << EOF +logstash: + servers: + - url: "$logstash_url" +server: + host: "${host:-0.0.0.0}" + port: $port +logging: + level: "$log_level" +EOF From 8b83f81f5539c79cdef39e05e4e32d1d3b3d9fda Mon Sep 17 00:00:00 2001 From: Jakub Date: Tue, 26 Dec 2023 19:44:57 +0100 Subject: [PATCH 31/36] Add migration guide to README --- README.md | 137 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 84 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index ba96c0fb..bdff9e9a 100644 --- a/README.md +++ b/README.md @@ -87,17 +87,48 @@ The Helm chart has its own [README](./chart/README.md). ### Configuration -The application can be configured using the following environment variables, which are also loaded from `.env` file: - -| Variable Name | Description | Default Value | -| -------------- | ----------------------------------------------------------- | ----------------------- | -| `LOGSTASH_URL` | URL to Logstash API | `http://localhost:9600` | -| `PORT` | Port on which the application will be exposed | `9198` | -| `HOST` | Host on which the application will be exposed | empty string | -| `LOG_LEVEL` | [Log level](https://pkg.go.dev/golang.org/x/exp/slog#Level) | empty (defaults to info) | +The application is now configured using a YAML file instead of environment variables. An example configuration is as follows: + +```yaml +logstash: + servers: + - url: "http://logstash:9600" # URL to Logstash API + - url: "http://logstash2:9600" +server: + host: "0.0.0.0" # Host on which the application will be exposed (default: all interfaces) + port: 9198 # Port on which the application will be exposed +logging: + level: "debug" # Log level (debug, info, warn, error) +``` All configuration variables can be checked in the [config directory](./config/). +Previously the application was configured using environment variables. The old configuration is no longer supported, +however a [migration script](./scripts/migrate_env_to_yaml.sh) is provided to migrate the old configuration to the new one. +See more in the [Migration](#migration) section. + +## Migration + +### From v1 to v2 + +#### With Migration Script + +1. Backup Configuration: Save your existing .env file. +2. Run Migration Script: Execute ./scripts/migrate_v1_to_v2.sh [path/to/.env]. If your .env is in the default location, + simply run ./scripts/migrate_v1_to_v2.sh. + You can use it via make: `make migrate-v1-to-v2`. +3. Save New Configuration: Redirect output to new_config.yaml using ./scripts/migrate_v1_to_v2.sh.sh > config.yaml. +4. Set `EXPORTER_CONFIG_LOCATION`: Update this environment variable to the path of config.yaml (or leave the default). +5. Test Application: Ensure the application runs correctly with the new configuration. + +#### Without Migration Script + +1. Backup Configuration: Keep a record of your current environment variables. +2. Create YAML File: Manually create a config.yaml file following the new format. +3. Transfer Settings: Copy values from your .env file or noted environment variables into the corresponding sections in new_config.yaml. +4. Set EXPORTER_CONFIG_LOCATION: Point this environment variable to new_config.yaml. +5. Test Application: Check if the application functions properly with the new configuration. + ## Building ### Makefile @@ -105,7 +136,7 @@ All configuration variables can be checked in the [config directory](./config/). #### Available Commands - + - `make all`: Builds binary executables for all OS (Win, Darwin, Linux). - `make run`: Runs the Go Exporter application. - `make build-linux`: Builds a binary executable for Linux. @@ -127,7 +158,7 @@ All configuration variables can be checked in the [config directory](./config/). - `make helm-readme`: Generates Helm chart README.md file. - `make clean-elasticsearch`: Cleans Elasticsearch data, works only with default ES port. The command may take a very long time to complete. - `make help`: Shows info about available commands. - + #### File Structure @@ -138,91 +169,91 @@ The binary executables are saved in the out directory. #### Example Usage - + Builds binary executables for all OS (Win, Darwin, Linux): - + make all - + Runs the Go Exporter application: - + make run - + Builds a binary executable for Linux: - + make build-linux - + Builds a binary executable for Darwin: - + make build-darwin - + Builds a binary executable for Windows: - + make build-windows - + Builds a Docker image for the Go Exporter application: - + make build-docker - + Builds a multi-arch Docker image (`amd64` and `arm64`): - + make build-docker-multi - + Deletes all binary executables in the out directory: - + make clean - + Runs all tests: - + make test - + Displays test coverage report: - + make test-coverage - + Starts a Docker-compose configuration: - + make compose - + Starts a Docker-compose configuration until it's ready: - + make wait-for-compose - + Stops a Docker-compose configuration: - + make compose-down - + Verifies the metrics from the Go Exporter application: - + make verify-metrics - + Pulls the Docker image from the registry: - + make pull - + Shows logs from the Docker-compose configuration: - + make logs - + Minifies the binary executables: - + make minify - + Installs readme-generator-for-helm tool: - + make install-helm-readme - + Generates Helm chart README.md file: - + make helm-readme - + Cleans Elasticsearch data, works only with default ES port. The command may take a very long time to complete: - + make clean-elasticsearch - + Shows info about available commands: - + make help - + ## Helper Scripts From 4c486728747ad4c55e9acb679161e7932e08bdf1 Mon Sep 17 00:00:00 2001 From: Jakub Date: Tue, 26 Dec 2023 20:27:23 +0100 Subject: [PATCH 32/36] Update test chart dependencies --- .helm/logstash-integration-test-chart/Chart.lock | 10 +++++----- .helm/logstash-integration-test-chart/Chart.yaml | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.helm/logstash-integration-test-chart/Chart.lock b/.helm/logstash-integration-test-chart/Chart.lock index cee219bf..091843e1 100644 --- a/.helm/logstash-integration-test-chart/Chart.lock +++ b/.helm/logstash-integration-test-chart/Chart.lock @@ -4,12 +4,12 @@ dependencies: version: 8.5.1 - name: apache repository: https://charts.bitnami.com/bitnami - version: 9.2.23 + version: 10.2.4 - name: prometheus repository: https://prometheus-community.github.io/helm-charts - version: 20.2.0 + version: 25.8.2 - name: logstash-exporter repository: file://../../chart/ - version: v1.0.2 -digest: sha256:1733217c222c212aac4b58826c147d7acb2f61fe01ce6985b139050803915d92 -generated: "2023-04-12T10:38:00.905306965+02:00" + version: 1.0.2 +digest: sha256:7d4c4f09f7947734ed527834d32065bebdb655fddd854fe9ba0481d3d9939e7f +generated: "2023-12-26T20:27:15.623329+01:00" diff --git a/.helm/logstash-integration-test-chart/Chart.yaml b/.helm/logstash-integration-test-chart/Chart.yaml index ca757646..4f86c7b2 100644 --- a/.helm/logstash-integration-test-chart/Chart.yaml +++ b/.helm/logstash-integration-test-chart/Chart.yaml @@ -5,16 +5,16 @@ type: application version: 0.1.0 dependencies: - name: logstash - version: "8.5.1" + version: "^8.5.1" repository: https://helm.elastic.co condition: logstash.enabled # we are replacing elasticsearch with apache for testing purposes - name: apache - version: "^9.2.23" + version: "^10.2.4" repository: https://charts.bitnami.com/bitnami condition: apache.enabled - name: prometheus - version: "^20.2.0" + version: "^25.8.2" repository: https://prometheus-community.github.io/helm-charts condition: prometheus.enabled - name: logstash-exporter From 4849e51b68039238994d6a7641da1694fec63c35 Mon Sep 17 00:00:00 2001 From: Jakub Date: Tue, 26 Dec 2023 20:35:46 +0100 Subject: [PATCH 33/36] Disable alertmanager and kube-state-metrics in test chart --- .helm/logstash-integration-test-chart/values.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.helm/logstash-integration-test-chart/values.yaml b/.helm/logstash-integration-test-chart/values.yaml index fdac64b5..bda4b9c9 100644 --- a/.helm/logstash-integration-test-chart/values.yaml +++ b/.helm/logstash-integration-test-chart/values.yaml @@ -23,6 +23,10 @@ logstash: prometheus: enabled: true + alertmanager: + enabled: false + kube-state-metrics: + enabled: false service: type: ClusterIP config: | From 4d659154ad25c759c28831909387e68aaf5a0a21 Mon Sep 17 00:00:00 2001 From: Jakub Date: Tue, 26 Dec 2023 20:49:43 +0100 Subject: [PATCH 34/36] Remove prometheus push geteway, change volume config --- .helm/logstash-integration-test-chart/values.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.helm/logstash-integration-test-chart/values.yaml b/.helm/logstash-integration-test-chart/values.yaml index bda4b9c9..624c00ca 100644 --- a/.helm/logstash-integration-test-chart/values.yaml +++ b/.helm/logstash-integration-test-chart/values.yaml @@ -27,6 +27,8 @@ prometheus: enabled: false kube-state-metrics: enabled: false + prometheus-pushgateway: + enabled: false service: type: ClusterIP config: | From c226dbed98346d2b47bf345582ba8ad20e97c060 Mon Sep 17 00:00:00 2001 From: Jakub Date: Tue, 26 Dec 2023 20:52:44 +0100 Subject: [PATCH 35/36] Finally fix volume config for v2 --- chart/templates/deployment.yaml | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/chart/templates/deployment.yaml b/chart/templates/deployment.yaml index d4d37a85..3f0453f2 100644 --- a/chart/templates/deployment.yaml +++ b/chart/templates/deployment.yaml @@ -85,6 +85,8 @@ spec: containers: - name: exporter env: + - name: EXPORTER_CONFIG_LOCATION + value: /app/config.yml {{- range $key, $value := .Values.deployment.env }} - name: {{ $key | quote }} value: {{ $value | quote }} @@ -119,11 +121,11 @@ spec: ports: - name: http containerPort: {{ required "service.port is required" .Values.service.port }} - volumeMounts: - - name: config-volume - mountPath: /app/config.yml - subPath: config.yml - volumes: - - name: config-volume - configMap: - name: {{ include "logstash-exporter.name" . }} + volumeMounts: + - name: config-volume + mountPath: /app/config.yml + subPath: config.yml + volumes: + - name: config-volume + configMap: + name: {{ include "logstash-exporter.name" . }} From 5dbb15dc7e964f5e7626ee7fa76ca8cde0f1acaf Mon Sep 17 00:00:00 2001 From: Jakub Date: Tue, 26 Dec 2023 20:56:57 +0100 Subject: [PATCH 36/36] Add important note about V2 --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index bdff9e9a..88235e52 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,10 @@ The project was created as rewrite of existing awesome application which was also written in Go, but it was not maintained for a long time. A lot of code was reused from the original project. +**Important:** V2 version of the application is currently in beta. It is recommended to use V1 version in production. +It is still maintained and available under [v1 branch](https://github.com/kuskoman/logstash-exporter/tree/v1). +Make sure to check the [Migration](#migration) section before upgrading to V2. + **Important:** Because of limited workforces, this project is tested only against a single Logstash version. You can check the tested version in [docker-compose.yml](./docker-compose.yml) file. Using this exporter with other versions of Logstash may not work properly (although most of the metrics should work).