5
5
package main
6
6
7
7
import (
8
+ "errors"
8
9
"fmt"
9
10
"net/http"
11
+ "path"
10
12
"time"
11
13
12
14
"cloud.google.com/go/compute/metadata"
@@ -126,26 +128,14 @@ func newMetricService() (*metricService, error) {
126
128
if err != nil {
127
129
return nil , err
128
130
}
129
- zone , err := metadata .Zone ()
130
- if err != nil {
131
- return nil , err
132
- }
133
- iname , err := metadata .InstanceName ()
131
+ gr , err := gceResource ("go-playground-sandbox" )
134
132
if err != nil {
135
133
return nil , err
136
134
}
137
135
138
136
sd , err := stackdriver .NewExporter (stackdriver.Options {
139
- ProjectID : projID ,
140
- MonitoredResource : (* monitoredResource )(& mrpb.MonitoredResource {
141
- Type : "generic_task" ,
142
- Labels : map [string ]string {
143
- "instance_id" : iname ,
144
- "job" : "go-playground-sandbox" ,
145
- "project_id" : projID ,
146
- "zone" : zone ,
147
- },
148
- }),
137
+ ProjectID : projID ,
138
+ MonitoredResource : gr ,
149
139
ReportingInterval : time .Minute , // Minimum interval for stackdriver is 1 minute.
150
140
})
151
141
if err != nil {
@@ -154,7 +144,6 @@ func newMetricService() (*metricService, error) {
154
144
155
145
// Minimum interval for stackdriver is 1 minute.
156
146
view .SetReportingPeriod (time .Minute )
157
- view .RegisterExporter (sd )
158
147
// Start the metrics exporter.
159
148
if err := sd .StartMetricsExporter (); err != nil {
160
149
return nil , err
@@ -163,6 +152,7 @@ func newMetricService() (*metricService, error) {
163
152
return & metricService {sdExporter : sd }, nil
164
153
}
165
154
155
+ // metricService controls metric exporters.
166
156
type metricService struct {
167
157
sdExporter * stackdriver.Exporter
168
158
pExporter * prometheus.Exporter
@@ -176,6 +166,7 @@ func (m *metricService) ServeHTTP(w http.ResponseWriter, r *http.Request) {
176
166
http .Error (w , http .StatusText (http .StatusNotFound ), http .StatusNotFound )
177
167
}
178
168
169
+ // Stop flushes metrics and stops exporting. Stop should be called before exiting.
179
170
func (m * metricService ) Stop () {
180
171
if sde := m .sdExporter ; sde != nil {
181
172
// Flush any unsent data before exiting.
@@ -192,3 +183,58 @@ type monitoredResource mrpb.MonitoredResource
192
183
func (r * monitoredResource ) MonitoredResource () (resType string , labels map [string ]string ) {
193
184
return r .Type , r .Labels
194
185
}
186
+
187
+ // gceResource populates a monitoredResource with GCE Metadata.
188
+ //
189
+ // The returned monitoredResource will have the type set to "generic_task".
190
+ func gceResource (jobName string ) (* monitoredResource , error ) {
191
+ projID , err := metadata .ProjectID ()
192
+ if err != nil {
193
+ return nil , err
194
+ }
195
+ zone , err := metadata .Zone ()
196
+ if err != nil {
197
+ return nil , err
198
+ }
199
+ iname , err := metadata .InstanceName ()
200
+ if err != nil {
201
+ return nil , err
202
+ }
203
+ igName , err := instanceGroupName ()
204
+ if err != nil {
205
+ return nil , err
206
+ } else if igName == "" {
207
+ igName = projID
208
+ }
209
+
210
+ return (* monitoredResource )(& mrpb.MonitoredResource {
211
+ Type : "generic_task" , // See: https://cloud.google.com/monitoring/api/resources#tag_generic_task
212
+ Labels : map [string ]string {
213
+ "project_id" : projID ,
214
+ "location" : zone ,
215
+ "namespace" : igName ,
216
+ "job" : jobName ,
217
+ "task_id" : iname ,
218
+ },
219
+ }), nil
220
+ }
221
+
222
+ // instanceGroupName fetches the instanceGroupName from the instance metadata.
223
+ //
224
+ // The instance group manager applies a custom "created-by" attribute to the instance, which is not part of the
225
+ // metadata package API, and must be queried separately.
226
+ //
227
+ // An empty string will be returned if a metadata.NotDefinedError is returned when fetching metadata.
228
+ // An error will be returned if other errors occur when fetching metadata.
229
+ func instanceGroupName () (string , error ) {
230
+ ig , err := metadata .InstanceAttributeValue ("created-by" )
231
+ if nde := metadata .NotDefinedError ("" ); err != nil && ! errors .As (err , & nde ) {
232
+ return "" , err
233
+ }
234
+ if ig == "" {
235
+ return "" , nil
236
+ }
237
+ // "created-by" format: "projects/{{InstanceID}}/zones/{{Zone}}/instanceGroupManagers/{{Instance Group Name}}
238
+ ig = path .Base (ig )
239
+ return ig , err
240
+ }
0 commit comments