1// Copyright The OpenTelemetry Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//       http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package scraperhelper
16
17import (
18	"context"
19
20	"go.opentelemetry.io/collector/component"
21	"go.opentelemetry.io/collector/component/componenthelper"
22	"go.opentelemetry.io/collector/config"
23	"go.opentelemetry.io/collector/model/pdata"
24	"go.opentelemetry.io/collector/obsreport"
25	"go.opentelemetry.io/collector/receiver/scrapererror"
26)
27
28// ScrapeMetrics scrapes metrics.
29type ScrapeMetrics func(context.Context) (pdata.MetricSlice, error)
30
31// ScrapeResourceMetrics scrapes resource metrics.
32type ScrapeResourceMetrics func(context.Context) (pdata.ResourceMetricsSlice, error)
33
34type baseSettings struct {
35	componentOptions []componenthelper.Option
36}
37
38// ScraperOption apply changes to internal options.
39type ScraperOption func(*baseSettings)
40
41// Scraper is the base interface for scrapers.
42type Scraper interface {
43	component.Component
44
45	// ID returns the scraper id.
46	ID() config.ComponentID
47	Scrape(context.Context, config.ComponentID) (pdata.Metrics, error)
48}
49
50type baseScraper struct {
51	component.Component
52	id config.ComponentID
53}
54
55func (b baseScraper) ID() config.ComponentID {
56	return b.id
57}
58
59// WithStart sets the function that will be called on startup.
60func WithStart(start componenthelper.StartFunc) ScraperOption {
61	return func(o *baseSettings) {
62		o.componentOptions = append(o.componentOptions, componenthelper.WithStart(start))
63	}
64}
65
66// WithShutdown sets the function that will be called on shutdown.
67func WithShutdown(shutdown componenthelper.ShutdownFunc) ScraperOption {
68	return func(o *baseSettings) {
69		o.componentOptions = append(o.componentOptions, componenthelper.WithShutdown(shutdown))
70	}
71}
72
73type metricsScraper struct {
74	baseScraper
75	ScrapeMetrics
76}
77
78var _ Scraper = (*metricsScraper)(nil)
79
80// NewMetricsScraper creates a Scraper that calls Scrape at the specified
81// collection interval, reports observability information, and passes the
82// scraped metrics to the next consumer.
83func NewMetricsScraper(
84	name string,
85	scrape ScrapeMetrics,
86	options ...ScraperOption,
87) Scraper {
88	set := &baseSettings{}
89	for _, op := range options {
90		op(set)
91	}
92
93	ms := &metricsScraper{
94		baseScraper: baseScraper{
95			Component: componenthelper.New(set.componentOptions...),
96			id:        config.NewID(config.Type(name)),
97		},
98		ScrapeMetrics: scrape,
99	}
100
101	return ms
102}
103
104func (ms metricsScraper) Scrape(ctx context.Context, receiverID config.ComponentID) (pdata.Metrics, error) {
105	ctx = obsreport.ScraperContext(ctx, receiverID, ms.ID())
106	scrp := obsreport.NewScraper(obsreport.ScraperSettings{ReceiverID: receiverID, Scraper: ms.ID()})
107	ctx = scrp.StartMetricsOp(ctx)
108	metrics, err := ms.ScrapeMetrics(ctx)
109	count := 0
110	md := pdata.Metrics{}
111	if err == nil || scrapererror.IsPartialScrapeError(err) {
112		md = pdata.NewMetrics()
113		metrics.MoveAndAppendTo(md.ResourceMetrics().AppendEmpty().InstrumentationLibraryMetrics().AppendEmpty().Metrics())
114		count = md.MetricCount()
115	}
116	scrp.EndMetricsOp(ctx, count, err)
117	return md, err
118}
119
120type resourceMetricsScraper struct {
121	baseScraper
122	ScrapeResourceMetrics
123}
124
125var _ Scraper = (*resourceMetricsScraper)(nil)
126
127// NewResourceMetricsScraper creates a Scraper that calls Scrape at the
128// specified collection interval, reports observability information, and
129// passes the scraped resource metrics to the next consumer.
130func NewResourceMetricsScraper(
131	id config.ComponentID,
132	scrape ScrapeResourceMetrics,
133	options ...ScraperOption,
134) Scraper {
135	set := &baseSettings{}
136	for _, op := range options {
137		op(set)
138	}
139
140	rms := &resourceMetricsScraper{
141		baseScraper: baseScraper{
142			Component: componenthelper.New(set.componentOptions...),
143			id:        id,
144		},
145		ScrapeResourceMetrics: scrape,
146	}
147
148	return rms
149}
150
151func (rms resourceMetricsScraper) Scrape(ctx context.Context, receiverID config.ComponentID) (pdata.Metrics, error) {
152	ctx = obsreport.ScraperContext(ctx, receiverID, rms.ID())
153	scrp := obsreport.NewScraper(obsreport.ScraperSettings{ReceiverID: receiverID, Scraper: rms.ID()})
154	ctx = scrp.StartMetricsOp(ctx)
155	resourceMetrics, err := rms.ScrapeResourceMetrics(ctx)
156
157	count := 0
158	md := pdata.Metrics{}
159	if err == nil || scrapererror.IsPartialScrapeError(err) {
160		md = pdata.NewMetrics()
161		resourceMetrics.MoveAndAppendTo(md.ResourceMetrics())
162		count = md.MetricCount()
163	}
164
165	scrp.EndMetricsOp(ctx, count, err)
166	return md, err
167}
168