55package main
66
77import (
8+ "errors"
89 "fmt"
910 "net/http"
11+ "path"
1012 "time"
1113
1214 "cloud.google.com/go/compute/metadata"
@@ -126,26 +128,14 @@ func newMetricService() (*metricService, error) {
126128 if err != nil {
127129 return nil , err
128130 }
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" )
134132 if err != nil {
135133 return nil , err
136134 }
137135
138136 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 ,
149139 ReportingInterval : time .Minute , // Minimum interval for stackdriver is 1 minute.
150140 })
151141 if err != nil {
@@ -154,7 +144,6 @@ func newMetricService() (*metricService, error) {
154144
155145 // Minimum interval for stackdriver is 1 minute.
156146 view .SetReportingPeriod (time .Minute )
157- view .RegisterExporter (sd )
158147 // Start the metrics exporter.
159148 if err := sd .StartMetricsExporter (); err != nil {
160149 return nil , err
@@ -163,6 +152,7 @@ func newMetricService() (*metricService, error) {
163152 return & metricService {sdExporter : sd }, nil
164153}
165154
155+ // metricService controls metric exporters.
166156type metricService struct {
167157 sdExporter * stackdriver.Exporter
168158 pExporter * prometheus.Exporter
@@ -176,6 +166,7 @@ func (m *metricService) ServeHTTP(w http.ResponseWriter, r *http.Request) {
176166 http .Error (w , http .StatusText (http .StatusNotFound ), http .StatusNotFound )
177167}
178168
169+ // Stop flushes metrics and stops exporting. Stop should be called before exiting.
179170func (m * metricService ) Stop () {
180171 if sde := m .sdExporter ; sde != nil {
181172 // Flush any unsent data before exiting.
@@ -192,3 +183,58 @@ type monitoredResource mrpb.MonitoredResource
192183func (r * monitoredResource ) MonitoredResource () (resType string , labels map [string ]string ) {
193184 return r .Type , r .Labels
194185}
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