-
Notifications
You must be signed in to change notification settings - Fork 159
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add AC hit rate metrics with prometheus labels (alternative implementation with decorator) #358
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,8 @@ import ( | |
"encoding/hex" | ||
"io" | ||
"path/filepath" | ||
|
||
pb "github.com/bazelbuild/remote-apis/build/bazel/remote/execution/v2" | ||
) | ||
|
||
// EntryKind describes the kind of cache entry | ||
|
@@ -50,6 +52,52 @@ func (e *Error) Error() string { | |
return e.Text | ||
} | ||
|
||
// Represent the context of an incoming request. For now it acts as an | ||
// adapter providing a common interface to headers from HTTP and gRPC | ||
// requests. In the future it could be extend if additional | ||
// information needs to be associated with a request, or be propagated | ||
// from HTTP/gRPC servers towards disk cache, or perhaps further | ||
// to proxies. | ||
type RequestContext interface { | ||
// Return values for HTTP/gRPC header in the associated | ||
// request. Returns a slice since there could be several | ||
// headers with same name. Returns empty slice if no | ||
// headers exist with the requested name. | ||
// The headerName is expected in lowercase. | ||
GetHeader(headerName string) (headerValues []string) | ||
} | ||
|
||
// TODO Document interface | ||
type BlobStore interface { | ||
// TODO change to io.ReadCloser? | ||
Put(kind EntryKind, hash string, size int64, rdr io.Reader, reqCtx RequestContext) error | ||
|
||
Get(kind EntryKind, hash string, size int64, reqCtx RequestContext) (io.ReadCloser, int64, error) | ||
|
||
Contains(kind EntryKind, hash string, size int64, reqCtx RequestContext) (bool, int64) | ||
} | ||
|
||
// TODO Document interface | ||
type AcStore interface { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this interface used anywhere outside BlobAcStore? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No. I’m only thinking that having a separate AcStore interface might be more generic and flexible. Especially if GetValidatedActionResult method would be extracted from disk.go in the future. Or if there would be decorators wrapping only AcStore. But I don’t have much experience of embedding interfaces and structs in golang. It might work with only BlobStore and BlobAcStore interface, if you think it is preferable to avoid AcStore? |
||
GetValidatedActionResult(hash string, reqCtx RequestContext) (*pb.ActionResult, []byte, error) | ||
} | ||
|
||
// TODO Document interface | ||
type BlobAcStore interface { | ||
BlobStore | ||
AcStore | ||
} | ||
|
||
// TODO Document interface | ||
type Stats interface { | ||
Stats() (totalSize int64, reservedSize int64, numItems int) | ||
MaxSize() int64 | ||
} | ||
|
||
// TODO Could the proxies implement the BlobStore interface instead? And remove Proxy interface? | ||
// Having access to the original headers would allow new use cases such as forwarding of | ||
// custom headers from client via proxy, or support for HTTP headers like Max-Forwards. | ||
|
||
// Proxy is the interface that (optional) proxy backends must implement. | ||
// Implementations are expected to be safe for concurrent use. | ||
type Proxy interface { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -27,6 +27,7 @@ import ( | |
"github.com/golang/protobuf/proto" | ||
) | ||
|
||
// TODO remove these counters? | ||
var ( | ||
cacheHits = promauto.NewCounter(prometheus.CounterOpts{ | ||
Name: "bazel_remote_disk_cache_hits", | ||
|
@@ -230,7 +231,7 @@ func (c *Cache) loadExistingFiles() error { | |
// Put stores a stream of `size` bytes from `r` into the cache. | ||
// If `hash` is not the empty string, and the contents don't match it, | ||
// a non-nil error is returned. | ||
func (c *Cache) Put(kind cache.EntryKind, hash string, size int64, r io.Reader) (rErr error) { | ||
func (c *Cache) Put(kind cache.EntryKind, hash string, size int64, r io.Reader, reqCtx cache.RequestContext) (rErr error) { | ||
if size < 0 { | ||
return fmt.Errorf("Invalid (negative) size: %d", size) | ||
} | ||
|
@@ -471,7 +472,7 @@ func (c *Cache) availableOrTryProxy(key string, size int64, blobPath string) (rc | |
// item is not found, the io.ReadCloser will be nil. If some error occurred | ||
// when processing the request, then it is returned. Callers should provide | ||
// the `size` of the item to be retrieved, or -1 if unknown. | ||
func (c *Cache) Get(kind cache.EntryKind, hash string, size int64) (rc io.ReadCloser, s int64, rErr error) { | ||
func (c *Cache) Get(kind cache.EntryKind, hash string, size int64, reqCtx cache.RequestContext) (rc io.ReadCloser, s int64, rErr error) { | ||
|
||
// The hash format is checked properly in the http/grpc code. | ||
// Just perform a simple/fast check here, to catch bad tests. | ||
|
@@ -575,7 +576,7 @@ func (c *Cache) Get(kind cache.EntryKind, hash string, size int64) (rc io.ReadCl | |
// one) will be checked. | ||
// | ||
// Callers should provide the `size` of the item, or -1 if unknown. | ||
func (c *Cache) Contains(kind cache.EntryKind, hash string, size int64) (bool, int64) { | ||
func (c *Cache) Contains(kind cache.EntryKind, hash string, size int64, reqCtx cache.RequestContext) (bool, int64) { | ||
|
||
// The hash format is checked properly in the http/grpc code. | ||
// Just perform a simple/fast check here, to catch bad tests. | ||
|
@@ -648,9 +649,12 @@ func cacheFilePath(kind cache.EntryKind, cacheDir string, hash string) string { | |
// value from the CAS if it and all its dependencies are also available. If | ||
// not, nil values are returned. If something unexpected went wrong, return | ||
// an error. | ||
func (c *Cache) GetValidatedActionResult(hash string) (*pb.ActionResult, []byte, error) { | ||
// TODO Consider separating implementation of cache.AcStore interface, to open up | ||
// possibilities combining that functionality with proxies, and also allow | ||
// bazel-remote configurations with proxy but no local disk storage? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A proxy-only mode has been on my todo-list for a while. I have considered doing this as a short-circuit inside disk.Cache (it might be time to rename or split the package at that point). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @mostynb Does it make sense to bring back the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @mostynb Is it something more than implementation of GetValidatedActionResult, that you expect will be needed from disk.io, in a proxy-only-mode? |
||
func (c *Cache) GetValidatedActionResult(hash string, reqCtx cache.RequestContext) (*pb.ActionResult, []byte, error) { | ||
|
||
rc, sizeBytes, err := c.Get(cache.AC, hash, -1) | ||
rc, sizeBytes, err := c.Get(cache.AC, hash, -1, reqCtx) | ||
if rc != nil { | ||
defer rc.Close() | ||
} | ||
|
@@ -675,15 +679,15 @@ func (c *Cache) GetValidatedActionResult(hash string) (*pb.ActionResult, []byte, | |
|
||
for _, f := range result.OutputFiles { | ||
if len(f.Contents) == 0 { | ||
found, _ := c.Contains(cache.CAS, f.Digest.Hash, f.Digest.SizeBytes) | ||
found, _ := c.Contains(cache.CAS, f.Digest.Hash, f.Digest.SizeBytes, reqCtx) | ||
if !found { | ||
return nil, nil, nil // aka "not found" | ||
} | ||
} | ||
} | ||
|
||
for _, d := range result.OutputDirectories { | ||
r, size, err := c.Get(cache.CAS, d.TreeDigest.Hash, d.TreeDigest.SizeBytes) | ||
r, size, err := c.Get(cache.CAS, d.TreeDigest.Hash, d.TreeDigest.SizeBytes, reqCtx) | ||
if r == nil { | ||
return nil, nil, err // aka "not found", or an err if non-nil | ||
} | ||
|
@@ -714,7 +718,7 @@ func (c *Cache) GetValidatedActionResult(hash string) (*pb.ActionResult, []byte, | |
if f.Digest == nil { | ||
continue | ||
} | ||
found, _ := c.Contains(cache.CAS, f.Digest.Hash, f.Digest.SizeBytes) | ||
found, _ := c.Contains(cache.CAS, f.Digest.Hash, f.Digest.SizeBytes, reqCtx) | ||
if !found { | ||
return nil, nil, nil // aka "not found" | ||
} | ||
|
@@ -725,7 +729,7 @@ func (c *Cache) GetValidatedActionResult(hash string) (*pb.ActionResult, []byte, | |
if f.Digest == nil { | ||
continue | ||
} | ||
found, _ := c.Contains(cache.CAS, f.Digest.Hash, f.Digest.SizeBytes) | ||
found, _ := c.Contains(cache.CAS, f.Digest.Hash, f.Digest.SizeBytes, reqCtx) | ||
if !found { | ||
return nil, nil, nil // aka "not found" | ||
} | ||
|
@@ -734,14 +738,14 @@ func (c *Cache) GetValidatedActionResult(hash string) (*pb.ActionResult, []byte, | |
} | ||
|
||
if result.StdoutDigest != nil { | ||
found, _ := c.Contains(cache.CAS, result.StdoutDigest.Hash, result.StdoutDigest.SizeBytes) | ||
found, _ := c.Contains(cache.CAS, result.StdoutDigest.Hash, result.StdoutDigest.SizeBytes, reqCtx) | ||
if !found { | ||
return nil, nil, nil // aka "not found" | ||
} | ||
} | ||
|
||
if result.StderrDigest != nil { | ||
found, _ := c.Contains(cache.CAS, result.StderrDigest.Hash, result.StderrDigest.SizeBytes) | ||
found, _ := c.Contains(cache.CAS, result.StderrDigest.Hash, result.StderrDigest.SizeBytes, reqCtx) | ||
if !found { | ||
return nil, nil, nil // aka "not found" | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we could use a map[string]string instead of a RequestContext interface in these functions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried that first, but ended up with cache.RequestContext because:
gRPC and HTTP/2 is using canonical lowercase header names, but the golang HTTP/1.1 library normalize header names to start with capital letter. I want to hide that difference, but avoid translating headers that are never accessed, and instead translate on demand in:
bazel-remote/server/http.go
Lines 395 to 398 in 4b2b8b2
Minimize overhead for creating cache.RequestContext instance in general (only allocate struct and store a pointer)
Allows extending cache.RequestContext with additional methods that returns non-header meta information. Such as Host, RemoteAddr or Cookie from HTTP requests. Or any other information that could be added by bazel-remote’s HTTP/GRPC servers, like enum indicating if original request were HTTP or GRPC.
Allows extending cache.RequestContext to allow proxies to cancel outgoing requests, if associated incomming request is canceled. I have not looked into details, but seems to be something related to that in underlying GRPC and HTTP contexts.
Allows extending cache.RequestContext in general, instead of having to update all Get/Put/Contains invocations with additional parameters, all over bazel-remote codebase.
Allows propagating custom information in general between a chain of different cache.BlobStore implementations (such as proxies, decorators, …)