diff --git a/README.md b/README.md index b8385eb..cf9d432 100755 --- a/README.md +++ b/README.md @@ -201,6 +201,7 @@ Supported optional metrics: * `artifacts` - Extracts number of artifacts created/downloaded for each repository. Enabling this will add `artifactory_artifacts_*` metrics. Please note that on large Artifactory instances, this may impact the performance. * `replication_status` - Extracts status of replication for each repository which has replication enabled. Enabling this will add the `status` label to `artifactory_replication_enabled` metric. * `federation_status` - Extracts federation metrics. Enabling this will add two new metrics: `artifactory_federation_mirror_lag`, and `artifactory_federation_unavailable_mirror`. Please note that these metrics are only available in Artifactory Enterprise Plus and version 7.18.3 and above. +* `open_metrics` - Exposes Open Metrics from the JFrog Platform. For more information about Open Metrics, please refer to [JFrog Platform Open Metrics](https://jfrog.com/help/r/jfrog-platform-administration-documentation/open-metrics). ### Grafana Dashboard diff --git a/artifactory/openmetrics.go b/artifactory/openmetrics.go new file mode 100644 index 0000000..e3ca87c --- /dev/null +++ b/artifactory/openmetrics.go @@ -0,0 +1,32 @@ +package artifactory + +import ( + "github.com/go-kit/log/level" +) + +const openMetricsEndpoint = "v1/metrics" + +type OpenMetrics struct { + PromMetrics string + NodeId string +} + +// FetchOpenMetrics makes the API call to open metrics endpoint and returns all the open metrics +func (c *Client) FetchOpenMetrics() (OpenMetrics, error) { + var openMetrics OpenMetrics + level.Debug(c.logger).Log("msg", "Fetching openMetrics") + resp, err := c.FetchHTTP(openMetricsEndpoint) + if err != nil { + if err.(*APIError).status == 404 { + return openMetrics, nil + } + return openMetrics, err + } + + level.Debug(c.logger).Log("msg", "OpenMetrics from Artifactory", "body", string(resp.Body)) + + openMetrics.NodeId = resp.NodeId + openMetrics.PromMetrics = string(resp.Body) + + return openMetrics, nil +} diff --git a/collector/artifacts.go b/collector/artifacts.go index 0f3990a..6a20610 100644 --- a/collector/artifacts.go +++ b/collector/artifacts.go @@ -3,7 +3,6 @@ package collector import ( "encoding/json" "fmt" - "github.com/go-kit/log/level" "github.com/prometheus/client_golang/prometheus" ) diff --git a/collector/collector.go b/collector/collector.go index 22cece3..086cd33 100755 --- a/collector/collector.go +++ b/collector/collector.go @@ -68,6 +68,9 @@ var ( "mirrorLag": newMetric("mirror_lag", "federation", "Federation mirror lag in milliseconds.", federationLabelNames), "unavailableMirror": newMetric("unavailable_mirror", "federation", "Unsynchronized federated mirror status", append([]string{"status"}, federationLabelNames...)), } + openMetrics = metrics{ + "openMetrics": newMetric("open_metrics", "openmetrics", "OpenMetrics proxied from JFrog Platform", defaultLabelNames), + } ) func init() { @@ -99,6 +102,11 @@ func (e *Exporter) Describe(ch chan<- *prometheus.Desc) { ch <- m } } + if e.optionalMetrics.OpenMetrics { + for _, m := range openMetrics { + ch <- m + } + } ch <- e.up.Desc() ch <- e.totalScrapes.Desc() @@ -154,6 +162,14 @@ func (e *Exporter) scrape(ch chan<- prometheus.Metric) (up float64) { } } + // Collect and export open metrics + if e.optionalMetrics.OpenMetrics { + err = e.exportOpenMetrics(ch) + if err != nil { + return 0 + } + } + // Collect and export system metrics err = e.exportSystem(license, ch) if err != nil { diff --git a/collector/openMetrics.go b/collector/openMetrics.go new file mode 100644 index 0000000..97a443e --- /dev/null +++ b/collector/openMetrics.go @@ -0,0 +1,60 @@ +package collector + +import ( + "strings" + + "github.com/go-kit/log/level" + "github.com/prometheus/client_golang/prometheus" + io_prometheus_client "github.com/prometheus/client_model/go" + "github.com/prometheus/common/expfmt" +) + +func (e *Exporter) exportOpenMetrics(ch chan<- prometheus.Metric) error { + // Fetch Open Metrics + openMetrics, err := e.client.FetchOpenMetrics() + if err != nil { + level.Error(e.logger).Log("msg", "There was an issue when try to fetch openMetrics") + e.totalAPIErrors.Inc() + return err + } + + level.Debug(e.logger).Log("msg", "OpenMetrics from Artifactory util", "body", openMetrics.PromMetrics) + + // assign openMetrics.Metric to a string variable + openMetricsString := openMetrics.PromMetrics + + parser := expfmt.TextParser{} + metrics, err := parser.TextToMetricFamilies(strings.NewReader(openMetricsString)) + if err != nil { + // handle the error + return err + } + + for _, family := range metrics { + for _, metric := range family.Metric { + // create labels map + labels := make(map[string]string) + for _, label := range metric.Label { + labels[*label.Name] = *label.Value + } + + // create a new descriptor + desc := prometheus.NewDesc( + family.GetName(), + family.GetHelp(), + nil, + labels, + ) + + // create a new metric and collect it + switch family.GetType() { + case io_prometheus_client.MetricType_COUNTER: + ch <- prometheus.MustNewConstMetric(desc, prometheus.CounterValue, metric.GetCounter().GetValue()) + case io_prometheus_client.MetricType_GAUGE: + ch <- prometheus.MustNewConstMetric(desc, prometheus.GaugeValue, metric.GetGauge().GetValue()) + } + } + } + + return nil +} diff --git a/config/config.go b/config/config.go index 839245b..101dcce 100644 --- a/config/config.go +++ b/config/config.go @@ -22,7 +22,7 @@ var ( optionalMetrics = kingpin.Flag("optional-metric", "optional metric to be enabled. Pass multiple times to enable multiple optional metrics.").PlaceHolder("metric-name").Strings() ) -var optionalMetricsList = []string{"artifacts", "replication_status", "federation_status"} +var optionalMetricsList = []string{"artifacts", "replication_status", "federation_status", "open_metrics"} // Credentials represents Username and Password or API Key for // Artifactory Authentication @@ -37,6 +37,7 @@ type OptionalMetrics struct { Artifacts bool ReplicationStatus bool FederationStatus bool + OpenMetrics bool } // Config represents all configuration options for running the Exporter. @@ -88,6 +89,8 @@ func NewConfig() (*Config, error) { optMetrics.ReplicationStatus = true case "federation_status": optMetrics.FederationStatus = true + case "open_metrics": + optMetrics.OpenMetrics = true default: return nil, fmt.Errorf("unknown optional metric: %s. Valid optional metrics are: %s", metric, optionalMetricsList) }