discovery/gce: keep Compute SD client from defeating dead-code elimination

The Discovery struct held *compute.Service and *compute.InstancesService as
fields and is boxed into the discovery.Discoverer interface. Once reflection is
reachable in the program (it always is, via the YAML/config machinery), the Go
linker conservatively retains every exported method of any concrete type
reachable through an interface, including via struct fields. *compute.Service
exposes ~150 sub-services and their operations, so all of them — 994 list/get
operations and their serializers — were retained even though discovery only
calls Instances.List.

Wrap the single used operation in a closure over the concrete *compute.Service
so the service lives only in closure context, which reflection cannot traverse.
Returning a *compute.InstancesListCall would not help, since that type has an
s *Service back-reference that re-propagates the marker, so the closure
encapsulates the whole List/Filter/Pages chain and only exposes the
*compute.InstanceList data type discovery already uses.

compute/v1 footprint drops from ~4.9 MB to ~33 KB, and the prometheus and
promtool binaries each shrink by ~13.5 MB. No functional or API change.

Signed-off-by: Julien Pivotto <291750+roidelapluie@users.noreply.github.com>
This commit is contained in:
Julien Pivotto 2026-05-29 13:50:08 +02:00
parent 55226fb8b2
commit fd493fe25a

View file

@ -17,7 +17,6 @@ import (
"context"
"errors"
"fmt"
"net/http"
"strconv"
"strings"
"time"
@ -113,18 +112,34 @@ func (c *SDConfig) UnmarshalYAML(unmarshal func(any) error) error {
return nil
}
// instancesLister lists the instances of a project/zone, paging through the
// results and invoking f for each page.
type instancesLister func(ctx context.Context, f func(*compute.InstanceList) error) error
// newInstancesLister captures *compute.Service in a closure instead of storing
// it on the interface-boxed Discovery struct. This keeps the binary smaller:
// otherwise reflection keeps every Compute method live, defeating dead-code
// elimination.
func newInstancesLister(svc *compute.Service, project, zone, filter string) instancesLister {
isvc := compute.NewInstancesService(svc)
return func(ctx context.Context, f func(*compute.InstanceList) error) error {
ilc := isvc.List(project, zone)
if filter != "" {
ilc = ilc.Filter(filter)
}
return ilc.Pages(ctx, f)
}
}
// Discovery periodically performs GCE-SD requests. It implements
// the Discoverer interface.
type Discovery struct {
*refresh.Discovery
project string
zone string
filter string
client *http.Client
svc *compute.Service
isvc *compute.InstancesService
port int
tagSeparator string
project string
zone string
listInstances instancesLister
port int
tagSeparator string
}
// NewDiscovery returns a new Discovery which periodically refreshes its targets.
@ -137,20 +152,18 @@ func NewDiscovery(conf SDConfig, opts discovery.DiscovererOptions) (*Discovery,
d := &Discovery{
project: conf.Project,
zone: conf.Zone,
filter: conf.Filter,
port: conf.Port,
tagSeparator: conf.TagSeparator,
}
var err error
d.client, err = google.DefaultClient(context.Background(), compute.ComputeReadonlyScope)
client, err := google.DefaultClient(context.Background(), compute.ComputeReadonlyScope)
if err != nil {
return nil, fmt.Errorf("error setting up communication with GCE service: %w", err)
}
d.svc, err = compute.NewService(context.Background(), option.WithHTTPClient(d.client))
svc, err := compute.NewService(context.Background(), option.WithHTTPClient(client))
if err != nil {
return nil, fmt.Errorf("error setting up communication with GCE service: %w", err)
}
d.isvc = compute.NewInstancesService(d.svc)
d.listInstances = newInstancesLister(svc, conf.Project, conf.Zone, conf.Filter)
d.Discovery = refresh.NewDiscovery(
refresh.Options{
@ -170,11 +183,7 @@ func (d *Discovery) refresh(ctx context.Context) ([]*targetgroup.Group, error) {
Source: fmt.Sprintf("GCE_%s_%s", d.project, d.zone),
}
ilc := d.isvc.List(d.project, d.zone)
if d.filter != "" {
ilc = ilc.Filter(d.filter)
}
err := ilc.Pages(ctx, func(l *compute.InstanceList) error {
err := d.listInstances(ctx, func(l *compute.InstanceList) error {
for _, inst := range l.Items {
if len(inst.NetworkInterfaces) == 0 {
continue