-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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
Redis receiver #138
Conversation
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 { |
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.
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)) | ||
} | ||
|
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.
Need to test error/failure handling as well.
receiver/redisreceiver/receiver.go
Outdated
} | ||
|
||
func (r redisReceiver) Start(host component.Host) error { | ||
r.Lock() |
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 modeled this on the other receivers but would like to learn more about why the mutex and startOnce are needed.
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.
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).
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.
Ok, will remove for now. Thanks.
import "github.com/open-telemetry/opentelemetry-collector/config/configmodels" | ||
|
||
type Config struct { | ||
configmodels.ReceiverSettings `mapstructure:",squash"` |
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.
Please add a README with configuration information -- see other receivers for an example
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.
Sounds good. Will do.
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.
@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.
receiver/redisreceiver/converters.go
Outdated
import ( | ||
"strconv" | ||
|
||
metricsProto "github.com/census-instrumentation/opencensus-proto/gen-go/metrics/v1" |
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.
We usually alias this import to metricspb
elsewhere in the codebase.
metricsProto "github.com/census-instrumentation/opencensus-proto/gen-go/metrics/v1" | |
metricspb "github.com/census-instrumentation/opencensus-proto/gen-go/metrics/v1" |
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.
Fixed.
receiver/redisreceiver/api_parser.go
Outdated
|
||
import "strings" | ||
|
||
type apiParser 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.
Please add comments to explain the purpose of non-trivial structs and 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.
Done.
package redisreceiver | ||
|
||
import ( | ||
resourceProto "github.com/census-instrumentation/opencensus-proto/gen-go/resource/v1" |
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.
For uniformity with the rest of the codebase:
resourceProto "github.com/census-instrumentation/opencensus-proto/gen-go/resource/v1" | |
resourcepb "github.com/census-instrumentation/opencensus-proto/gen-go/resource/v1" |
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.
Fixed.
receiver/redisreceiver/receiver.go
Outdated
} | ||
|
||
func (r redisReceiver) Start(host component.Host) error { | ||
r.Lock() |
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.
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", |
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.
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?
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 kind of punted on the units for this draft, but will make sure they are consistent with the spec.
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.
Fixed.
type metricType int | ||
|
||
const ( | ||
unspecified = iota | ||
gaugeInt | ||
gaugeDouble | ||
gaugeDistribution | ||
cumulativeInt | ||
cumulativeDouble | ||
) |
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.
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?
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 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.
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.
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.
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.
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? |
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.
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.
Thanks for the review. Will add comments. |
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.
I signed it |
Co-Authored-By: Paulo Janotti <pjanotti@splunk.com>
receiver/redisreceiver/proto.go
Outdated
} | ||
|
||
// 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 { |
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.
You can use this: https://godoc.org/github.com/golang/protobuf/ptypes#TimestampProto
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.
Nice, fixed.
receiver/redisreceiver/proto.go
Outdated
|
||
if redisMetric.mdType == metricspb.MetricDescriptor_CUMULATIVE_INT64 || | ||
redisMetric.mdType == metricspb.MetricDescriptor_CUMULATIVE_DOUBLE { | ||
startTime = ×tamp.Timestamp{} // todo: not sure about this |
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 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).
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.
Nice, thanks. I have made a change to track the server start time as you suggest.
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 |
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.
👍
return nil | ||
} | ||
|
||
// Runs intermittently, querying Redis and building Metrics to send to the |
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.
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, ...
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.
Better, fixed.
continue | ||
} | ||
pair := strings.Split(line, ":") | ||
if len(pair) == 2 { // defensive, should always == 2 |
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.
👍
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. |
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.
Nice overview.
receiver/redisreceiver/README.md
Outdated
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. |
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.
Nit: please use consistent line spacing for paragraphs (the first 2 paragraphs do not have an empty line between).
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.
Done (it's just one paragraph).
receiver/redisreceiver/README.md
Outdated
|
||
_Required._ | ||
|
||
### refresh_interval |
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.
Is it collection_interval or refresh_interval?
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.
Should be collection interval -- I renamed the field and missed this file. Fixed.
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
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.
LGTM. Thanks @pmcollins!
* Run golangci-lint in all directories with go.mod * Imports fix by golangci-lint * Propagate failures from commands running in loops
Description: Add Redis receiver.