Skip to content

Commit ad4a6e6

Browse files
committed
Add new metrics
1 parent 49092b3 commit ad4a6e6

File tree

5 files changed

+358
-48
lines changed

5 files changed

+358
-48
lines changed

dialer.go

Lines changed: 94 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ import (
4444
"github.com/google/uuid"
4545
"golang.org/x/net/proxy"
4646
"google.golang.org/api/option"
47+
48+
tel "cloud.google.com/go/cloudsqlconn/internal/tel"
4749
sqladmin "google.golang.org/api/sqladmin/v1beta4"
4850
)
4951

@@ -71,6 +73,8 @@ var (
7173
//go:embed version.txt
7274
versionString string
7375
userAgent = "cloud-sql-go-connector/" + strings.TrimSpace(versionString)
76+
// dialerID is a unique ID for the dialer process.
77+
dialerID = uuid.New().String()
7478
)
7579

7680
// keyGenerator encapsulates the details of RSA key generation to provide lazy
@@ -174,7 +178,9 @@ type Dialer struct {
174178

175179
// dialerID uniquely identifies a Dialer. Used for monitoring purposes,
176180
// *only* when a client has configured OpenCensus exporters.
177-
dialerID string
181+
dialerID string
182+
metricsMu sync.Mutex
183+
metricRecorders map[instance.ConnName]tel.MetricRecorder
178184

179185
// dialFunc is the function used to connect to the address on the named
180186
// network. By default, it is golang.org/x/net/proxy#Dial.
@@ -190,6 +196,18 @@ type Dialer struct {
190196
// metadataExchangeDisabled true when the dialer should never
191197
// send MDX mdx requests.
192198
metadataExchangeDisabled bool
199+
200+
// applicationName is the name of the application using the dialer.
201+
applicationName string
202+
203+
// disableBuiltInMetrics turns the internal metric export into a no-op.
204+
disableBuiltInMetrics bool
205+
206+
// clientOpts are options for all Google Cloud API clients.
207+
clientOpts []option.ClientOption
208+
209+
// userAgent is the combined user agent string.
210+
userAgent string
193211
}
194212

195213
var (
@@ -208,11 +226,12 @@ func (nullLogger) Debugf(_ context.Context, _ string, _ ...interface{}) {}
208226
// RSA keypair is generated will be faster.
209227
func NewDialer(ctx context.Context, opts ...Option) (*Dialer, error) {
210228
cfg := &dialerConfig{
211-
refreshTimeout: cloudsql.RefreshTimeout,
212-
dialFunc: proxy.Dial,
213-
logger: nullLogger{},
214-
useragents: []string{userAgent},
215-
failoverPeriod: cloudsql.FailoverPeriod,
229+
refreshTimeout: cloudsql.RefreshTimeout,
230+
dialFunc: proxy.Dial,
231+
logger: nullLogger{},
232+
useragents: []string{userAgent},
233+
failoverPeriod: cloudsql.FailoverPeriod,
234+
applicationName: "unknown",
216235
}
217236
for _, opt := range opts {
218237
opt(cfg)
@@ -318,17 +337,49 @@ func NewDialer(ctx context.Context, opts ...Option) (*Dialer, error) {
318337
sqladmin: client,
319338
logger: cfg.logger,
320339
defaultDialConfig: dc,
321-
dialerID: uuid.New().String(),
340+
dialerID: dialerID,
322341
iamTokenProvider: cfg.iamLoginTokenProvider,
342+
metricRecorders: map[instance.ConnName]tel.MetricRecorder{},
323343
dialFunc: cfg.dialFunc,
324344
resolver: r,
325345
failoverPeriod: cfg.failoverPeriod,
326346
metadataExchangeDisabled: cfg.metadataExchangeDisabled,
347+
userAgent: strings.Join(cfg.useragents, " "),
348+
applicationName: cfg.applicationName,
327349
}
328350

351+
// print dialer id to terminal for debugging purposes
352+
fmt.Println("Cloud SQL Go Connector Dialer ID:", d.dialerID)
353+
329354
return d, nil
330355
}
331356

357+
// metricRecorder does a lazy initialization of the metric exporter.
358+
func (d *Dialer) metricRecorder(ctx context.Context, inst instance.ConnName) tel.MetricRecorder {
359+
d.metricsMu.Lock()
360+
defer d.metricsMu.Unlock()
361+
if mr, ok := d.metricRecorders[inst]; ok {
362+
return mr
363+
}
364+
cfg := tel.Config{
365+
Enabled: !d.disableBuiltInMetrics,
366+
Version: versionString,
367+
ResourceContainer: inst.Project(),
368+
ResourceID: inst.Name(),
369+
ClientUID: d.dialerID,
370+
ApplicationName: d.applicationName,
371+
Region: inst.Region(),
372+
ClientRegion: "Client-Region-Testing", // TODO: detect client region
373+
ComputePlatform: "Compute-Platform-Testing", // TODO: detect compute platform
374+
ConnectorType: tel.ConnectorTypeValue(d.userAgent),
375+
ConnectorVersion: versionString,
376+
DatabaseEngineType: "DB-Engine-Type-Testing", // TODO: detect database engine type
377+
}
378+
mr := tel.NewMetricRecorder(ctx, d.logger, cfg, d.clientOpts...)
379+
d.metricRecorders[inst] = mr
380+
return mr
381+
}
382+
332383
// Dial returns a net.Conn connected to the specified Cloud SQL instance. The
333384
// icn argument may be the instance's connection name in the format
334385
// "project-name:region:instance-name" or a DNS name that resolves to an
@@ -339,8 +390,29 @@ func (d *Dialer) Dial(ctx context.Context, icn string, opts ...DialOption) (conn
339390
return nil, ErrDialerClosed
340391
default:
341392
}
393+
cfg := d.defaultDialConfig
394+
for _, opt := range opts {
395+
opt(&cfg)
396+
}
397+
398+
// Resolve the instance connection name to a ConnName struct.
399+
// Note: icn may be a domain name that resolves to an instance connection name.
400+
cn, err := d.resolver.Resolve(ctx, icn)
401+
if err != nil {
402+
return nil, err
403+
}
404+
mr := d.metricRecorder(ctx, cn)
405+
342406
startTime := time.Now()
343407
var endDial trace.EndSpanFunc
408+
attrs := tel.Attributes{
409+
IAMAuthN: cfg.useIAMAuthN,
410+
RefreshType: tel.RefreshAheadType,
411+
IPType: cfg.ipType,
412+
}
413+
if d.lazyRefresh {
414+
attrs.RefreshType = tel.RefreshLazyType
415+
}
344416
ctx, endDial = trace.StartSpan(ctx, "cloud.google.com/go/cloudsqlconn.Dial",
345417
trace.AddInstanceName(icn),
346418
trace.AddDialerID(d.dialerID),
@@ -349,10 +421,6 @@ func (d *Dialer) Dial(ctx context.Context, icn string, opts ...DialOption) (conn
349421
trace.RecordDialError(context.Background(), icn, d.dialerID, err)
350422
endDial(err)
351423
}()
352-
cn, err := d.resolver.Resolve(ctx, icn)
353-
if err != nil {
354-
return nil, err
355-
}
356424

357425
// Log if resolver changed the instance name input string.
358426
if cn.DomainName() != "" {
@@ -363,14 +431,10 @@ func (d *Dialer) Dial(ctx context.Context, icn string, opts ...DialOption) (conn
363431
d.logger.Debugf(ctx, "resolved instance connection string %s to %s", icn, cn.String())
364432
}
365433

366-
cfg := d.defaultDialConfig
367-
for _, opt := range opts {
368-
opt(&cfg)
369-
}
370-
371434
var endInfo trace.EndSpanFunc
372435
ctx, endInfo = trace.StartSpan(ctx, "cloud.google.com/go/cloudsqlconn/internal.InstanceInfo")
373-
c, err := d.connectionInfoCache(ctx, cn, &cfg.useIAMAuthN)
436+
c, cacheHit, err := d.connectionInfoCache(ctx, cn, &cfg.useIAMAuthN)
437+
attrs.CacheHit = cacheHit
374438
if err != nil {
375439
endInfo(err)
376440
return nil, err
@@ -453,10 +517,16 @@ func (d *Dialer) Dial(ctx context.Context, icn string, opts ...DialOption) (conn
453517
n := c.openConnsCount.Add(1)
454518
trace.RecordOpenConnections(ctx, int64(n), d.dialerID, cn.String())
455519
trace.RecordDialLatency(ctx, icn, d.dialerID, latency)
520+
mr.RecordOpenConnection(ctx, attrs)
521+
mr.RecordConnectLatencies(ctx, attrs, latency)
456522

457523
closeFunc := func() {
458524
n := c.openConnsCount.Add(^uint64(0)) // c.openConnsCount = c.openConnsCount - 1
459525
trace.RecordOpenConnections(context.Background(), int64(n), d.dialerID, cn.String())
526+
mr.RecordClosedConnection(context.Background(), attrs)
527+
mr.RecordClosedConnectionCount(context.Background(), attrs)
528+
// lot the message to terminal for debugging purposes
529+
fmt.Println("Cloud SQL Go Connector Dialer ID:", d.dialerID, "closed connection to instance:", cn.String())
460530
}
461531
errFunc := func(err error) {
462532
// io.EOF occurs when the server closes the connection. This is safe to
@@ -553,7 +623,7 @@ func (d *Dialer) EngineVersion(ctx context.Context, icn string) (string, error)
553623
if err != nil {
554624
return "", err
555625
}
556-
c, err := d.connectionInfoCache(ctx, cn, &d.defaultDialConfig.useIAMAuthN)
626+
c, _, err := d.connectionInfoCache(ctx, cn, &d.defaultDialConfig.useIAMAuthN)
557627
if err != nil {
558628
return "", err
559629
}
@@ -577,7 +647,7 @@ func (d *Dialer) Warmup(ctx context.Context, icn string, opts ...DialOption) err
577647
for _, opt := range opts {
578648
opt(&cfg)
579649
}
580-
c, err := d.connectionInfoCache(ctx, cn, &cfg.useIAMAuthN)
650+
c, _, err := d.connectionInfoCache(ctx, cn, &cfg.useIAMAuthN)
581651
if err != nil {
582652
return err
583653
}
@@ -724,7 +794,7 @@ func createKey(cn instance.ConnName) cacheKey {
724794
// modify the existing one, or leave it unchanged as needed.
725795
func (d *Dialer) connectionInfoCache(
726796
ctx context.Context, cn instance.ConnName, useIAMAuthN *bool,
727-
) (*monitoredCache, error) {
797+
) (*monitoredCache, bool, error) {
728798
k := createKey(cn)
729799

730800
d.lock.RLock()
@@ -733,7 +803,7 @@ func (d *Dialer) connectionInfoCache(
733803

734804
if ok && !c.isClosed() {
735805
c.UpdateRefresh(useIAMAuthN)
736-
return c, nil
806+
return c, ok, nil
737807
}
738808

739809
d.lock.Lock()
@@ -745,7 +815,7 @@ func (d *Dialer) connectionInfoCache(
745815
// c exists and is not closed
746816
if ok && !c.isClosed() {
747817
c.UpdateRefresh(useIAMAuthN)
748-
return c, nil
818+
return c, ok, nil
749819
}
750820

751821
// Create a new instance of monitoredCache
@@ -756,7 +826,7 @@ func (d *Dialer) connectionInfoCache(
756826
d.logger.Debugf(ctx, "[%v] Connection info added to cache", cn.String())
757827
rsaKey, err := d.keyGenerator.rsaKey()
758828
if err != nil {
759-
return nil, err
829+
return nil, ok, err
760830
}
761831
var cache connectionInfoCache
762832
if d.lazyRefresh {
@@ -779,7 +849,7 @@ func (d *Dialer) connectionInfoCache(
779849
c = newMonitoredCache(cache, cn, d.failoverPeriod, d.resolver, d.logger)
780850
d.cache[k] = c
781851

782-
return c, nil
852+
return c, ok, nil
783853
}
784854

785855
// newMDXRequest builds a metadata exchange request based on the connection

go.mod

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,17 @@ toolchain go1.25.3
77
require (
88
cloud.google.com/go/auth v0.17.0
99
cloud.google.com/go/auth/oauth2adapt v0.2.8
10+
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0
1011
github.com/go-sql-driver/mysql v1.9.3
1112
github.com/google/uuid v1.6.0
1213
github.com/jackc/pgx/v4 v4.18.3
1314
github.com/jackc/pgx/v5 v5.7.6
1415
github.com/microsoft/go-mssqldb v1.9.3
1516
go.opencensus.io v0.24.0
17+
go.opentelemetry.io/otel v1.38.0
18+
go.opentelemetry.io/otel/metric v1.38.0
19+
go.opentelemetry.io/otel/sdk v1.38.0
20+
go.opentelemetry.io/otel/sdk/metric v1.38.0
1621
golang.org/x/net v0.46.0
1722
golang.org/x/oauth2 v0.32.0
1823
golang.org/x/time v0.14.0
@@ -24,7 +29,9 @@ require (
2429

2530
require (
2631
cloud.google.com/go/compute/metadata v0.9.0 // indirect
32+
cloud.google.com/go/monitoring v1.24.2 // indirect
2733
filippo.io/edwards25519 v1.1.0 // indirect
34+
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0 // indirect
2835
github.com/felixge/httpsnoop v1.0.4 // indirect
2936
github.com/go-logr/logr v1.4.3 // indirect
3037
github.com/go-logr/stdr v1.2.2 // indirect
@@ -43,12 +50,13 @@ require (
4350
github.com/jackc/pgtype v1.14.4 // indirect
4451
github.com/jackc/puddle/v2 v2.2.2 // indirect
4552
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
53+
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect
4654
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect
47-
go.opentelemetry.io/otel v1.38.0 // indirect
48-
go.opentelemetry.io/otel/metric v1.38.0 // indirect
4955
go.opentelemetry.io/otel/trace v1.38.0 // indirect
5056
golang.org/x/crypto v0.43.0 // indirect
5157
golang.org/x/sync v0.17.0 // indirect
5258
golang.org/x/sys v0.37.0 // indirect
5359
golang.org/x/text v0.30.0 // indirect
60+
google.golang.org/genproto v0.0.0-20250922171735-9219d122eba9 // indirect
61+
google.golang.org/genproto/googleapis/api v0.0.0-20250922171735-9219d122eba9 // indirect
5462
)

0 commit comments

Comments
 (0)