Conversation
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.
Please rename the directory and the associated package as "stackdriver", so that we can add support for other exporter backends in the future.
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.
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.
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.
This could be renamed as just Exported, so that usages would be stackdriver.Exporter.
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.
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.
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.
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.
Please start comments with upper case. Here and in other places.
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.
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
}
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.
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.
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.
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.
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.
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.
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
|
easwars
left a comment
There was a problem hiding this comment.
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.
Please add a separate comment for each exported variable, in Go syntax.
| 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.
What non-deterministic behavior are you talking about?
There was a problem hiding this comment.
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.
How about accepting an Options instead of *Options in this function, and you would need to make a copy here.
| 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.
Is this some hard limit imposed by Stackdriver? If not, why should it be not configurable?
There was a problem hiding this comment.
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.
This function should ideally reside with other functions of the Exporter type.
| } | ||
|
|
||
| // 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.
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.
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.
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.
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.
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.
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.
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.
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.
Does this have to an exported variable?
There was a problem hiding this comment.
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 |
| 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.
Please refer to these as API calls and not RPC calls, here and everywhere else. Thanks.
|
|
||
| // 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.
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.
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.