Skip to content

Commit

Permalink
Merge pull request #115 from newrelic/applicationMetricNames
Browse files Browse the repository at this point in the history
feat(apm): Add support application metric names and data
  • Loading branch information
zlesnr authored Jan 29, 2020
2 parents 8d1c9e4 + 6cd4d5e commit 78c1e65
Show file tree
Hide file tree
Showing 5 changed files with 304 additions and 104 deletions.
2 changes: 1 addition & 1 deletion pkg/alerts/channels_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ func TestIntegrationChannel(t *testing.T) {
},
Payload: MapStringInterface{
"account_id": "123",
"array": []interface{}{"string", 2},
"array": []interface{}{"string", 2},
"object": map[string]interface{}{
"key": "value",
"nestedObject": map[string]interface{}{
Expand Down
63 changes: 63 additions & 0 deletions pkg/apm/apm_types.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package apm

import "time"

// Application represents information about a New Relic application.
type Application struct {
ID int `json:"id,omitempty"`
Expand Down Expand Up @@ -97,3 +99,64 @@ type LabelLinks struct {
Applications []int `json:"applications"`
Servers []int `json:"servers"`
}

// ListApplicationsParams represents a set of filters to be
// used when querying New Relic applications.
type ListApplicationsParams struct {
Name string `url:"filter[name],omitempty"`
Host string `url:"filter[host],omitempty"`
IDs []int `url:"filter[ids],omitempty,comma"`
Language string `url:"filter[language],omitempty"`
}

// UpdateApplicationParams represents a set of parameters to be
// used when updating New Relic applications.
type UpdateApplicationParams struct {
Name string
Settings ApplicationSettings
}

// MetricNamesParams are the request parameters for the /metrics.json endpoint.
type MetricNamesParams struct {
Name string `url:"name,omitempty"`
}

// MetricDataParams are the request parameters for the /metrics/data.json endpoint.
type MetricDataParams struct {
Names []string `url:"names,omitempty"`
Values []string `url:"values,omitempty"`
From *time.Time `url:"from,omitempty"`
To *time.Time `url:"to,omitempty"`
Period int `url:"period,omitempty"`
Summarize bool `url:"summarize,omitempty"`
Raw bool `url:"raw,omitempty"`
}

// MetricName is the name of a metric, and the names of the values that can be retrieved.
type MetricName struct {
Name string `json:"name,omitempty"`
Values []string `json:"values,omitempty"`
}

// MetricData is the series of time windows and the data therein, for a given metric name.
type MetricData struct {
Name string `json:"name,omitempty"`
Timeslices []MetricTimeslice `json:"timeslices,omitempty"`
}

// MetricTimeslice is a single window of time for a given metric, with the associated metric data.
type MetricTimeslice struct {
From *time.Time `json:"from"`
To *time.Time `json:"to"`
Values MetricTimesliceValues `json:"values"`
}

//MetricTimesliceValues is the collection of metric values for a single time slice.
type MetricTimesliceValues struct {
AsPercentage float64 `json:"as_percentage"`
AverageTime float64 `json:"average_time"`
CallsPerMinute float64 `json:"calls_per_minute"`
MaxValue float64 `json:"max_value"`
TotalCallTimePerMinute float64 `json:"total_call_time_per_minute"`
Utilization float64 `json:"utilization"`
}
79 changes: 63 additions & 16 deletions pkg/apm/applications.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,9 @@ package apm

import (
"fmt"
"time"
)

// ListApplicationsParams represents a set of filters to be
// used when querying New Relic applications.
type ListApplicationsParams struct {
Name string `url:"filter[name],omitempty"`
Host string `url:"filter[host],omitempty"`
IDs []int `url:"filter[ids],omitempty,comma"`
Language string `url:"filter[language],omitempty"`
}

// ListApplications is used to retrieve New Relic applications.
func (apm *APM) ListApplications(params *ListApplicationsParams) ([]*Application, error) {
response := applicationsResponse{}
Expand Down Expand Up @@ -49,13 +41,6 @@ func (apm *APM) GetApplication(applicationID int) (*Application, error) {
return &response.Application, nil
}

// UpdateApplicationParams represents a set of parameters to be
// used when updating New Relic applications.
type UpdateApplicationParams struct {
Name string
Settings ApplicationSettings
}

// UpdateApplication is used to update a New Relic application's name and/or settings.
func (apm *APM) UpdateApplication(applicationID int, params UpdateApplicationParams) (*Application, error) {
response := applicationResponse{}
Expand Down Expand Up @@ -88,6 +73,54 @@ func (apm *APM) DeleteApplication(applicationID int) (*Application, error) {
return &response.Application, nil
}

// GetMetricNames is used to retrieve a list of known metrics and their value names for the given resource.
//
// https://rpm.newrelic.com/api/explore/applications/metric_names
func (apm *APM) GetMetricNames(applicationID int, params MetricNamesParams) ([]*MetricName, error) {
response := metricNamesResponse{}
metrics := []*MetricName{}
nextURL := fmt.Sprintf("/applications/%d/metrics.json", applicationID)

for nextURL != "" {
resp, err := apm.client.Get(nextURL, &params, &response)

if err != nil {
return nil, err
}

metrics = append(metrics, response.Metrics...)

paging := apm.pager.Parse(resp)
nextURL = paging.Next
}

return metrics, nil
}

// GetMetricData is used to retrieve a list of values for each of the requested metrics.
//
// https://rpm.newrelic.com/api/explore/applications/metric_data
func (apm *APM) GetMetricData(applicationID int, params MetricDataParams) ([]*MetricData, error) {
response := metricDataResponse{}
data := []*MetricData{}
nextURL := fmt.Sprintf("/applications/%d/metrics/data.json", applicationID)

for nextURL != "" {
resp, err := apm.client.Get(nextURL, &params, &response)

if err != nil {
return nil, err
}

data = append(data, response.MetricData.Metrics...)

paging := apm.pager.Parse(resp)
nextURL = paging.Next
}

return data, nil
}

type applicationsResponse struct {
Applications []*Application `json:"applications,omitempty"`
}
Expand All @@ -104,3 +137,17 @@ type updateApplicationFields struct {
Name string `json:"name,omitempty"`
Settings ApplicationSettings `json:"settings,omitempty"`
}

type metricNamesResponse struct {
Metrics []*MetricName
}

type metricDataResponse struct {
MetricData struct {
From *time.Time `json:"from"`
To *time.Time `json:"to"`
MetricsNotFound []string `json:"metrics_not_found"`
MetricsFound []string `json:"metrics_found"`
Metrics []*MetricData `json:"metrics"`
} `json:"metric_data"`
}
104 changes: 17 additions & 87 deletions pkg/apm/applications_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,114 +3,44 @@
package apm

import (
"os"
"testing"

"github.com/newrelic/newrelic-client-go/pkg/config"
"github.com/stretchr/testify/require"
)

func TestIntegrationListApplications(t *testing.T) {
func TestIntegrationApplications(t *testing.T) {
t.Parallel()

apiKey := os.Getenv("NEWRELIC_API_KEY")
client := newIntegrationTestClient(t)

if apiKey == "" {
t.Skipf("acceptance testing requires an API key")
}

api := New(config.Config{
APIKey: apiKey,
LogLevel: "debug",
})

_, err := api.ListApplications(nil)

if err != nil {
t.Fatalf("ListApplications error: %s", err)
}
}

func TestIntegrationGetApplication(t *testing.T) {
t.Parallel()

apiKey := os.Getenv("NEWRELIC_API_KEY")

if apiKey == "" {
t.Skipf("acceptance testing requires an API key")
}

api := New(config.Config{
APIKey: apiKey,
LogLevel: "debug",
})

a, err := api.ListApplications(nil)

if err != nil {
t.Fatal(err)
}

_, err = api.GetApplication(a[0].ID)

if err != nil {
t.Fatal(err)
}
}

func TestIntegrationUpdateApplication(t *testing.T) {
t.Parallel()

apiKey := os.Getenv("NEWRELIC_API_KEY")

if apiKey == "" {
t.Skipf("acceptance testing requires an API key")
}

api := New(config.Config{
APIKey: apiKey,
LogLevel: "debug",
})

a, err := api.ListApplications(nil)

if err != nil {
t.Fatal(err)
}
a, err := client.ListApplications(nil)
require.NoError(t, err)

_, err = api.GetApplication(a[0].ID)

if err != nil {
t.Fatal(err)
}
_, err = client.GetApplication(a[0].ID)
require.NoError(t, err)

params := UpdateApplicationParams{
Name: a[0].Name,
Settings: a[0].Settings,
}

_, err = api.UpdateApplication(a[0].ID, params)
_, err = client.UpdateApplication(a[0].ID, params)
require.NoError(t, err)

if err != nil {
t.Fatal(err)
}
n, err := client.GetMetricNames(a[0].ID, MetricNamesParams{})
require.NoError(t, err)

_, err = client.GetMetricData(a[0].ID, MetricDataParams{Names: []string{n[0].Name}})
require.NoError(t, err)
}

func TestIntegrationDeleteApplication(t *testing.T) {
t.Skip()
t.Skip("What does delete mean in the case where we have no create?")
t.Parallel()

apiKey := os.Getenv("NEWRELIC_API_KEY")

if apiKey == "" {
t.Skipf("acceptance testing requires an API key")
}

api := New(config.Config{
APIKey: apiKey,
LogLevel: "debug",
})
client := newIntegrationTestClient(t)

_, err := api.DeleteApplication(0)
_, err := client.DeleteApplication(0)

if err != nil {
t.Fatal(err)
Expand Down
Loading

0 comments on commit 78c1e65

Please sign in to comment.