Skip to content

Commit

Permalink
Change behavior of custom metrics query
Browse files Browse the repository at this point in the history
  • Loading branch information
camdencheek committed Apr 28, 2020
1 parent 1991514 commit 2ed25d2
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 67 deletions.
16 changes: 8 additions & 8 deletions oracledb-config.yml.sample
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@ instances:
tablespaces: '["tablespace1", "tablespace2"]'
# Collect extended metrics. If omitted, defaults to false.
extended_metrics: true
# custom_metrics_query: >-
# SELECT
# 'physical_reads' AS "metric_name",
# 'gauge' AS "metric_type",
# SUM(PHYRDS) AS "metric_value",
# INST_ID AS "inst_id"
# FROM gv$filestat
# GROUP BY INST_ID
# A custom metrics query will run the custom query, then save the columns as
# metrics on the OracleCustomSample event type.
custom_metrics_query: >-
SELECT
SUM(PHYRDS) AS "physical_reads",
INST_ID AS "inst_id"
FROM gv$filestat
GROUP BY INST_ID
labels:
env: production
81 changes: 30 additions & 51 deletions src/metric_definitions.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@ type newrelicMetric struct {
// a channel along with the metadata needed to insert it into the correct
// metric set
type newrelicMetricSender struct {
metric *newrelicMetric
metadata map[string]string
metric *newrelicMetric
metadata map[string]string
customMetrics []map[string]interface{}
}

// oracleMetricGroup is a struct that contains all the information needed
Expand Down Expand Up @@ -157,6 +158,12 @@ func (mg *customMetricGroup) Collect(db *sqlx.DB, wg *sync.WaitGroup, metricChan
return
}
}
defer func() {
err := rows.Close()
if err != nil {
log.Error("Failed to close rows: %s", err)
}
}()

rows, err = db.Queryx(mg.Query)
if err != nil {
Expand All @@ -170,6 +177,11 @@ func (mg *customMetricGroup) Collect(db *sqlx.DB, wg *sync.WaitGroup, metricChan
}
}()

