-
Notifications
You must be signed in to change notification settings - Fork 2.4k
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
Hook up MetricsQueryService to main funcs #3079
Hook up MetricsQueryService to main funcs #3079
Conversation
Signed-off-by: albertteoh <[email protected]>
@@ -111,11 +111,15 @@ func createGRPCServer(querySvc *querysvc.QueryService, options *QueryOptions, lo | |||
server := grpc.NewServer(grpcOpts...) | |||
|
|||
handler := NewGRPCHandler(querySvc, logger, tracer) | |||
|
|||
// TODO: Register MetricsQueryService |
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.
Will be addressed in a follow-up PR implementing MetricsQuery support for the gRPC handler.
return server, nil | ||
} | ||
|
||
func createHTTPServer(querySvc *querysvc.QueryService, queryOpts *QueryOptions, tracer opentracing.Tracer, logger *zap.Logger) (*http.Server, error) { | ||
func createHTTPServer(querySvc *querysvc.QueryService, metricsQuerySvc *querysvc.MetricsQueryService, queryOpts *QueryOptions, tracer opentracing.Tracer, logger *zap.Logger) (*http.Server, error) { | ||
// TODO: Add HandlerOptions.MetricsQueryService |
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.
Will be addressed in a follow-up PR implementing MetricsQuery support for the HTTP handler.
func NewMetricsQueryService(reader metricsstore.Reader) *MetricsQueryService { | ||
return &MetricsQueryService{ | ||
metricsReader: reader, | ||
} | ||
} | ||
|
||
func (mqs MetricsQueryService) Enabled() bool { |
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.
Hopefully I understood your suggestion correctly, @yurishkuro.
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.
no, what I meant was adding this:
type nullMetricsQueryService struct {}
func (nullMetricsQueryService) GetLatencies(ctx context.Context, params *metricsstore.LatenciesQueryParameters) (*metrics.MetricFamily, error) { return nil, errNotEnabled }
...
and using it in RPC handlers. Basically, solve the problem with polymorphism, not with a bunch of nil checks all over the place.
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.
that's pretty cool :) although, unless if I'm mistaken, I see two minor issues:
- The handlers will need to parse inputs, etc. then call
GetLatencies
, before it knows that Metrics Querying is disabled. I think it would be better to return early; but not sure it's possible with this approach. - Once calling
GetLatencies
, the handler needs to differentiate theerrNotEnabled
from an "exceptional" error case so it can return an appropriate error code rather than the genericStatusInternalServerError
code, which is important as the UI needs to handle both cases differently.
We could follow a pattern like this but there's no guarantee the error could be wrapped up, so I imagine we'd need use something likeerrors.Is
.
what do you think?
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.
point (1) is probably not particularly important, what's the downside of parsing inputs?
point (2) is valid if you want to differentiate real failure from not-implemented (grpc has UNIMPLEMENTED status code iirc). But the handler should look like this:
handler() {
res, err := service.call()
if err != nil {
return nil, errWithStatusCode(err)
}
}
There will be only one place in the code, errWithStatusCode(), that needs to understand errNotEnabled
and transform it. Easy to test, rather than each handler branching to make checks.
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.
and yes, you'd want to use error.Is
, it's the new Go way, what we have with if err == spanstore.ErrTraceNotFound {
predates that.
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.
what's the downside of parsing inputs?
not a large downside, and the emphasis wasn't so much on the input parsing but on when the handler knows if the feature is enabled or disabled. I feel the intent is clearer when reading the handler code instead of needing to follow it to the point where the error is handled:
metricsHandler() {
if notEnabled {
return notImplementedErrorCode
}
res, err := service.call()
...
}
In any case, it's a small matter I think, and happy to go ahead with your suggestion.
Signed-off-by: albertteoh <[email protected]>
Codecov Report
@@ Coverage Diff @@
## master #3079 +/- ##
=======================================
Coverage 95.83% 95.84%
=======================================
Files 234 236 +2
Lines 10068 10073 +5
=======================================
+ Hits 9649 9654 +5
+ Misses 349 348 -1
- Partials 70 71 +1
Continue to review full report at Codecov.
|
Signed-off-by: albertteoh <[email protected]>
Signed-off-by: albertteoh <[email protected]>
Signed-off-by: albertteoh <[email protected]>
cmd/all-in-one/main.go
Outdated
} | ||
|
||
func createMetricsReaderFactory(fc metricsPlugin.FactoryConfig) (*metricsPlugin.Factory, error) { | ||
if !metricsQueryEnabled(fc) { |
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.
are these checks still necessary? wouldn't you always get something back (perhaps disabled service)?
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.
Yeah, it's still necessary otherwise: Cannot initialize metrics store factory: unknown metrics type "". Valid types are [prometheus]
Do you think it's worth doing something similar with the factories? i.e. have disabledMetricsReaderFactory
that simply returns a nil
Reader? That way, these checks won't be necessary.
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 think you can extend the same approach - consider default metrics store type to be "none" and return a "disabled reader" in that case. Basically push this all the way down (you won't need "disabled service" in this case).
Signed-off-by: albertteoh <[email protected]>
Signed-off-by: albertteoh <[email protected]>
Signed-off-by: albertteoh <[email protected]>
plugin/metrics/factory.go
Outdated
case DisabledStorageType: | ||
return disabled.NewFactory(), nil |
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.
Here's the point where the "default" storage factory is created that returns the disabled metrics reader.
"time" | ||
|
||
"github.com/jaegertracing/jaeger/proto-gen/api_v2/metrics" | ||
"github.com/jaegertracing/jaeger/storage/metricsstore" | ||
) | ||
|
||
// MetricsQueryService contains the underlying reader required for querying the metrics store. | ||
// MetricsQueryService provides a means of querying R.E.D metrics from an underlying metrics store. | ||
type MetricsQueryService struct { |
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.
What is MetricsQueryService
supposed to add on top of metricsstore.Reader
?
Note that we used to have HTTP handler use SpanReader directly, but then refactored it into QueryService because we needed to share it with GRPC Handler and the query service was an abstraction on top of two span readers: primary and archive. In your case there doesn't seem to be any additional abstraction needed, you just proxy all calls into the reader directly. Not a huge issue to leave as is, but just curious if there's any thinking that a layer of abstraction would be needed in the future.
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.
Indeed MetricsQueryService
adds little on top of metricsstore.Reader
. It has exactly the same function signature as the Reader and yes, it simply forwards the call. I also don't see a need for multiple metrics readers at runtime, for now at least.
It was added to follow the same design as span/dependency querying to avoid confusing/surprising those following the code and seeing a QueryService
being used by handlers to query spans/dependencies, then a Reader
used to query metrics; when they may expect the handlers to refer to "QueryService" abstractions for querying spans, dependencies and metrics.
One possibility to avoid confusion, yet minimizing unnecessary code, is to make MetricsQueryService
an interface, embedding metricsstore.Reader
, meaning we can remove the MetricsQueryService
struct and its proxy functions altogether and simply give the handlers a metricsstore.Reader
instance. That way, handlers still refer to the "MetricsQueryService" alias for metrics, but instead using a Reader implementation. Something like:
type MetricsQueryService interface {
metricsstore.Reader
}
plugin/metrics/factory.go
Outdated
"github.com/jaegertracing/jaeger/plugin/metrics/prometheus" | ||
"github.com/jaegertracing/jaeger/storage" | ||
"github.com/jaegertracing/jaeger/storage/metricsstore" | ||
) | ||
|
||
const ( | ||
// DisabledStorageType is the storage type used when METRICS_STORAGE_TYPE is unset. | ||
DisabledStorageType = "" |
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.
doesn't seem to be used outside of this pkg.
@@ -0,0 +1,74 @@ | |||
// Copyright (c) 2021 The Jaeger Authors. |
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.
plugin/metrics/disabled/metricsstore/
a bit of overkill with nesting of packages, I would've kept it next to Factory under metrics/disabled
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.
small nits, lgtm otherwise
Signed-off-by: albertteoh <[email protected]>
Signed-off-by: albertteoh [email protected]
Which problem is this PR solving?
Short description of the changes
all-in-one
andquery
executables to be passed on to the HTTP and gRPC handlers.MetricsQueryService
exposes anEnabled()
endpoint to determine if the MetricsQuery feature is enabled (METRICS_STORAGE_TYPE
env var is set), allowing HTTP and gRPC handlers to respond with a sensible error message/code.