-
Notifications
You must be signed in to change notification settings - Fork 0
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
stackdriver exporter implemented #2
base: master
Are you sure you want to change the base?
Conversation
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.
More comments on the way ...
monitoring/exporter/exporter.go
Outdated
// 3.2. RowData has correcponding GCP projects, and we can determine its project ID. | ||
// 3.3. After trimming labels and tags, configuration of all view data matches that of corresponding | ||
// stackdriver metric | ||
package exporter |
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 rename the directory and the associated package as "stackdriver", so that we can add support for other exporter backends 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.
ok
monitoring/exporter/exporter.go
Outdated
"go.opencensus.io/stats/view" | ||
"google.golang.org/api/option" | ||
"google.golang.org/api/support/bundler" | ||
monitoredrespb "google.golang.org/genproto/googleapis/api/monitoredres" |
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.
If you are taking the trouble to alias the import, I would rather that you use a smaller alias name. Something like:
mrpb "google.golang.org/genproto/googleapis/api/monitoredres"
mpb "google.golang.org/genproto/googleapis/monitoring/v3"
etc.
Here and in all other places. Thanks.
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
monitoring/exporter/exporter.go
Outdated
|
||
// StatsExporter is the exporter that can be registered to opencensus. A StatsExporter object must | ||
// be created by NewStatsExporter(). | ||
type StatsExporter 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.
This could be renamed as just Exported, so that usages would be stackdriver.Exporter.
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
monitoring/exporter/exporter.go
Outdated
// StatsExporter is the exporter that can be registered to opencensus. A StatsExporter object must | ||
// be created by NewStatsExporter(). | ||
type StatsExporter struct { | ||
ctx context.Context |
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'm not a big fan of storing a context within a struct. I see that the context object is only used for creating the metric client in NewStatsExporter(), which is already being passed the context. So, why do you need to store the context object?
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.
Sorry, I see that you are using the context in other places. Would it possible to actually pass a context from the caller instead of storing it in this exporter object. Something to think about. If it is too much of a hassle, at least add a TODO to have that cleaned up later on.
https://groups.google.com/forum/#!topic/golang-nuts/xRbzq8yzKWI
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 generally agree that context should not be stored in a struct. But MetricClient.CreateTimeSeries() is not directly called by customer, so I think it's ugly compromise for now. My lame excuse is that current stackdriver exporter is doing the same thing. (sigh...) But I added a TODO as you suggested.
monitoring/exporter/exporter.go
Outdated
BundleDelayThreshold time.Duration | ||
BundleCountThreshold int | ||
|
||
// callback functions provided by user. |
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 start comments with upper case. Here and in other places.
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
monitoring/exporter/exporter.go
Outdated
|
||
// We don't want to modify user-supplied options, so save default options directly in | ||
// exporter. | ||
if opts.GetProjectID != 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.
You can replace all these if-else blocks with an initialization followed by an if-block.
Example:
e.onError = defaultOnError
if opts.OnError != nil {
e.onError = opts.OnError
}
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
monitoring/exporter/exporter.go
Outdated
} | ||
|
||
// We wrap monitoring.MetricClient and it's maker for testing. | ||
type metricClient interface { |
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 shouldn't have to define an interface for this. Instead the client member in the exporter should just be a monitoring.MetricClient object, and in your test you should make a fake MetricClient by mocking the MetricServiceClient in the below definition of MetricClient.
// MetricClient is a client for interacting with Stackdriver Monitoring API.
type MetricClient struct {
// The connection to the service.
conn *grpc.ClientConn
// The gRPC API client.
metricClient monitoringpb.MetricServiceClient
// The call options for this service.
CallOptions *MetricCallOptions
// The metadata to be sent with each request.
metadata metadata.MD
}
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. I removed that interface but mocked monitoring.NewMetricClient and monitoring.MetricClient.CreateTimesSeries
monitoring/exporter/exporter.go
Outdated
} | ||
|
||
// default values for options | ||
func defaultGetProjectID(rd *RowData) (string, error) { |
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.
Would it better if the default function handled the checking of Tags in the RowData to see if there is a project_id tag and returning the corresponding ID?
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. I did and wrote a test for it
monitoring/exporter/project_data.go
Outdated
} | ||
|
||
// We wrap bundler and its maker for testing purpose. | ||
type expBundler interface { |
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.
Again, I feel this is not the correct use of interface. You should return concrete types wherever possible, and interfaces should be defined by the users at the place of use.
https://medium.com/@cep21/preemptive-interface-anti-pattern-in-go-54c18ac0668a
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. Similar to monitroing.MetricClient, I mocked some function defined in the blunder package.
865798a
to
277f032
Compare
And general comments (most of them about tests) on commit "fix suggested by easwars" 3f25a33
|
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 haven't yet looked at the tests. Will wait till the code comments are resolved. Thanks.
// RPC calls. | ||
ClientOptions []option.ClientOption | ||
|
||
// options for bundles amortizing export requests. Note that a bundle is created for each |
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 separate comment for each exported variable, in Go syntax.
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
return &mrpb.MonitoredResource{Type: "global"}, nil | ||
} | ||
|
||
// Following functions are wrapper of functions that may show non-deterministic behavior. Only tests |
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 non-deterministic behavior are you talking about?
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 that comment is unnecessary. I simply removed those parts.
projDataMap: make(map[string]*projectData), | ||
} | ||
|
||
// We don't want to modify user-supplied options, so save default options directly in |
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.
How about accepting an Options instead of *Options in this function, and you would need to make a copy here.
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
mpb "google.golang.org/genproto/googleapis/monitoring/v3" | ||
) | ||
|
||
// MaxTimeSeriePerUpload is the maximum number of time series that stackdriver accepts. Only test |
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 this some hard limit imposed by Stackdriver? If not, why should it be not configurable?
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.
Replied separately.
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.
As you suggested I reduced the number to 100, and make it configurable but put a warning there.
bndler *bundler.Bundler | ||
} | ||
|
||
func (e *Exporter) newProjectData(projectID string) *projectData { |
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.
This function should ideally reside with other functions of the Exporter type.
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
} | ||
|
||
// makeLables constructs label that's ready for being uploaded to stackdriver. | ||
func (e *Exporter) makeLabels(tags []tag.Tag) map[string]string { |
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.
Same for this function.
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
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.
Sorry, I changed the file name from project_data.go to upload.go and collects all functions/methods that works for uploading row data after bundler request for an upload operation. (It seems better for me. If you don't like, I can split by structs again, or merge everything to one file except row_data_to_point.go )
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 followed your suggestion. One file per struct now.
pt := newPoint(rd.View, rd.Row, rd.Start, rd.End) | ||
if pt.Value == nil { | ||
err := fmt.Errorf("inconsistent data found in view %s", rd.View.Name) | ||
pd.parent.onError(err, rd) |
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.
How about calling these onError functions in goroutines to not block here on what happens in the error handlers?
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.
Can we just add requirement to onError() that it should be non-blocking? If I call "go { ...onError() }", then, I guess it's better to create go routine for all places that onError() is called.
// reqRds contains RowData objects those are uploaded to stackdriver at given iteration. | ||
// It's main usage is for error reporting. For actual uploading operation, we use req. | ||
// remainingRds are RowData that has not been processed at all. | ||
var reqRds, remainingRds []*RowData |
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.
This whole flow seems a little hard to read for me. How about restructuring this code as follows:
func uploadRowData(bundle) {
- get the slice of RowData from the bundle
- iterate through the slice, and for each RowData {
- call a func named makeTS() which returns one &mpb.TimeSeries
- maintain a slice of TimeSeries objects here, and append the one returned in the previous line to that slice
- if the slice has reached the required length, call another function (or just inline it here) to actually make the RPC
} - if you have items still in the timeSeries slice, make another call to send an RPC
}
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.
I generally followed your suggestion.
151f1a0
to
51b24c1
Compare
0b16819
to
8fb9793
Compare
I merged all changes to one commit: 'review round 2 fix" 8fb9793 . I hope it's easy for you to review. |
mpb "google.golang.org/genproto/googleapis/monitoring/v3" | ||
) | ||
|
||
// MaxTimeSeriePerUpload is the maximum number of time series that's uploaded to the stackdriver |
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.
Would you mind framing this as follows:
MaxTimeSeriesPerUpload is the maximum number of timeseries objects that will be uploaded to Stackdriver in one API call. Users are not encouraged to change this value as Stackdriver may reject an upload request if it contains too many timeseries objects.
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
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 behavior of MaxTimeSeriesPerUpload is somewhat changed, so I also changed the comment.
// MaxTimeSeriePerUpload is the maximum number of time series that's uploaded to the stackdriver | ||
// at once. Consumer may change this value, but note that stackdriver may reject upload request if | ||
// the number of time series is too large. | ||
var MaxTimeSeriesPerUpload = 100 |
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.
Does this have to an exported variable?
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.
Did separate discussion to resolve issue.
type projectData struct { | ||
parent *Exporter | ||
projectID string | ||
// We make bundler for each project because call to monitoring RPC can be grouped only in |
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.
s/monitoring RPC/monitoring API
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
TimeSeries: ts, | ||
} | ||
if err := createTimeSeries(exp.client, exp.ctx, req); err != nil { | ||
newErr := fmt.Errorf("RPC call to create time series failed for project %s: %v", pd.projectID, err) |
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 refer to these as API calls and not RPC calls, here and everywhere else. Thanks.
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
|
||
// newTestExp creates an exporter which saves error to errStorage. Caller should not set | ||
// opts.OnError. | ||
func newTestExp(t *testing.T, opts Options) *Exporter { |
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 make this file consistent by not accepting testing.T as a parameter to these helper functions.
These helper functions should just return an error, and it is up to the caller to call t.Error() or t.Fatal().
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
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 also implemented a tiny helper type to aggregate multiple errors.
Changes not mentioned in above.
|
bf4e62e
to
1e12b16
Compare
Let's hope this sync is clean.