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

Redis receiver #138

Merged
merged 33 commits into from
Apr 8, 2020
Merged

Redis receiver #138

merged 33 commits into from
Apr 8, 2020

Conversation

pmcollins
Copy link
Member

Description: Add Redis receiver.

Pablo Collins added 4 commits March 12, 2020 12:52
Todo:
1) Error handling: currently bailing out on any error. Need to add resiliency.
2) Improved organization: everything is in one sprawling package.
3) Verification of Redis metric types.
4) Licensing headers.
5) Comments.
6) Logging.
@@ -0,0 +1,231 @@
package redisreceiver

func uptimeInSeconds() *redisMetric {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It has been suggested that I turn this data into a map. Looking into it.

require.Nil(t, err)
require.Equal(t, 29, len(md.Metrics))
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to test error/failure handling as well.

}

func (r redisReceiver) Start(host component.Host) error {
r.Lock()
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I modeled this on the other receivers but would like to learn more about why the mutex and startOnce are needed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should not be needed. Start is called once only.

@pjanotti any thoughts on this? I saw the same pattern in signalfxreceiver, so perhaps there is something I am missing.

(We do have "once" protection in opencensusreceiver, but there it is needed because it implements trace and metrics receiver via single instance and internal start function may be called twice. Everywhere else I do not think we need such protection).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, will remove for now. Thanks.

import "github.com/open-telemetry/opentelemetry-collector/config/configmodels"

type Config struct {
configmodels.ReceiverSettings `mapstructure:",squash"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a README with configuration information -- see other receivers for an example

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good. Will do.

Copy link
Member

@tigrannajaryan tigrannajaryan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pmcollins Nice work. I left a few comments.

A general concern about the code is lack of explanatory comments. Would be great to add some.

import (
"strconv"

metricsProto "github.com/census-instrumentation/opencensus-proto/gen-go/metrics/v1"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We usually alias this import to metricspb elsewhere in the codebase.

Suggested change
metricsProto "github.com/census-instrumentation/opencensus-proto/gen-go/metrics/v1"
metricspb "github.com/census-instrumentation/opencensus-proto/gen-go/metrics/v1"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.


import "strings"

type apiParser struct {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add comments to explain the purpose of non-trivial structs and functions.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

package redisreceiver

import (
resourceProto "github.com/census-instrumentation/opencensus-proto/gen-go/resource/v1"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For uniformity with the rest of the codebase:

Suggested change
resourceProto "github.com/census-instrumentation/opencensus-proto/gen-go/resource/v1"
resourcepb "github.com/census-instrumentation/opencensus-proto/gen-go/resource/v1"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

}

func (r redisReceiver) Start(host component.Host) error {
r.Lock()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should not be needed. Start is called once only.

@pjanotti any thoughts on this? I saw the same pattern in signalfxreceiver, so perhaps there is something I am missing.

(We do have "once" protection in opencensusreceiver, but there it is needed because it implements trace and metrics receiver via single instance and internal start function may be called twice. Everywhere else I do not think we need such protection).

func uptimeInSeconds() *redisMetric {
return &redisMetric{
key: "uptime_in_seconds",
units: "seconds",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this eventually gets assigned to MetricDescriptor.Unit it needs to follow conventions defined in http://unitsofmeasure.org/ucum.html

@bogdandrutu do I understand it correct that we should use "c/s" form for unit, i.e. for seconds we should use "s". Is that right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kind of punted on the units for this draft, but will make sure they are consistent with the spec.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

Comment on lines 73 to 82
type metricType int

const (
unspecified = iota
gaugeInt
gaugeDouble
gaugeDistribution
cumulativeInt
cumulativeDouble
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any reason that we redeclare this instead of using MetricDescriptor_Type type? The code elsewhere assumes they must be equivalent and uses just type-cast for converting between metricType and MetricDescriptor_Type. In that case what do we gain by having this separate declaration?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to keep this independent of protobuf because it was my understanding that there would be a new data model coming soon that was encoding agnostic, so I didn't want to potentially have to change these values in a bunch of places. Happy to use MetricDescriptor_Type directly though if you think that would be better at this time.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I understand now the intent. However, because we simply use type conversion between metricType and MetricDescriptor_Type they are inherently tightly coupled although it may not appear so when glancing at the code. If the intent is to make redis metric declarations encoding agnostic then it would be better to have conversion function from metricType to MetricDescriptor_Type and avoid using type conversion.
That is likely an overkill though. We are likely fine with relying on internal data types (and we can assume MetricDescriptor_Type is an internal data type - although the assumption is temporary) and just use MetricDescriptor_Type directly. Once we have the new internal data types we can just update the declaration here. When that happens the compiler will tell us if there are any type mismatches elsewhere, but only if we avoid using type conversions.

Anyway, this is a minor nit for now. Eventually I believe we should feel free to use internal data types within our codebase.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good. Replaced with MetricDescriptor_Type.

func buildSingleProtoMetric(strVal string, redisMetric *redisMetric) (*metricsProto.Metric, error) {
f := getPointConverter(redisMetric)
if f == nil {
return nil, errors.New("oops") // todo constant?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Presumably this can never happen because we don't have redisMetrics of any type that is not an int or double. Can we somehow ensure that if we do we can a compile error or we have a test that ensures this is not possible? This would prevent future problems if someone adds a redisMetric with a different type to getDefaultRedisMetrics.

@pmcollins
Copy link
Member Author

@pmcollins Nice work. I left a few comments.

A general concern about the code is lack of explanatory comments. Would be great to add some.

Thanks for the review. Will add comments.

@pmcollins
Copy link
Member Author

I signed it

@pmcollins pmcollins marked this pull request as ready for review March 23, 2020 18:32
@pmcollins pmcollins requested a review from songy23 as a code owner March 23, 2020 18:32
receiver/redisreceiver/README.md Show resolved Hide resolved
receiver/redisreceiver/README.md Outdated Show resolved Hide resolved
receiver/redisreceiver/README.md Outdated Show resolved Hide resolved
receiver/redisreceiver/README.md Outdated Show resolved Hide resolved
receiver/redisreceiver/client.go Outdated Show resolved Hide resolved
receiver/redisreceiver/keyspace.go Outdated Show resolved Hide resolved
receiver/redisreceiver/proto.go Outdated Show resolved Hide resolved
receiver/redisreceiver/redis_runnable.go Outdated Show resolved Hide resolved
receiver/redisreceiver/redis_metric.go Show resolved Hide resolved
receiver/redisreceiver/info.go Outdated Show resolved Hide resolved
}

// TODO: Maybe this should be moved to a general purpose utility if we don't have one already
func timeToTimestamp(t *time.Time) *timestamp.Timestamp {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, fixed.


if redisMetric.mdType == metricspb.MetricDescriptor_CUMULATIVE_INT64 ||
redisMetric.mdType == metricspb.MetricDescriptor_CUMULATIVE_DOUBLE {
startTime = &timestamp.Timestamp{} // todo: not sure about this
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 we should approximate the startTime as:
Now() - uptime_in_seconds

We should do this only the first time we query the metrics because of the network delay/clock skew we may not get the same number all the time, but the approximation I proposed is good enough.

And as a bonus we can calculate the current time not as the Time.Now() but as startTime + uptime_in_seconds - first_scrape_uptime_in_seconds (got at first scrape). This way we avoid network delays problems.

If we do this trick with start_time we should track when uptime_in_seconds < last_scrape_uptime_in_seconds and recalculate the startime and update the first_scrape_uptime_in_seconds (if you do the bonus part).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, thanks. I have made a change to track the server start time as you suggest.

@tigrannajaryan
Copy link
Member

Please use imperative mood in the subject line of commit message first line and add more details to both commit message and PR description to explain what the change does.

If you didn't yet have a chance please read:

instance, build metrics from that data, and send them to the next consumer at a
configurable interval.

Status: beta
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

return nil
}

// Runs intermittently, querying Redis and building Metrics to send to the
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Runs intermittently

This is a bit confusing. This function performs a single run, single cycle of collection from Redis, right? When I read the comment I get the impression that Run() function internally performs multiple run cycles.

Perhaps reword a bit, like this:

// Run is called periodically, ...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better, fixed.

continue
}
pair := strings.Split(line, ":")
if len(pair) == 2 { // defensive, should always == 2
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Comment on lines +5 to +7
The Redis receiver is designed to retrieve Redis INFO data from a single Redis
instance, build metrics from that data, and send them to the next consumer at a
configurable interval.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice overview.

Comment on lines 13 to 22
The Redis INFO command returns information and statistics about a Redis
server (see [https://redis.io/commands/info](https://redis.io/commands/info) for
details).
The Redis receiver extracts values from the result and converts them to open
telemetry metrics. Details about the metrics produced by the Redis receiver
can be found by browsing [metric_functions.go](metric_functions.go).

For example, one of the fields returned by the Redis INFO command is
`used_cpu_sys` which indicates the system CPU consumed by the Redis server,
expressed in seconds, since the start of the Redis instance.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: please use consistent line spacing for paragraphs (the first 2 paragraphs do not have an empty line between).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done (it's just one paragraph).


_Required._

### refresh_interval
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it collection_interval or refresh_interval?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be collection interval -- I renamed the field and missed this file. Fixed.

Pablo Collins and others added 6 commits April 6, 2020 12:02
Cumulative metrics have an implied start time. This change keeps track
of the server start time so it can be applied to cumulative metrics.

Also fixes issues raised in PR feedback.
* Use named return values for errors that should be treated as warnings
* Handle malformed keyspace key value pairs
* Update README to use env var in example config
* Remove unnecessary EndMetricsReceiveOp in redis runnable
Copy link
Contributor

@pjanotti pjanotti left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Thanks @pmcollins!

@pjanotti pjanotti merged commit 7fd9a76 into open-telemetry:master Apr 8, 2020
ljmsc referenced this pull request in ljmsc/opentelemetry-collector-contrib Feb 21, 2022
* Run golangci-lint in all directories with go.mod

* Imports fix by golangci-lint

* Propagate failures from commands running in loops
XinRanZhAWS pushed a commit to XinRanZhAWS/opentelemetry-collector-contrib that referenced this pull request Feb 9, 2024
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.

None yet

6 participants