Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions elastictransport/elastictransport.go
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,13 @@ func (c *Client) Perform(req *http.Request) (*http.Response, error) {
break
}

// Record metrics for retry, when enabled
if c.metrics != nil {
c.metrics.Lock()
c.metrics.retries++
c.metrics.Unlock()
}

// Drain and close body when retrying after response
if shouldCloseBody && i < c.maxRetries {
if res.Body != nil {
Expand Down
6 changes: 6 additions & 0 deletions elastictransport/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ type connectionable interface {
type Metrics struct {
Requests int `json:"requests"`
Failures int `json:"failures"`
Retries int `json:"retries"`
Responses map[int]int `json:"responses"`

Connections []fmt.Stringer `json:"connections"`
Expand Down Expand Up @@ -70,6 +71,7 @@ type metrics struct {

requests int
failures int
retries int
responses map[int]int

connections []*Connection
Expand All @@ -92,6 +94,7 @@ func (c *Client) Metrics() (Metrics, error) {
m := Metrics{
Requests: c.metrics.requests,
Failures: c.metrics.failures,
Retries: c.metrics.retries,
Responses: make(map[int]int, len(c.metrics.responses)),
}

Expand Down Expand Up @@ -148,6 +151,9 @@ func (m Metrics) String() string {
b.WriteString(" Failures:")
b.WriteString(strconv.Itoa(m.Failures))

b.WriteString(" Retries:")
b.WriteString(strconv.Itoa(m.Retries))

if len(m.Responses) > 0 {
b.WriteString(" Responses: ")
b.WriteString("[")
Expand Down
112 changes: 112 additions & 0 deletions elastictransport/metrics_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,118 @@ func TestMetrics(t *testing.T) {
t.Errorf("Unexpected output: %s", m)
}
})

t.Run("Retry metrics tracking", func(t *testing.T) {
var attemptCount int
expectedRetries := 2

u, _ := url.Parse("http://foo.bar")
tp, _ := New(Config{
URLs: []*url.URL{u},
Transport: &mockTransp{
RoundTripFunc: func(req *http.Request) (*http.Response, error) {
attemptCount++
fmt.Printf("Attempt #%d", attemptCount)
if attemptCount <= expectedRetries {
fmt.Print(": ERR\n")
return nil, &mockNetError{error: fmt.Errorf("Mock network error (%d)", attemptCount)}
}
fmt.Print(": OK\n")
return &http.Response{Status: "200 OK", StatusCode: 200}, nil
},
},
EnableMetrics: true,
})

req, _ := http.NewRequest("GET", "/test", nil)
res, err := tp.Perform(req)

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

if res.StatusCode != 200 {
t.Errorf("Unexpected response status: %d", res.StatusCode)
}

// Verify metrics
metrics, err := tp.Metrics()
if err != nil {
t.Fatalf("Failed to get metrics: %s", err)
}

if metrics.Requests != 1 {
t.Errorf("Expected 1 request, got %d", metrics.Requests)
}

if metrics.Retries != expectedRetries {
t.Errorf("Expected %d retries, got %d", expectedRetries, metrics.Retries)
}

if metrics.Failures != expectedRetries {
t.Errorf("Expected %d failures, got %d", expectedRetries, metrics.Failures)
}

// Verify the string representation includes retries
metricsStr := metrics.String()
expectedSubstring := fmt.Sprintf("Retries:%d", expectedRetries)
if !regexp.MustCompile(expectedSubstring).MatchString(metricsStr) {
t.Errorf("Expected metrics string to contain '%s', got: %s", expectedSubstring, metricsStr)
}

fmt.Printf("Final metrics: %s\n", metricsStr)
})

t.Run("No retry metrics when retries disabled", func(t *testing.T) {
var attemptCount int

u, _ := url.Parse("http://foo.bar")
tp, _ := New(Config{
URLs: []*url.URL{u},
Transport: &mockTransp{
RoundTripFunc: func(req *http.Request) (*http.Response, error) {
attemptCount++
fmt.Printf("Attempt #%d", attemptCount)
if attemptCount == 1 {
fmt.Print(": ERR\n")
return nil, &mockNetError{error: fmt.Errorf("Mock network error (%d)", attemptCount)}
}
fmt.Print(": OK\n")
return &http.Response{Status: "200 OK", StatusCode: 200}, nil
},
},
EnableMetrics: true,
DisableRetry: true, // Retries disabled
})

req, _ := http.NewRequest("GET", "/test", nil)
_, err := tp.Perform(req)

// Should fail since retries are disabled
if err == nil {
t.Fatalf("Expected error due to disabled retries")
}

// Verify metrics - should show 1 failure but 0 retries
metrics, err := tp.Metrics()
if err != nil {
t.Fatalf("Failed to get metrics: %s", err)
}

if metrics.Requests != 1 {
t.Errorf("Expected 1 request, got %d", metrics.Requests)
}

if metrics.Retries != 0 {
t.Errorf("Expected 0 retries when disabled, got %d", metrics.Retries)
}

if metrics.Failures != 1 {
t.Errorf("Expected 1 failure, got %d", metrics.Failures)
}

fmt.Printf("Final metrics (retries disabled): %s\n", metrics.String())
})
}

func TestTransportPerformAndReadMetricsResponses(t *testing.T) {
Expand Down