sender := newrelicMetricSender{
metadata: map[string]string{
"instanceID": instanceID,
},
}
for rows.Next() {
row := make(map[string]interface{})
err := rows.MapScan(row)
Expand All @@ -178,58 +190,25 @@ func (mg *customMetricGroup) Collect(db *sqlx.DB, wg *sync.WaitGroup, metricChan
return
}

nameInterface, ok := row["metric_name"]
if !ok {
log.Error("Missing required column 'metric_name' in custom query")
return
}
name, ok := nameInterface.(string)
if !ok {
log.Error("Non-string type %T for custom query 'metric_name' column", nameInterface)
continue
}

metricTypeInterface, ok := row["metric_type"]
if !ok {
log.Error("Missing required column 'metric_type' in custom query")
return
}
metricTypeString, ok := metricTypeInterface.(string)
if !ok {
log.Error("Non-string type %T for custom query 'metric_type' column", metricTypeInterface)
continue
}
metricType, err := metric.SourceTypeForName(metricTypeString)
if err != nil {
log.Error("Invalid metric type %s: %s", metricTypeString, err)
continue
}

value, ok := row["metric_value"]
if !ok {
log.Error("Missing required column 'metric_type' in custom query")
return
}

metadata := make(map[string]string)
metadata["instanceID"] = instanceID
for k, v := range row {
if k == "metric_name" || k == "metric_type" || k == "metric_value" {
continue
convertedMetrics := make(map[string]interface{})
for key, val := range row {
switch v := val.(type) {
case goracle.Number:
num, err := strconv.ParseFloat(string(v), 64)
if err != nil {
log.Error("Failed to convert %s to a number")
continue
}
convertedMetrics[key] = num
default:
convertedMetrics[key] = val
}

metadata[k] = fmt.Sprintf("%v", v)
}

newMetric := &newrelicMetric{
name: name,
metricType: metricType,
value: value,
}

metricChan <- newrelicMetricSender{metric: newMetric, metadata: metadata}

sender.customMetrics = append(sender.customMetrics, convertedMetrics)
}

metricChan <- sender
}

var oracleLongRunningQueries = oracleMetricGroup{
Expand Down Expand Up @@ -1135,7 +1114,7 @@ var oracleTablespaceMetrics = oracleMetricGroup{

if len(tablespaceWhiteList) > 0 {
query += `
WHERE TABLESPACE_NAME IN (`
WHERE a.TABLESPACE_NAME IN (`

for i, tablespace := range tablespaceWhiteList {
query += fmt.Sprintf(`'%s'`, tablespace)
Expand Down
59 changes: 52 additions & 7 deletions src/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"sync"

"github.com/jmoiron/sqlx"
"github.com/newrelic/infra-integrations-sdk/data/metric"
nrmetric "github.com/newrelic/infra-integrations-sdk/data/metric"
"github.com/newrelic/infra-integrations-sdk/integration"
"github.com/newrelic/infra-integrations-sdk/log"
)
Expand Down Expand Up @@ -71,8 +71,8 @@ func collectMetrics(db *sqlx.DB, populaterWg *sync.WaitGroup, i *integration.Int
func populateMetrics(metricChan <-chan newrelicMetricSender, i *integration.Integration, instanceLookUp map[string]string) {

// Create storage maps for tablespace and instance metric sets
tsMetricSets := make(map[string]*metric.Set)
instanceMetricSets := make(map[string]*metric.Set)
tsMetricSets := make(map[string]*nrmetric.Set)
instanceMetricSets := make(map[string]*nrmetric.Set)

for {
metricSender, ok := <-metricChan
Expand All @@ -88,6 +88,25 @@ func populateMetrics(metricChan <-chan newrelicMetricSender, i *integration.Inte
if err := ms.SetMetric(metric.name, metric.value, metric.metricType); err != nil {
log.Error("Failed to set metric %s: %s", metric.name, err)
}
} else if metricSender.customMetrics != nil {
instanceID := metricSender.metadata["instanceID"]
instanceName := func() string {
if name, ok := instanceLookUp[instanceID]; ok {
return name
}

return instanceID
}()

for _, row := range metricSender.customMetrics {
ms := createCustomMetricSet(instanceName, i)
for key, val := range row {
err := ms.SetMetric(key, val, inferMetricType(val))
if err != nil {
log.Error("Failed to set metric %s with value %v and type %T: %s", key, val, val, err)
}
}
}
} else if instanceID, ok := metricSender.metadata["instanceID"]; ok {
instanceName := func() string {
if name, ok := instanceLookUp[instanceID]; ok {
Expand All @@ -101,13 +120,25 @@ func populateMetrics(metricChan <-chan newrelicMetricSender, i *integration.Inte
if err := ms.SetMetric(metric.name, metric.value, metric.metricType); err != nil {
log.Error("Failed to set metric %s: %s", metric.name, err)
}

}
}
}

func inferMetricType(val interface{}) nrmetric.SourceType {
switch val.(type) {
case string:
return nrmetric.ATTRIBUTE
case float32, float64, int, int32, int64:
return nrmetric.GAUGE
default:
return nrmetric.ATTRIBUTE
}
}

// getOrCreateMetricSet either retrieves a metric set from a map or creates the metric set
// and inserts it into the map.
func getOrCreateMetricSet(entityIdentifier string, entityType string, m map[string]*metric.Set, i *integration.Integration) *metric.Set {
func getOrCreateMetricSet(entityIdentifier string, entityType string, m map[string]*nrmetric.Set, i *integration.Integration) *nrmetric.Set {

// If the metric set already exists, return it
set, ok := m[entityIdentifier]
Expand All @@ -126,11 +157,11 @@ func getOrCreateMetricSet(entityIdentifier string, entityType string, m map[stri
serviceIDAttr,
)

var newSet *metric.Set
var newSet *nrmetric.Set
if entityType == "instance" {
newSet = e.NewMetricSet("OracleDatabaseSample", metric.Attr("entityName", "ora-instance:"+entityIdentifier), metric.Attr("displayName", entityIdentifier))
newSet = e.NewMetricSet("OracleDatabaseSample", nrmetric.Attr("entityName", "ora-instance:"+entityIdentifier), nrmetric.Attr("displayName", entityIdentifier))
} else if entityType == "tablespace" {
newSet = e.NewMetricSet("OracleTablespaceSample", metric.Attr("entityName", "ora-tablespace:"+entityIdentifier), metric.Attr("displayName", entityIdentifier))
newSet = e.NewMetricSet("OracleTablespaceSample", nrmetric.Attr("entityName", "ora-tablespace:"+entityIdentifier), nrmetric.Attr("displayName", entityIdentifier))
} else {
log.Error("Unreachable code")
os.Exit(1)
Expand All @@ -142,6 +173,20 @@ func getOrCreateMetricSet(entityIdentifier string, entityType string, m map[stri
return newSet
}

func createCustomMetricSet(instanceID string, i *integration.Integration) *nrmetric.Set {
endpointIDAttr := integration.IDAttribute{Key: "endpoint", Value: fmt.Sprintf("%s:%s", args.Hostname, args.Port)}
serviceIDAttr := integration.IDAttribute{Key: "serviceName", Value: args.ServiceName}
e, _ := i.EntityReportedVia( //can't error if both name and namespace are defined
fmt.Sprintf("%s:%s", args.Hostname, args.Port),
instanceID,
"ora-instance",
endpointIDAttr,
serviceIDAttr,
)

return e.NewMetricSet("OracleCustomSample", nrmetric.Attr("entityName", "ora-instance:"+instanceID), nrmetric.Attr("displayName", instanceID))
}

// maxTablespaces is the maximum amount of Tablespaces that can be collect.
// If there are more than this number of Tablespaces then collection of
// Tablespaces will fail.
Expand Down
2 changes: 1 addition & 1 deletion src/metrics_definitions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ func TestOracleTablespaceMetrics_Whitlist(t *testing.T) {
t.Error(err)
}

mock.ExpectQuery(`.*WHERE TABLESPACE_NAME IN \('testtablespace','othertablespace'\).*`).WillReturnRows(
mock.ExpectQuery(`.*WHERE a.TABLESPACE_NAME IN \('testtablespace','othertablespace'\).*`).WillReturnRows(
sqlmock.NewRows([]string{"TABLESPACE_NAME", "USED", "OFFLINE", "SIZE", "USED_PERCENT"}).
AddRow("testtablespace", 1234, 0, 4321, 12),
)
Expand Down

0 comments on commit 2ed25d2

Please sign in to comment.