Skip to content

Commit

Permalink
Merge pull request #139 from nats-io/js-varz
Browse files Browse the repository at this point in the history
[added] recursive discovery of varz values
  • Loading branch information
matthiashanel authored Jun 25, 2021
2 parents 5e178e0 + 0cb20ec commit 2677b2f
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 29 deletions.
118 changes: 89 additions & 29 deletions collector/collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,15 @@ type CollectedServer struct {
ID string
}

type metric struct {
path []string
metric interface{}
}

// NATSCollector collects NATS metrics
type NATSCollector struct {
sync.Mutex
Stats map[string]interface{}
Stats map[string]metric
httpClient *http.Client
endpoint string
system string
Expand Down Expand Up @@ -173,7 +178,7 @@ func (nc *NATSCollector) Describe(ch chan<- *prometheus.Desc) {

// for each stat in nc.Stats
for _, k := range nc.Stats {
switch m := k.(type) {
switch m := k.metric.(type) {

// Describe the stat to the channel
case *prometheus.GaugeVec:
Expand Down Expand Up @@ -203,30 +208,42 @@ func (nc *NATSCollector) makeRequests() map[string]map[string]interface{} {
return resps
}

func lookupValue(response map[string]interface{}, path []string) interface{} {
for _, tk := range path {
switch r := response[tk].(type) {
case map[string]interface{}:
response = r
default:
return r
}
}
return nil
}

// collectStatsFromRequests collects the statistics from a set of responses
// returned by a NATS server.
func (nc *NATSCollector) collectStatsFromRequests(
key string, stat interface{}, resps map[string]map[string]interface{}, ch chan<- prometheus.Metric) {
switch m := stat.(type) {
key string, stat metric, resps map[string]map[string]interface{}, ch chan<- prometheus.Metric) {
switch m := stat.metric.(type) {
case *prometheus.GaugeVec:
for id, response := range resps {
switch v := response[key].(type) {
case float64: // not sure why, but all my json numbers are coming here.
switch v := lookupValue(response, stat.path).(type) {
case float64: // json only has floats
m.WithLabelValues(id).Set(v)
case string:
m.With(prometheus.Labels{"server_id": id, "value": v}).Set(1)
default:
Debugf("value no longer a float", id, v)
Debugf("value %s no longer a float", key, id, v)
}
}
m.Collect(ch) // update the stat.
case *prometheus.CounterVec:
for id, response := range resps {
switch v := response[key].(type) {
case float64: // not sure why, but all my json numbers are coming here.
switch v := lookupValue(response, stat.path).(type) {
case float64: // json only has floats
m.WithLabelValues(id).Add(v)
default:
Debugf("value no longer a float", id, v)
Debugf("value %s no longer a float", key, id, v)
}
}
m.Collect(ch) // update the stat.
Expand All @@ -251,11 +268,10 @@ func (nc *NATSCollector) Collect(ch chan<- prometheus.Metric) {
// initMetricsFromServers builds the configuration
// For each NATS Metrics endpoint (/*z) get the first URL
// to determine the list of possible metrics.
// TODO: flatten embedded maps.
func (nc *NATSCollector) initMetricsFromServers(namespace string) {
var response map[string]interface{}

nc.Stats = make(map[string]interface{})
nc.Stats = make(map[string]metric)

// gets URLs until one responds.
for _, v := range nc.servers {
Expand All @@ -276,30 +292,74 @@ func (nc *NATSCollector) initMetricsFromServers(namespace string) {
}
}

nc.objectToMetrics(response, namespace)
}

// returns a sanitized fully qualified name and path
func fqName(name string, prefix ...string) (string, []string) {
l := len(prefix) + 1
path := make([]string, 0, l)
if l > 1 {
path = append(path, prefix...)
}
path = append(path, name)
fqn := strings.Trim(strings.ReplaceAll(strings.Join(path, "_"), "/", "_"), "_")
for strings.Contains(fqn, "__") {
fqn = strings.ReplaceAll(fqn, "__", "_")
}
return fqn, path
}

func (nc *NATSCollector) objectToMetrics(response map[string]interface{}, namespace string, prefix ...string) {
skipFQN := map[string]struct{}{
"leaf": {},
"trusted_operators_claim": {},
"cluster_tls_timeout": {},
"cluster_cluster_port": {},
"cluster_auth_timeout": {},
"gateway_port": {},
"gateway_auth_timeout": {},
"gateway_tls_timeout": {},
"gateway_connect_retries": {},
}
labelKeys := map[string]struct{}{
"server_id": {},
"server_name": {},
"version": {},
"domain": {},
"leader": {},
"name": {},
}

// for each metric
for k := range response {
// if it's not already defined in metricDefinitions
_, ok := nc.Stats[k]
if !ok {
i := response[k]
switch v := i.(type) {
case float64: // all json numbers are handled here.
nc.Stats[k] = newPrometheusGaugeVec(nc.system, nc.endpoint, k, "", namespace)
case string:
if _, ok := labelKeys[k]; !ok {
break
}
nc.Stats[k] = newLabelGauge(nc.system, nc.endpoint, k, "", namespace, "value")
default:
// not one of the types currently handled
Tracef("Unknown type: %v, %v", k, v)
fqn, path := fqName(k, prefix...)
if _, ok := skipFQN[fqn]; ok {
continue
}
// if it's not already defined in metricDefinitions
if _, ok := nc.Stats[fqn]; ok {
continue
}
i := response[k]
switch v := i.(type) {
case float64: // all json numbers are handled here.
nc.Stats[fqn] = metric{
path: path,
metric: newPrometheusGaugeVec(nc.system, nc.endpoint, fqn, "", namespace),
}
case string:
if _, ok := labelKeys[k]; !ok {
break
}
nc.Stats[fqn] = metric{
path: path,
metric: newLabelGauge(nc.system, nc.endpoint, fqn, "", namespace, "value"),
}
case map[string]interface{}:
// recurse and flatten
nc.objectToMetrics(v, namespace, path...)
default:
// not one of the types currently handled
Tracef("Unknown type: %v %v, %v", fqn, k, v)
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
Expand Down Expand Up @@ -177,6 +178,7 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4=
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
Expand Down

0 comments on commit 2677b2f

Please sign in to comment.