Skip to content
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

Merged
merged 11 commits into from
Jun 12, 2021

Conversation

albertteoh
Copy link
Contributor

Signed-off-by: albertteoh [email protected]

Which problem is this PR solving?

Short description of the changes

  • Instantiates the MetricsQueryService from both the all-in-one and query executables to be passed on to the HTTP and gRPC handlers.
  • MetricsQueryService exposes an Enabled() 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.

@@ -111,11 +111,15 @@ func createGRPCServer(querySvc *querysvc.QueryService, options *QueryOptions, lo
server := grpc.NewServer(grpcOpts...)

handler := NewGRPCHandler(querySvc, logger, tracer)

// TODO: Register MetricsQueryService
Copy link
Contributor Author

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
Copy link
Contributor Author

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 {
Copy link
Contributor Author

@albertteoh albertteoh Jun 10, 2021

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.

Copy link
Member

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.

Copy link
Contributor Author

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 the errNotEnabled from an "exceptional" error case so it can return an appropriate error code rather than the generic StatusInternalServerError 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 like errors.Is.

what do you think?

Copy link
Member

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.

Copy link
Member

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.

Copy link
Contributor Author

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
Copy link

codecov bot commented Jun 10, 2021

Codecov Report

Merging #3079 (a8a420f) into master (e33977e) will increase coverage by 0.00%.
The diff coverage is 100.00%.

Impacted file tree graph

@@           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     
Impacted Files Coverage Δ
cmd/query/app/querysvc/metrics_query_service.go 100.00% <ø> (ø)
cmd/query/app/server.go 97.08% <100.00%> (ø)
plugin/metrics/disabled/factory.go 100.00% <100.00%> (ø)
plugin/metrics/disabled/reader.go 100.00% <100.00%> (ø)
plugin/metrics/factory.go 100.00% <100.00%> (ø)
cmd/query/app/static_handler.go 94.35% <0.00%> (-2.42%) ⬇️
plugin/storage/integration/integration.go 77.90% <0.00%> (+0.55%) ⬆️
pkg/config/tlscfg/cert_watcher.go 94.80% <0.00%> (+2.59%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update e33977e...a8a420f. Read the comment docs.

Signed-off-by: albertteoh <[email protected]>
@albertteoh albertteoh marked this pull request as ready for review June 10, 2021 03:19
@albertteoh albertteoh requested a review from a team as a code owner June 10, 2021 03:19
@albertteoh albertteoh requested a review from vprithvi June 10, 2021 03:19
cmd/query/app/querysvc/metrics_query_service.go Outdated Show resolved Hide resolved
cmd/query/app/querysvc/metrics_query_service.go Outdated Show resolved Hide resolved
}

func createMetricsReaderFactory(fc metricsPlugin.FactoryConfig) (*metricsPlugin.Factory, error) {
if !metricsQueryEnabled(fc) {
Copy link
Member

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)?

Copy link
Contributor Author

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.

Copy link
Member

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).

albertteoh added 3 commits June 11, 2021 10:58
Signed-off-by: albertteoh <[email protected]>
Signed-off-by: albertteoh <[email protected]>
Signed-off-by: albertteoh <[email protected]>
Comment on lines 68 to 69
case DisabledStorageType:
return disabled.NewFactory(), nil
Copy link
Contributor Author

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 {
Copy link
Member

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.

Copy link
Contributor Author

@albertteoh albertteoh Jun 12, 2021

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
}

"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 = ""
Copy link
Member

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.
Copy link
Member

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

yurishkuro
yurishkuro previously approved these changes Jun 11, 2021
Copy link
Member

@yurishkuro yurishkuro left a 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

@yurishkuro yurishkuro merged commit 013efad into jaegertracing:master Jun 12, 2021
@albertteoh albertteoh deleted the 2954-hookup-mains branch June 13, 2021 05:04
@jpkrohling jpkrohling added this to the Release 1.24.0 milestone Jun 22, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants