-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Metrics stdout export pipeline #265
Changes from 47 commits
19346a4
cb9557a
03f7854
09d88a1
865b7ab
5719abe
c6ca4fd
acc2450
5bebed5
5b23af4
80e0df8
f533814
3423242
a788631
fbc50a1
49aa969
c2c73be
5ea9128
f0d986c
63d79a8
8716da3
837035d
9586471
0c09f8c
03ff7d2
88a236e
bf313da
db41c9d
dd6229c
0bc5ffe
c575b08
3be8e6e
214b882
048c8d9
657c064
0d78cfa
f81aa34
94580ae
2dee926
e4c9dde
a7623d8
8fcfe95
df3b3af
4121362
41d3f7b
72872fa
0160c3d
77e4c3f
dc23ae1
18b8340
dfbc881
9ecdf51
60ab98b
bf2f1e6
419ed4f
0953112
8034ebe
a472048
08a26de
c09bd0e
9ef0a37
557f912
0133786
e518398
72e3d30
9acdc5a
d7a5cda
399c34c
2a75566
efb75ed
1153503
623a63a
e298c94
d75bc6e
723d084
8b5a4d6
13e0580
b75059e
d03709a
93fe58f
782cabf
eedaaab
f67b47c
2e7007d
046db4a
a294720
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
// Copyright 2019, 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 stdout // import "go.opentelemetry.io/otel/exporter/metric/stdout" | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"os" | ||
"strings" | ||
"time" | ||
|
||
export "go.opentelemetry.io/otel/sdk/export/metric" | ||
"go.opentelemetry.io/otel/sdk/metric/aggregator" | ||
) | ||
|
||
type Exporter struct { | ||
options Options | ||
} | ||
|
||
// Options are the options to be used when initializing a stdout export. | ||
type Options struct { | ||
// File is the destination. If not set, os.Stdout is used. | ||
File *os.File | ||
|
||
// PrettyPrint will pretty the json representation of the span, | ||
// making it print "pretty". Default is false. | ||
PrettyPrint bool | ||
|
||
// DoNotPrintTime suppresses timestamp printing. This is | ||
// useful to create testable examples or if the are being | ||
jmacd marked this conversation as resolved.
Show resolved
Hide resolved
|
||
DoNotPrintTime bool | ||
|
||
// Quantiles are the desired aggregation quantiles for measure | ||
// metric data, used when the configured aggregator supports | ||
// quantiles. | ||
// | ||
// Note: this exporter is meant as a demonstration; a real | ||
// exporter may wish to configure quantiles on a per-metric | ||
// basis. | ||
Quantiles []float64 | ||
jmacd marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
type expoBatch struct { | ||
Timestamp *time.Time `json:"time,omitempty"` | ||
Updates []expoLine `json:"updates,omitempty"` | ||
} | ||
|
||
type expoLine struct { | ||
Name string `json:"name"` | ||
Max interface{} `json:"max,omitempty"` | ||
Sum interface{} `json:"sum,omitempty"` | ||
Count interface{} `json:"count,omitempty"` | ||
LastValue interface{} `json:"last,omitempty"` | ||
|
||
// Note: this is a pointer because omitempty doesn't work when time.IsZero() | ||
Timestamp *time.Time `json:"time,omitempty"` | ||
} | ||
|
||
var _ export.Exporter = &Exporter{} | ||
jmacd marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
func New(options Options) *Exporter { | ||
if options.File == nil { | ||
options.File = os.Stdout | ||
} | ||
return &Exporter{ | ||
options: options, | ||
} | ||
} | ||
|
||
func (e *Exporter) Export(_ context.Context, producer export.Producer) error { | ||
var batch expoBatch | ||
if !e.options.DoNotPrintTime { | ||
ts := time.Now() | ||
batch.Timestamp = &ts | ||
} | ||
producer.Foreach(func(record export.Record) { | ||
desc := record.Descriptor() | ||
labels := record.Labels() | ||
jmacd marked this conversation as resolved.
Show resolved
Hide resolved
|
||
agg := record.Aggregator() | ||
|
||
var expose expoLine | ||
if sum, ok := agg.(aggregator.Sum); ok { | ||
expose.Sum = sum.Sum().Emit(desc.NumberKind()) | ||
|
||
} else if lv, ok := agg.(aggregator.LastValue); ok { | ||
ts := lv.Timestamp() | ||
expose.LastValue = lv.LastValue().Emit(desc.NumberKind()) | ||
expose.Timestamp = &ts | ||
jmacd marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
} else if msc, ok := agg.(aggregator.MaxSumCount); ok { | ||
expose.Max = msc.Max().Emit(desc.NumberKind()) | ||
expose.Sum = msc.Sum().Emit(desc.NumberKind()) | ||
expose.Count = msc.Count().Emit(desc.NumberKind()) | ||
|
||
} else if dist, ok := agg.(aggregator.Distribution); ok { | ||
expose.Max = dist.Max().Emit(desc.NumberKind()) | ||
expose.Sum = dist.Sum().Emit(desc.NumberKind()) | ||
expose.Count = dist.Count().Emit(desc.NumberKind()) | ||
|
||
// TODO print one configured quantile per line | ||
} | ||
|
||
var sb strings.Builder | ||
|
||
sb.WriteString(desc.Name()) | ||
|
||
if labels.Len() > 0 { | ||
sb.WriteRune('{') | ||
sb.WriteString(labels.Encoded()) | ||
sb.WriteRune('}') | ||
} | ||
|
||
expose.Name = sb.String() | ||
|
||
batch.Updates = append(batch.Updates, expose) | ||
}) | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: Shouldn't you return err here first if Otherwise you might export output when there's an error that happened as you processed the batch? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For this exporter specifically--if it is only an aggregator error not a marshal error--I figured it would be best to continue formatting the output and fill in "NaN". I figure NaN will parse as an error, but this at least yields a human-readable output. A single error is still returned, which the controller will print. I can be convinced to be less tolerant of errors though. |
||
var data []byte | ||
var err error | ||
if e.options.PrettyPrint { | ||
data, err = json.MarshalIndent(batch, "", "\t") | ||
} else { | ||
data, err = json.Marshal(batch) | ||
} | ||
|
||
if err != nil { | ||
return err | ||
} | ||
|
||
fmt.Fprintln(e.options.File, string(data)) | ||
return nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use of these extra labels was not obvious until I understood how everything works. Essentially they are ignored because it is using defaultkey batcher which only uses labels defined as part of metric descriptor.
Also, labels set via distributedcontext are not automatically used. It is by design but it is not obvious.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I sort of feel like throwing away this example. It's full of random, incoherent calls.