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 otlptest
16
17import (
18	"sort"
19
20	collectormetricpb "go.opentelemetry.io/otel/exporters/otlp/internal/opentelemetry-proto-gen/collector/metrics/v1"
21	collectortracepb "go.opentelemetry.io/otel/exporters/otlp/internal/opentelemetry-proto-gen/collector/trace/v1"
22	commonpb "go.opentelemetry.io/otel/exporters/otlp/internal/opentelemetry-proto-gen/common/v1"
23	metricpb "go.opentelemetry.io/otel/exporters/otlp/internal/opentelemetry-proto-gen/metrics/v1"
24	resourcepb "go.opentelemetry.io/otel/exporters/otlp/internal/opentelemetry-proto-gen/resource/v1"
25	tracepb "go.opentelemetry.io/otel/exporters/otlp/internal/opentelemetry-proto-gen/trace/v1"
26)
27
28// Collector is an interface that mock collectors should implements,
29// so they can be used for the end-to-end testing.
30type Collector interface {
31	Stop() error
32	GetResourceSpans() []*tracepb.ResourceSpans
33	GetMetrics() []*metricpb.Metric
34}
35
36// SpansStorage stores the spans. Mock collectors could use it to
37// store spans they have received.
38type SpansStorage struct {
39	rsm       map[string]*tracepb.ResourceSpans
40	spanCount int
41}
42
43// MetricsStorage stores the metrics. Mock collectors could use it to
44// store metrics they have received.
45type MetricsStorage struct {
46	metrics []*metricpb.Metric
47}
48
49// NewSpansStorage creates a new spans storage.
50func NewSpansStorage() SpansStorage {
51	return SpansStorage{
52		rsm: make(map[string]*tracepb.ResourceSpans),
53	}
54}
55
56// AddSpans adds spans to the spans storage.
57func (s *SpansStorage) AddSpans(request *collectortracepb.ExportTraceServiceRequest) {
58	for _, rs := range request.GetResourceSpans() {
59		rstr := resourceString(rs.Resource)
60		if existingRs, ok := s.rsm[rstr]; !ok {
61			s.rsm[rstr] = rs
62			// TODO (rghetia): Add support for library Info.
63			if len(rs.InstrumentationLibrarySpans) == 0 {
64				rs.InstrumentationLibrarySpans = []*tracepb.InstrumentationLibrarySpans{
65					{
66						Spans: []*tracepb.Span{},
67					},
68				}
69			}
70			s.spanCount += len(rs.InstrumentationLibrarySpans[0].Spans)
71		} else {
72			if len(rs.InstrumentationLibrarySpans) > 0 {
73				newSpans := rs.InstrumentationLibrarySpans[0].GetSpans()
74				existingRs.InstrumentationLibrarySpans[0].Spans =
75					append(existingRs.InstrumentationLibrarySpans[0].Spans,
76						newSpans...)
77				s.spanCount += len(newSpans)
78			}
79		}
80	}
81}
82
83// GetSpans returns the stored spans.
84func (s *SpansStorage) GetSpans() []*tracepb.Span {
85	spans := make([]*tracepb.Span, 0, s.spanCount)
86	for _, rs := range s.rsm {
87		spans = append(spans, rs.InstrumentationLibrarySpans[0].Spans...)
88	}
89	return spans
90}
91
92// GetResourceSpans returns the stored resource spans.
93func (s *SpansStorage) GetResourceSpans() []*tracepb.ResourceSpans {
94	rss := make([]*tracepb.ResourceSpans, 0, len(s.rsm))
95	for _, rs := range s.rsm {
96		rss = append(rss, rs)
97	}
98	return rss
99}
100
101// NewMetricsStorage creates a new metrics storage.
102func NewMetricsStorage() MetricsStorage {
103	return MetricsStorage{}
104}
105
106// AddMetrics adds metrics to the metrics storage.
107func (s *MetricsStorage) AddMetrics(request *collectormetricpb.ExportMetricsServiceRequest) {
108	for _, rm := range request.GetResourceMetrics() {
109		// TODO (rghetia) handle multiple resource and library info.
110		if len(rm.InstrumentationLibraryMetrics) > 0 {
111			s.metrics = append(s.metrics, rm.InstrumentationLibraryMetrics[0].Metrics...)
112		}
113	}
114}
115
116// GetMetrics returns the stored metrics.
117func (s *MetricsStorage) GetMetrics() []*metricpb.Metric {
118	// copy in order to not change.
119	m := make([]*metricpb.Metric, 0, len(s.metrics))
120	return append(m, s.metrics...)
121}
122
123func resourceString(res *resourcepb.Resource) string {
124	sAttrs := sortedAttributes(res.GetAttributes())
125	rstr := ""
126	for _, attr := range sAttrs {
127		rstr = rstr + attr.String()
128	}
129	return rstr
130}
131
132func sortedAttributes(attrs []*commonpb.KeyValue) []*commonpb.KeyValue {
133	sort.Slice(attrs[:], func(i, j int) bool {
134		return attrs[i].Key < attrs[j].Key
135	})
136	return attrs
137}
138