Files
Vishal SharmaandGitHub 51bffe8bf9 fix: update usage collection to remove deprecated param (#158)
* fix: update usage collection to remove deprecated param
2023-07-18 10:22:22 +05:30

454 lines
12 KiB
Go

// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package clickhousetracesexporter
import (
"context"
"encoding/json"
"fmt"
"math"
"strings"
"sync"
"time"
"github.com/ClickHouse/clickhouse-go/v2"
"github.com/SigNoz/signoz-otel-collector/usage"
"go.opencensus.io/stats"
"go.opencensus.io/stats/view"
"go.opencensus.io/tag"
"go.opentelemetry.io/collector/component"
"go.uber.org/zap"
)
type Encoding string
const (
// EncodingJSON is used for spans encoded as JSON.
EncodingJSON Encoding = "json"
// EncodingProto is used for spans encoded as Protobuf.
EncodingProto Encoding = "protobuf"
)
// SpanWriter for writing spans to ClickHouse
type SpanWriter struct {
logger *zap.Logger
db clickhouse.Conn
traceDatabase string
indexTable string
errorTable string
spansTable string
attributeTable string
attributeKeyTable string
encoding Encoding
delay time.Duration
size int
spans chan *Span
finish chan bool
done sync.WaitGroup
}
type WriterOptions struct {
logger *zap.Logger
db clickhouse.Conn
traceDatabase string
spansTable string
indexTable string
errorTable string
attributeTable string
attributeKeyTable string
encoding Encoding
delay time.Duration
size int
}
// NewSpanWriter returns a SpanWriter for the database
func NewSpanWriter(options WriterOptions) *SpanWriter {
if err := view.Register(SpansCountView, SpansCountBytesView); err != nil {
return nil
}
writer := &SpanWriter{
logger: options.logger,
db: options.db,
traceDatabase: options.traceDatabase,
indexTable: options.indexTable,
errorTable: options.errorTable,
spansTable: options.spansTable,
attributeTable: options.attributeTable,
attributeKeyTable: options.attributeKeyTable,
encoding: options.encoding,
delay: options.delay,
size: options.size,
spans: make(chan *Span, options.size),
finish: make(chan bool),
}
go writer.backgroundWriter()
return writer
}
func (w *SpanWriter) backgroundWriter() {
batch := make([]*Span, 0, w.size)
timer := time.After(w.delay)
last := time.Now()
for {
w.done.Add(1)
flush := false
finish := false
select {
case span := <-w.spans:
batch = append(batch, span)
flush = len(batch) == cap(batch)
case <-timer:
timer = time.After(w.delay)
flush = time.Since(last) > w.delay && len(batch) > 0
case <-w.finish:
finish = true
flush = len(batch) > 0
}
if flush {
if err := w.writeBatch(batch); err != nil {
w.logger.Error("Could not write a batch of spans", zap.Error(err))
}
batch = make([]*Span, 0, w.size)
last = time.Now()
}
w.done.Done()
if finish {
break
}
}
}
func (w *SpanWriter) writeBatch(batch []*Span) error {
if w.spansTable != "" {
if err := w.writeModelBatch(batch); err != nil {
logBatch := batch[:int(math.Min(10, float64(len(batch))))]
w.logger.Error("Could not write a batch of spans to model table: ", zap.Any("batch", logBatch), zap.Error(err))
return err
}
}
if w.indexTable != "" {
if err := w.writeIndexBatch(batch); err != nil {
logBatch := batch[:int(math.Min(10, float64(len(batch))))]
w.logger.Error("Could not write a batch of spans to index table: ", zap.Any("batch", logBatch), zap.Error(err))
return err
}
}
if w.errorTable != "" {
if err := w.writeErrorBatch(batch); err != nil {
logBatch := batch[:int(math.Min(10, float64(len(batch))))]
w.logger.Error("Could not write a batch of spans to error table: ", zap.Any("batch", logBatch), zap.Error(err))
return err
}
}
if w.attributeTable != "" && w.attributeKeyTable != "" {
if err := w.writeTagBatch(batch); err != nil {
w.logger.Error("Could not write a batch of spans to tag/tagKey tables: ", zap.Error(err))
return err
}
}
return nil
}
func (w *SpanWriter) writeIndexBatch(batchSpans []*Span) error {
ctx := context.Background()
statement, err := w.db.PrepareBatch(ctx, fmt.Sprintf("INSERT INTO %s.%s", w.traceDatabase, w.indexTable))
if err != nil {
logBatch := batchSpans[:int(math.Min(10, float64(len(batchSpans))))]
w.logger.Error("Could not prepare batch for index table: ", zap.Any("batch", logBatch), zap.Error(err))
return err
}
for _, span := range batchSpans {
err = statement.Append(
time.Unix(0, int64(span.StartTimeUnixNano)),
span.TraceId,
span.SpanId,
span.ParentSpanId,
span.ServiceName,
span.Name,
span.Kind,
span.DurationNano,
span.StatusCode,
span.ExternalHttpMethod,
span.ExternalHttpUrl,
span.Component,
span.DBSystem,
span.DBName,
span.DBOperation,
span.PeerService,
span.Events,
span.HttpMethod,
span.HttpUrl,
span.HttpCode,
span.HttpRoute,
span.HttpHost,
span.MsgSystem,
span.MsgOperation,
span.HasError,
span.TagMap,
span.GRPCMethod,
span.GRPCCode,
span.RPCSystem,
span.RPCService,
span.RPCMethod,
span.ResponseStatusCode,
span.StringTagMap,
span.NumberTagMap,
span.BoolTagMap,
span.ResourceTagsMap,
)
if err != nil {
w.logger.Error("Could not append span to batch: ", zap.Object("span", span), zap.Error(err))
return err
}
}
start := time.Now()
err = statement.Send()
ctx, _ = tag.New(ctx,
tag.Upsert(exporterKey, string(component.DataTypeTraces)),
tag.Upsert(tableKey, w.indexTable),
)
stats.Record(ctx, writeLatencyMillis.M(int64(time.Since(start).Milliseconds())))
return err
}
func (w *SpanWriter) writeTagBatch(batchSpans []*Span) error {
ctx := context.Background()
tagStatement, err := w.db.PrepareBatch(ctx, fmt.Sprintf("INSERT INTO %s.%s", w.traceDatabase, w.attributeTable))
if err != nil {
logBatch := batchSpans[:int(math.Min(10, float64(len(batchSpans))))]
w.logger.Error("Could not prepare batch for span attributes table due to error: ", zap.Error(err), zap.Any("batch", logBatch))
return err
}
tagKeyStatement, err := w.db.PrepareBatch(ctx, fmt.Sprintf("INSERT INTO %s.%s", w.traceDatabase, w.attributeKeyTable))
if err != nil {
logBatch := batchSpans[:int(math.Min(10, float64(len(batchSpans))))]
w.logger.Error("Could not prepare batch for span attributes key table due to error: ", zap.Error(err), zap.Any("batch", logBatch))
return err
}
for _, span := range batchSpans {
for _, spanAttribute := range span.SpanAttributes {
err = tagKeyStatement.Append(
spanAttribute.Key,
spanAttribute.TagType,
spanAttribute.DataType,
spanAttribute.IsColumn,
)
if err != nil {
w.logger.Error("Could not append span to tagKey Statement to batch due to error: ", zap.Error(err), zap.Object("span", span))
return err
}
if spanAttribute.DataType == "string" {
err = tagStatement.Append(
time.Unix(0, int64(span.StartTimeUnixNano)),
spanAttribute.Key,
spanAttribute.TagType,
spanAttribute.DataType,
spanAttribute.StringValue,
nil,
spanAttribute.IsColumn,
)
} else if spanAttribute.DataType == "float64" {
err = tagStatement.Append(
time.Unix(0, int64(span.StartTimeUnixNano)),
spanAttribute.Key,
spanAttribute.TagType,
spanAttribute.DataType,
nil,
spanAttribute.NumberValue,
spanAttribute.IsColumn,
)
} else if spanAttribute.DataType == "bool" {
err = tagStatement.Append(
time.Unix(0, int64(span.StartTimeUnixNano)),
spanAttribute.Key,
spanAttribute.TagType,
spanAttribute.DataType,
nil,
nil,
spanAttribute.IsColumn,
)
}
if err != nil {
w.logger.Error("Could not append span to tag Statement batch due to error: ", zap.Error(err), zap.Object("span", span))
return err
}
}
}
tagStart := time.Now()
err = tagStatement.Send()
stats.RecordWithTags(ctx,
[]tag.Mutator{
tag.Upsert(exporterKey, string(component.DataTypeTraces)),
tag.Upsert(tableKey, w.attributeTable),
},
writeLatencyMillis.M(int64(time.Since(tagStart).Milliseconds())),
)
if err != nil {
logBatch := batchSpans[:int(math.Min(10, float64(len(batchSpans))))]
w.logger.Error("Could not write to span attributes table due to error: ", zap.Error(err), zap.Any("batch", logBatch))
return err
}
tagKeyStart := time.Now()
err = tagKeyStatement.Send()
stats.RecordWithTags(ctx,
[]tag.Mutator{
tag.Upsert(exporterKey, string(component.DataTypeTraces)),
tag.Upsert(tableKey, w.attributeKeyTable),
},
writeLatencyMillis.M(int64(time.Since(tagKeyStart).Milliseconds())),
)
if err != nil {
logBatch := batchSpans[:int(math.Min(10, float64(len(batchSpans))))]
w.logger.Error("Could not write to span attributes key table due to error: ", zap.Error(err), zap.Any("batch", logBatch))
return err
}
return err
}
func (w *SpanWriter) writeErrorBatch(batchSpans []*Span) error {
ctx := context.Background()
statement, err := w.db.PrepareBatch(ctx, fmt.Sprintf("INSERT INTO %s.%s", w.traceDatabase, w.errorTable))
if err != nil {
logBatch := batchSpans[:int(math.Min(10, float64(len(batchSpans))))]
w.logger.Error("Could not prepare batch for error table: ", zap.Any("batch", logBatch), zap.Error(err))
return err
}
for _, span := range batchSpans {
if span.ErrorEvent.Name == "" {
continue
}
err = statement.Append(
time.Unix(0, int64(span.ErrorEvent.TimeUnixNano)),
span.ErrorID,
span.ErrorGroupID,
span.TraceId,
span.SpanId,
span.ServiceName,
span.ErrorEvent.AttributeMap["exception.type"],
span.ErrorEvent.AttributeMap["exception.message"],
span.ErrorEvent.AttributeMap["exception.stacktrace"],
stringToBool(span.ErrorEvent.AttributeMap["exception.escaped"]),
span.ResourceTagsMap,
)
if err != nil {
w.logger.Error("Could not append span to batch: ", zap.Object("span", span), zap.Error(err))
return err
}
}
start := time.Now()
err = statement.Send()
ctx, _ = tag.New(ctx,
tag.Upsert(exporterKey, string(component.DataTypeTraces)),
tag.Upsert(tableKey, w.errorTable),
)
stats.Record(ctx, writeLatencyMillis.M(int64(time.Since(start).Milliseconds())))
return err
}
func stringToBool(s string) bool {
if strings.ToLower(s) == "true" {
return true
}
return false
}
func (w *SpanWriter) writeModelBatch(batchSpans []*Span) error {
ctx := context.Background()
statement, err := w.db.PrepareBatch(ctx, fmt.Sprintf("INSERT INTO %s.%s", w.traceDatabase, w.spansTable))
if err != nil {
logBatch := batchSpans[:int(math.Min(10, float64(len(batchSpans))))]
w.logger.Error("Could not prepare batch for model table: ", zap.Any("batch", logBatch), zap.Error(err))
return err
}
metrics := map[string]usage.Metric{}
for _, span := range batchSpans {
var serialized []byte
usageMap := span.TraceModel
usageMap.TagMap = map[string]string{}
serialized, err = json.Marshal(span.TraceModel)
serializedUsage, err := json.Marshal(usageMap)
if err != nil {
return err
}
err = statement.Append(time.Unix(0, int64(span.StartTimeUnixNano)), span.TraceId, string(serialized))
if err != nil {
w.logger.Error("Could not append span to batch: ", zap.Object("span", span), zap.Error(err))
return err
}
usage.AddMetric(metrics, *span.Tenant, 1, int64(len(serializedUsage)))
}
start := time.Now()
err = statement.Send()
ctx, _ = tag.New(ctx,
tag.Upsert(exporterKey, string(component.DataTypeTraces)),
tag.Upsert(tableKey, w.spansTable),
)
stats.Record(ctx, writeLatencyMillis.M(int64(time.Since(start).Milliseconds())))
if err != nil {
return err
}
for k, v := range metrics {
stats.RecordWithTags(ctx, []tag.Mutator{tag.Upsert(usage.TagTenantKey, k)}, ExporterSigNozSentSpans.M(int64(v.Count)), ExporterSigNozSentSpansBytes.M(int64(v.Size)))
}
return nil
}
// WriteSpan writes the encoded span
func (w *SpanWriter) WriteSpan(span *Span) error {
w.spans <- span
return nil
}
// Close Implements io.Closer and closes the underlying storage
func (w *SpanWriter) Close() error {
w.finish <- true
w.done.Wait()
return nil
}