-
Notifications
You must be signed in to change notification settings - Fork 109
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: measure external latency (#779)
- Loading branch information
Showing
9 changed files
with
252 additions
and
43 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,8 @@ | ||
module github.com/ory/x | ||
|
||
go 1.21 | ||
go 1.22 | ||
|
||
toolchain go1.22.2 | ||
|
||
require ( | ||
code.dny.dev/ssrf v0.2.0 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
// Copyright © 2024 Ory Corp | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package httpx | ||
|
||
import ( | ||
"net/http" | ||
"time" | ||
|
||
"github.com/ory/x/reqlog" | ||
) | ||
|
||
// MeasureExternalLatencyTransport is an http.RoundTripper that measures the latency of all requests as external latency. | ||
type MeasureExternalLatencyTransport struct { | ||
Transport http.RoundTripper | ||
} | ||
|
||
var _ http.RoundTripper = (*MeasureExternalLatencyTransport)(nil) | ||
|
||
func (m *MeasureExternalLatencyTransport) RoundTrip(req *http.Request) (*http.Response, error) { | ||
upstreamHostPath := req.URL.Scheme + "://" + req.URL.Host + req.URL.Path | ||
defer reqlog.StartMeasureExternalCall(req.Context(), "http_request", upstreamHostPath, time.Now()) | ||
|
||
t := m.Transport | ||
if t == nil { | ||
t = http.DefaultTransport | ||
} | ||
return t.RoundTrip(req) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
// Copyright © 2024 Ory Corp | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package reqlog | ||
|
||
import ( | ||
"context" | ||
"sync" | ||
"time" | ||
) | ||
|
||
// WithEnableExternalLatencyMeasurement returns a context that measures external latencies. | ||
func WithEnableExternalLatencyMeasurement(ctx context.Context) context.Context { | ||
container := contextContainer{ | ||
latencies: make([]externalLatency, 0), | ||
} | ||
return context.WithValue(ctx, externalLatencyKey, &container) | ||
} | ||
|
||
// StartMeasureExternalCall starts measuring the duration of an external call. | ||
// The returned function has to be called to record the duration. | ||
func StartMeasureExternalCall(ctx context.Context, cause, detail string, start time.Time) { | ||
container, ok := ctx.Value(externalLatencyKey).(*contextContainer) | ||
if !ok { | ||
return | ||
} | ||
if _, ok := ctx.Value(disableExternalLatencyMeasurement).(bool); ok { | ||
return | ||
} | ||
|
||
container.Lock() | ||
defer container.Unlock() | ||
container.latencies = append(container.latencies, externalLatency{ | ||
Took: time.Since(start), | ||
Cause: cause, | ||
Detail: detail, | ||
}) | ||
} | ||
|
||
// totalExternalLatency returns the total duration of all external calls. | ||
func totalExternalLatency(ctx context.Context) (total time.Duration) { | ||
if _, ok := ctx.Value(disableExternalLatencyMeasurement).(bool); ok { | ||
return 0 | ||
} | ||
container, ok := ctx.Value(externalLatencyKey).(*contextContainer) | ||
if !ok { | ||
return 0 | ||
} | ||
|
||
container.Lock() | ||
defer container.Unlock() | ||
for _, l := range container.latencies { | ||
total += l.Took | ||
} | ||
return total | ||
} | ||
|
||
// WithDisableExternalLatencyMeasurement returns a context that does not measure external latencies. | ||
// Use this when you want to disable external latency measurements for a specific request. | ||
func WithDisableExternalLatencyMeasurement(ctx context.Context) context.Context { | ||
return context.WithValue(ctx, disableExternalLatencyMeasurement, true) | ||
} | ||
|
||
type ( | ||
externalLatency = struct { | ||
Took time.Duration | ||
Cause, Detail string | ||
} | ||
contextContainer = struct { | ||
latencies []externalLatency | ||
sync.Mutex | ||
} | ||
contextKey int | ||
) | ||
|
||
const ( | ||
externalLatencyKey contextKey = 1 | ||
disableExternalLatencyMeasurement contextKey = 2 | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
// Copyright © 2024 Ory Corp | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package reqlog | ||
|
||
import ( | ||
"encoding/json" | ||
"io" | ||
"net/http" | ||
"net/http/httptest" | ||
"sync" | ||
"testing" | ||
"time" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
"github.com/tidwall/gjson" | ||
"golang.org/x/sync/errgroup" | ||
) | ||
|
||
func TestExternalLatencyMiddleware(t *testing.T) { | ||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
NewMiddleware().ServeHTTP(w, r, func(w http.ResponseWriter, r *http.Request) { | ||
var wg sync.WaitGroup | ||
|
||
wg.Add(3) | ||
for i := range 3 { | ||
ctx := r.Context() | ||
if i%3 == 0 { | ||
ctx = WithDisableExternalLatencyMeasurement(ctx) | ||
} | ||
go func() { | ||
defer StartMeasureExternalCall(ctx, "", "", time.Now()) | ||
time.Sleep(100 * time.Millisecond) | ||
wg.Done() | ||
}() | ||
} | ||
wg.Wait() | ||
total := totalExternalLatency(r.Context()) | ||
_ = json.NewEncoder(w).Encode(map[string]any{ | ||
"total": total, | ||
}) | ||
}) | ||
})) | ||
defer ts.Close() | ||
|
||
bodies := make([][]byte, 100) | ||
eg := errgroup.Group{} | ||
for i := range bodies { | ||
eg.Go(func() error { | ||
res, err := http.Get(ts.URL) | ||
if err != nil { | ||
return err | ||
} | ||
defer res.Body.Close() | ||
bodies[i], err = io.ReadAll(res.Body) | ||
if err != nil { | ||
return err | ||
} | ||
return nil | ||
}) | ||
} | ||
|
||
require.NoError(t, eg.Wait()) | ||
|
||
for _, body := range bodies { | ||
actualTotal := gjson.GetBytes(body, "total").Int() | ||
assert.GreaterOrEqual(t, actualTotal, int64(200*time.Millisecond), string(body)) | ||
assert.Less(t, actualTotal, int64(300*time.Millisecond), string(body)) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters