From fd493fe25acf3cd0cc69debc478f2b7923934db4 Mon Sep 17 00:00:00 2001 From: Julien Pivotto <291750+roidelapluie@users.noreply.github.com> Date: Fri, 29 May 2026 13:50:08 +0200 Subject: [PATCH] discovery/gce: keep Compute SD client from defeating dead-code elimination MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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> --- discovery/gce/gce.go | 47 ++++++++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/discovery/gce/gce.go b/discovery/gce/gce.go index 6525ef9d90..507a3238a3 100644 --- a/discovery/gce/gce.go +++ b/discovery/gce/gce.go @@ -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